Jenkins流水线正则Matcher序列化错误及执行异常排查求助
Jenkins 流水线问题分析
问题背景
流水线代码片段
echo "lockfile = ${lockfile}" def cnt = params.COUNTER as Integer // 获取调用任务的构建编号(通过 params.UPSTREAM_URL 传递) def tmp = params.UPSTREAM_URL // 匹配斜杠包裹的数字 def pattern = /\/(\d+)\// def matcher = (tmp =~ pattern) matcher.find() def parentJob = matcher.group(1) try { echo "A" echo "B" lockfile = lockfile + '.' + parentJob echo "C, cnt=${cnt}, lf=${lockfile}" sleep 10 * cnt echo "D" timeout(time: citaConfig.timeoutMinutes, unit: 'MINUTES') { while (checkIfExists(lockfile + '.*')) { while (checkIfExists(lockfile + '.*')) { sleep 30 // 等待锁文件被删除 } sleep 10 * cnt // 额外等待避免重叠 } } if (checkIfExists(lockfile)) { error("等待锁文件 \"${lockfile}\" 释放超时") } sh "echo '${BUILD_URL}' > ${lockfile}.${BUILD_NUMBER} " } catch(error) { echo "C5 - caught ${error}" echo error.getMessage() }
执行日志
[Pipeline] echo lockfile = /foo/goo.txt [Pipeline] echo A [Pipeline] echo B [Pipeline] echo C, cnt=1, lf=/foo/goo.txt.144 [Pipeline] sleep Sleeping for 10 sec [Pipeline] echo C5 - caught java.io.NotSerializableException: java.util.regex.Matcher [Pipeline] echo java.util.regex.Matcher [Pipeline] }
问题解答
1. 正则Matcher的问题
Jenkins流水线基于Groovy实现,运行过程中会对上下文变量进行序列化(用于节点切换、流水线重启恢复等场景)。java.util.regex.Matcher是不可序列化的对象,而代码中把matcher定义在try块外部,作为当前作用域的变量保留,当流水线执行到需要序列化的步骤时,就会触发NotSerializableException异常。
2. 为什么echo C后进入sleep却未执行echo D,如何定位
- 原因:
sleep是流水线的暂停类步骤,执行时Jenkins会尝试序列化当前上下文以保存状态,此时发现matcher这个不可序列化对象,直接抛出异常并进入catch块,导致sleep之后的echo D及后续代码都没有机会执行。 - 定位方法:
- 从异常信息直接关联:日志明确指向
java.util.regex.Matcher,结合Jenkins序列化机制,可直接锁定matcher变量的序列化问题; - 观察执行流程:日志显示
sleep步骤启动后直接跳转到catch块,说明异常发生在sleep触发的序列化阶段,而非sleep执行完成后; - 代码验证:可临时在获取
parentJob后将matcher设为null,或把正则匹配逻辑放到@NonCPS注解的函数中(@NonCPS标记的代码不会被Jenkins序列化),验证异常是否消失。
- 从异常信息直接关联:日志明确指向
修复示例
把正则匹配逻辑封装到@NonCPS函数中,避免保留不可序列化的Matcher对象:
@NonCPS def extractParentJobNumber(String upstreamUrl) { def pattern = /\/(\d+)\// def matcher = (upstreamUrl =~ pattern) if (matcher.find()) { return matcher.group(1) } throw new IllegalArgumentException("无效的上游URL: ${upstreamUrl}") } // 替换原代码中的正则匹配部分: def parentJob = extractParentJobNumber(params.UPSTREAM_URL)
内容的提问来源于stack exchange,提问作者MBaas
相关产品推荐
相关产品推荐

