Jenkins Pipeline 中使用 JsonSlurper 報錯:cannot find current thread
- 🌟 背景
- ? 問題重現
- 🧠 原因解析:CPS 與非 CPS 安全方法沖突
- ? 解決方案一:使用 @NonCPS 注解(經典方案)
- ? 解決方案二:使用 `readJSON` 步驟(推薦)
- ? 解決方案三:完全在 `script {}` 中處理(適合小范圍)
- 🔁 最佳實踐推薦
- ? 避免的錯誤寫法
- 📌 總結
🌟 背景
在 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 anystages {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 anystages {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 anystages {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