Jenkins Pipeline 中使用 JsonSlurper 报错:cannot find current thread

发布于:2025-07-20 ⋅ 阅读:(18) ⋅ 点赞:(0)

🌟 背景

在 Jenkins 声明式 Pipeline 中,有时我们需要解析一段 JSON 字符串,例如部署路径、构建参数等。在 Groovy 中,最常见的方式是使用 JsonSlurper

def jsonData = new groovy.json.JsonSlurper().parseText(myJsonText)

然而,在 Jenkins 中你很可能会遇到如下报错:

java.io.IOException: cannot find current thread

这类报错常常让人摸不着头脑。为什么在 Groovy 中正常工作的代码,在 Jenkins Pipeline 中却报错?


⚠ 问题重现

pipeline {
    agent any
    stages {
        stage('Parse JSON') {
            steps {
                script {
                    def jsonText = '[{"src":"/a","dest":"/b"}]'
                    def deployList = new groovy.json.JsonSlurper().parseText(jsonText) // 报错行
                }
            }
        }
    }
}

运行报错:

Cannot contact <node>: java.io.IOException: cannot find current thread

🧠 原因解析:CPS 与非 CPS 安全方法冲突

Jenkins Pipeline 基于 Groovy CPS(Continuation Passing Style)转换机制 实现“流水线可恢复性”。这意味着:

  • Pipeline 中的脚本会被 Jenkins 转换成 CPS 代码
  • CPS 会将执行状态保存到磁盘,以支持“中断恢复”、“断点续跑”
  • 然而,一些方法(如 JsonSlurper.parseText())不是 CPS 安全的,即不能被 Jenkins 正确序列化和恢复

✴ 为什么会报错?

JsonSlurper 会在底层调用 Thread.currentThread()、或使用 Java 原生 IO API,这在 CPS 上下文中是不被支持的操作。因此 Jenkins 抛出:

java.io.IOException: cannot find current thread

本质上,是 Jenkins 的 CPS 执行引擎无法“保存你执行的上下文状态”。


✅ 解决方案一:使用 @NonCPS 注解(经典方案)

将解析方法单独封装,并添加 @NonCPS 注解:

@NonCPS
def parseDeployPath(String jsonText) {
    try {
        if (jsonText == null || jsonText.trim() == "") {
            // 输入为空,返回空列表
            return []
        }
        def rawList = new groovy.json.JsonSlurper().parseText(jsonText)
        def simpleList = rawList.collect { item -> 
            [src: item.src.toString(), dest: item.dest.toString()]
        }
        return simpleList
    } catch (Exception e) {
        println "❌ JSON 解析 deployPath 失败:${e.message}"
        return null
    }
}

pipeline {
    agent any
    stages {
        stage('Parse') {
            steps {
                script {
                    def json = '[{"src":"/a","dest":"/b"}]'
                    def deployList = parseDeployPath(json)
                    echo "Deploy List: ${deployList}"
                }
            }
        }
    }
}

为什么可行?

使用 @NonCPS 修饰的方法,不会被 Jenkins 的 CPS 引擎转换,因此可以使用原生 Groovy 方法,但代价是:

  • 无法使用 DSL(如 sh, echo 等)
  • 方法内不可访问 Pipeline 变量(如 env, params

✅ 解决方案二:使用 readJSON 步骤(推荐)

安装插件Pipeline Utility Steps

pipeline {
    agent any
    stages {
        stage('Parse JSON safely') {
            steps {
                script {
                    writeFile file: 'deploy.json', text: '[{"src":"/a","dest":"/b"}]'
                    def deployList = readJSON file: 'deploy.json'
                    deployList.each {
                        echo "src: ${it.src}, dest: ${it.dest}"
                    }
                }
            }
        }
    }
}

优势:

  • readJSON 是 Jenkins 官方提供的 DSL 级方法
  • 完全支持 流水线序列化和恢复
  • 不需要使用 @NonCPS

✅ 解决方案三:完全在 script {} 中处理(适合小范围)

虽然 JsonSlurper 本身不是 CPS 安全的,但在某些场景下,如果你在 script {} 中直接使用它,而没有调用嵌套函数,也能正常工作。

script {
    def json = '[{"src":"/a","dest":"/b"}]'
    def list = new groovy.json.JsonSlurper().parseText(json)
    list.each {
        echo "src: ${it.src}, dest: ${it.dest}"
    }
}

⚠ 注意:这种方式不一定在所有 Jenkins 环境中都安全,视具体版本而定。


🔁 最佳实践推荐

场景 推荐方式
生产级流水线中解析 JSON readJSON
工具方法、辅助转换 @NonCPS
快速测试、数据调试 script { JsonSlurper }

❌ 避免的错误写法

不要这样写:

def deployList = new groovy.json.JsonSlurper().parseText(params.jsonData)

或嵌套在函数中调用:

def parseIt() {
    return new JsonSlurper().parseText('...')
}

✅ 改为 @NonCPS 修饰 或使用 readJSON


📌 总结

Jenkins Pipeline 中,Groovy 方法并非都能直接使用。受限于 Jenkins 的 CPS 系统,很多涉及 IO、线程、状态不可序列化的操作会报错。面对 JsonSlurper 报错,推荐采用:

  • 首选:使用 Jenkins DSL 提供的 readJSON + writeFile
  • 通用:将 JSON 操作封装为 @NonCPS 方法
  • 调试:仅在 script {} 中临时使用 JsonSlurper


网站公告

今日签到

点亮在社区的每一天
去签到