Jenkins流水线调用类返回闭包时执行shell命令无限阻塞求助
解决Jenkins流水线中闭包执行shell命令无限阻塞的问题
我之前也碰到过一模一样的问题,核心原因是你用@NonCPS注解包裹了返回闭包的buildDriver方法,这和Jenkins流水线的CPS执行机制直接冲突了。
问题根源
Jenkins流水线依赖**CPS(续体传递风格)**来管理执行流程:它需要把所有流水线相关的操作(比如sh、stage)转换成CPS兼容的形式,这样才能正确跟踪执行状态、处理异步逻辑,以及在需要时序列化/恢复流水线上下文。
而你给buildDriver添加了@NonCPS注解,这会让Jenkins绕过对该方法的CPS转换。但你在方法内部返回的闭包里,又包含了大量Jenkins流水线API的调用——当你调用闭包的run()方法时,这些API调用无法被CPS机制正确跟踪,导致shell命令执行完成后,Jenkins找不到回到原执行上下文的路径,最终陷入无限阻塞。
解决方案
方案1:移除@NonCPS注解
直接去掉buildDriver方法上的@NonCPS,让Jenkins对这个方法和它返回的闭包进行CPS转换,这样就能正常跟踪执行流程了:
class Device { def context public Device(context) { this.context = context } // 移除@NonCPS注解 def buildDriver() { return ({ context.stage("Build Driver") { context.script { context.print("111111111111") context.sh("pwd;") context.print("222222222222") context.sh("ls -al") context.print("333333333333") } } }) } } dev = new Device(this) pipeline { agent any stages { stage("main stage") { steps { script { print("start script") dev.buildDriver().run() } } } } }
方案2:直接执行流水线步骤(更推荐)
其实完全没必要返回闭包,直接在buildDriver方法内部执行流水线步骤即可,这种写法更符合Jenkins的执行逻辑,也能从根源上避免闭包带来的上下文跟踪问题:
class Device { def context public Device(context) { this.context = context } def buildDriver() { context.stage("Build Driver") { context.script { context.print("111111111111") context.sh("pwd;") context.print("222222222222") context.sh("ls -al") context.print("333333333333") } } } } dev = new Device(this) pipeline { agent any stages { stage("main stage") { steps { script { print("start script") // 直接调用方法,无需run() dev.buildDriver() } } } } }
注意事项
在Jenkins自定义类中使用流水线API时,记住这一点:
@NonCPS注解只适合处理纯Groovy业务逻辑(比如字符串处理、数据计算、工具类方法),绝对不能用它包裹涉及Jenkins流水线步骤(sh、stage、script等)的方法,否则会破坏CPS的执行跟踪机制,引发各种奇怪的问题。
内容的提问来源于stack exchange,提问作者김현준
相关产品推荐
相关产品推荐

