You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用Jenkins API导致Groovy函数未被CPS转换的问题咨询

问题描述

我编写了如下测试函数:

def doSomething() {
    def node = Jenkins.get().getNode("node-name")
    def count = 0
    retry (5) {
        println "hello"
        count += 1
        if (count < 3) {
            throw new InterruptedException()
        }
    }
}

该函数在Pipeline的stage步骤中通过script块调用:

stage(..) {
  steps {
    script {
      doSomething()
    }

  }
}

运行时出现CPS序列化异常:

an exception which occurred:
    in field com.cloudbees.groovy.cps.impl.BlockScopeEnv.locals
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@5e799cd4
    in field com.cloudbees.groovy.cps.impl.CpsClosureDef.capture
    in object com.cloudbees.groovy.cps.impl.CpsClosureDef@e7332c3
    in field com.cloudbees.groovy.cps.impl.CpsClosure.def
    in object org.jenkinsci.plugins.workflow.cps.CpsClosure2@4fed11ca
    in field org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.closures
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@74cb9088
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@74cb9088
 Caused: java.io.NotSerializableException: hudson.model.Hudson
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:274)
    at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
...

移除def node = Jenkins.get().getNode..代码后异常消失。想咨询:是否因为CPS代码中无法调用Jenkins API?若是,原因是什么?

解答

不是CPS代码完全不能调用Jenkins API,问题出在Jenkins API返回的对象不可序列化,而Pipeline的CPS机制要求所有在闭包上下文里的变量必须是可序列化的。

具体原因:

  • Jenkins Pipeline采用CPS(Continuation-Passing Style)执行模式,会在执行暂停(比如重试、等待阶段切换)时序列化整个执行上下文,以便后续恢复执行。
  • Jenkins.get().getNode("node-name")获取到的节点对象,底层关联了hudson.model.Hudson(Jenkins核心实例),这个对象未实现Serializable接口,无法被序列化。
  • retry闭包会捕获所在作用域的所有变量(包括node),当Jenkins尝试序列化闭包上下文时,发现node引用了不可序列化的Hudson对象,就抛出了NotSerializableException。

解决办法:

  • 如果不需要在闭包里使用node变量,在闭包执行前切断对它的引用:
    def doSomething() {
        def node = Jenkins.get().getNode("node-name")
        // 处理node的逻辑,比如获取节点信息
        node = null // 切断引用,避免被闭包捕获
        def count = 0
        retry (5) {
            println "hello"
            count += 1
            if (count < 3) {
                throw new InterruptedException()
            }
        }
    }
    
  • 如果需要调用Jenkins API,用@NonCPS注解标记相关方法,让方法脱离CPS序列化机制执行(注意:@NonCPS方法里不能调用Pipeline步骤,也不能返回不可序列化对象给CPS上下文):
    @NonCPS
    def getJenkinsNode(String nodeName) {
        return Jenkins.get().getNode(nodeName)
    }
    
    def doSomething() {
        def node = getJenkinsNode("node-name")
        // 仅提取可序列化的属性,比如节点名称
        def nodeName = node.name
        node = null
        def count = 0
        retry (5) {
            println "hello, ${nodeName}"
            count += 1
            if (count < 3) {
                throw new InterruptedException()
            }
        }
    }
    
  • 优先使用Pipeline原生步骤(比如node步骤)操作节点,这类步骤已做序列化兼容处理,避免直接调用底层Jenkins API。

内容的提问来源于stack exchange,提问作者rjs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 11:55:26