Groovy writeFile触发NotSerializableException: java.lang.ProcessImpl求助
Jenkins共享库序列化异常原因及解决办法
异常原因
Jenkins流水线依赖序列化/反序列化实现断点续跑与分布式执行,所有在流水线流程中留存的对象必须实现Serializable接口。你通过commandToRun.execute()获取的Process对象(底层由ProcessImpl实现)本身不可序列化,当代码执行到writeFile步骤时,Jenkins需要序列化当前流水线上下文,这个不可序列化的Process对象就会触发NotSerializableException。
解决办法
1. 提取结果后立即丢弃Process对象
不要让Process对象留在流水线上下文里,执行命令后立刻提取所需的stdout、stderr和状态码,转为字符串或基本类型,让Process对象被垃圾回收:
def runCommand(String commandToRun) { def process = commandToRun.execute() def stdout = process.in.text def stderr = process.err.text process.waitFor() def exitCode = process.exitValue() // 仅返回可序列化的结果,不返回Process对象 return [stdout: stdout, stderr: stderr, exitCode: exitCode] } // 调用示例 def cmdResult = runCommand(commandToRun) writeFile file: 'command_output.log', text: """ STDOUT: ${cmdResult.stdout} STDERR: ${cmdResult.stderr} EXIT CODE: ${cmdResult.exitCode} """
2. 规范使用@NonCPS注解
之前用@NonCPS出现警告,大概率是因为注解方法内调用了Jenkins流水线步骤(比如writeFile),或让不可序列化对象流出方法。正确用法是将Process相关逻辑完全封装在@NonCPS方法内,仅返回可序列化的结果:
@NonCPS def runCommandNonCPS(String commandToRun) { def process = commandToRun.execute() def stdout = process.in.text def stderr = process.err.text process.waitFor() def exitCode = process.exitValue() // 返回可序列化的Map对象 return [stdout: stdout, stderr: stderr, exitCode: exitCode] } // 流水线主逻辑中调用 def cmdResult = runCommandNonCPS(commandToRun) writeFile file: 'command_output.log', text: """ STDOUT: ${cmdResult.stdout} STDERR: ${cmdResult.stderr} EXIT CODE: ${cmdResult.exitCode} """
这样@NonCPS方法内的不可序列化对象不会进入流水线上下文,警告会自动消失。
3. 改用Jenkins原生sh步骤(推荐)
你之前尝试的sh步骤其实可以直接获取输出和状态码,无需手动调用Process。这种方法完全符合Jenkins序列化要求,是最稳妥的方案:
方式一:分离捕获stdout和状态码
def runCommandWithSh(String commandToRun) { def shResult = sh( script: commandToRun, returnStdout: true, returnStatus: true ) return [ stdout: shResult.stdout.trim(), exitCode: shResult.status ] }
方式二:同时捕获stdout和stderr
通过临时文件存储stderr:
def runCommandWithSh(String commandToRun) { def stderrTempFile = "stderr_${UUID.randomUUID()}.log" def shResult = sh( script: "${commandToRun} 2>${stderrTempFile}", returnStdout: true, returnStatus: true ) def stderr = readFile(stderrTempFile).trim() sh "rm -f ${stderrTempFile}" // 清理临时文件 return [ stdout: shResult.stdout.trim(), stderr: stderr, exitCode: shResult.status ] }
内容的提问来源于stack exchange,提问作者Julian52575
相关产品推荐
相关产品推荐

