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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 01:02:10