首页 > 解决方案 > Jenkins pipeline throws "StackOverflowError: Excessively nested closures/functions"

问题描述

I have following Jenkinsfile:

#!groovy

def projectPath = "${projectPath}"
def specPath = "${specPath}"
int numberOfRetries = "${NUM_OF_RETRIES}".toInteger()

def failure = true
def retryAmount = 0
def start = System.currentTimeMillis()

def getSpecName() {
    specPath.split("/")[-1].split(".")[0]
}

def getProjectPath() {
    projectPath.split("/")[-1]
}

def rmDocker() {
    def remove = sh script: "docker rm -f cypress_${getSpecName()}", returnStatus: true
}

stage("Cypress Setup") {
    node("Cypress") {
        rmDocker()
    }
}

stage("Cypress Run") {
    node("Cypress") {
        currentBuild.setDisplayName("${projectPath} - ${getSpecName()}")
        while (failure && retryAmount < numberOfRetries) {
            sh "docker pull dockreg.bluestembrands.com/cypresswithtests:latest"
            if (getSpecName().toLowerCase().contains("auth")) {
                exit_code = sh script:"docker run --name cypress_${getSpecName()} dockreg.bluestembrands.com/cypresswithtests:latest sh -c \"node SQLSite/request.js & cypress run -P ${projectPath} --spec ${specPath} --env RUN=${retryAmount} --config videoCompression=${videoCompression} --reporter /usr/local/lib/node_modules/mochawesome-cypress-bsb --reporter-options \"reportDir=mochawesome-reports/run${retryAmount}/, reportName=mochawesome\"\"", returnStatus: true    
            } else {
                exit_code = sh script:"docker run --name cypress_${getSpecName()} dockreg.bluestembrands.com/cypresswithtests:latest sh -c \"cypress run -P ${projectPath} --spec ${specPath} --env RUN=${retryAmount} --config videoCompression=${videoCompression} --reporter /usr/local/lib/node_modules/mochawesome-cypress-bsb --reporter-options \"reportDir=mochawesome-reports/run${retryAmount}/, reportName=mochawesome\"\"", returnStatus: true  
            }
            failure = exit_code != 0
            try {
                println "/var/docker-mounts/nfs/qa/test-results/${getProjectPath()}-${getSpecName()}/"
                dir("/var/docker-mounts/nfs/qa/test-results/${getProjectPath()}-${getSpecName()}/") {
                    sh "docker cp cypress_${getSpecName()}:/cypress/${projectPath}/mochawesome-reports /var/docker-mounts/nfs/qa/test-results/${getProjectPath()}-${getSpecName()}/${BUILD_ID}"
                }
            } catch (Exception e) {
                println e
                echo "Failed to copy Mochawesome tests"
            }
            rmDocker()
            retryAmount++
        }
    }

    if (failure) {
        currentBuild.result = "FAILURE"
    }
}

It throws following exception when I try to run it:

java.lang.StackOverflowError: Excessively nested closures/functions at WorkflowScript.getProjectPath(WorkflowScript:16) - look for unbounded recursion - call depth: 1025
    at com.cloudbees.groovy.cps.impl.CpsFunction.invoke(CpsFunction.java:28)
    at com.cloudbees.groovy.cps.impl.CpsCallableInvocation.invoke(CpsCallableInvocation.java:40)
    at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:62)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:109)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixName(FunctionCallBlock.java:77)
    at sun.reflect.GeneratedMethodAccessor345.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
    at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
    at com.cloudbees.groovy.cps.Next.step(Next.java:83)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:174)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:163)
    at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:122)
    at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:261)
    at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:163)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$101(SandboxContinuable.java:34)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.lambda$run0$0(SandboxContinuable.java:59)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:108)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:58)
    at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:174)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:332)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$200(CpsThreadGroup.java:83)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:244)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:232)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:64)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:131)
    at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
    at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:59)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

The variables are:

NUM_OF_RETRIES: 3

specPath: bsb-haband-web/hab-shop/cypress/integration/Search/SearchNoResultsSpec.js

VideoCompression: false

projectPath: bsb-haband-web/hab-shop

BRANCH_NAME: master

I don't understand where the recursion call is happening as getProjectPath just does a standard split call.

I have tried changing it to .tokenize(), but it still fails.

It might be of note that more than one of these can run at the same times, however the error occurs even if run in isolation.

Could you please help me understanding why this StackOverflowError happens?

标签: jenkinsgroovyjenkins-pipeline

解决方案


调用getProjectPath()方法会导致此异常。发生这种情况是因为如果 Groovy 为某个字段找到了一个 getter 方法,它foogetFoo()在它看到访问foo值的任何时候回退以执行该方法。

在你的情况下是什么意思?当你调用方法时

def getProjectPath() {
    projectPath.split("/")[-1]
}

它会陷入无限递归,因为这种方法被视为:

def getProjectPath() {
    getProjectPath().split("/")[-1]
}

所以它永远不会达到.split("/")[-1]- 这就是为什么用tokenize()方法替换它并没有改变任何事情。

解决方法:重命名getProjectPath()方法或projectPath变量名。

Groovy 类属性

属性是类的外部可见特征。Java 中的典型约定是遵循 JavaBean 约定,即使用私有支持字段和 getter 的组合来表示属性,而不是仅仅使用公共字段来表示这些特性(这提供了更有限的抽象并会限制重构的可能性) /二传手。

来源: http: //groovy-lang.org/objectorientation.html#properties

Groovy 文档的这一部分解释了这种行为。它可以简化为一个示例 - 一个类,如:

class Person {
    String name
}

被编译成这样的东西:

class Person {
    private String name

    void setName(String name) {
        this.name = name
    }

    String getName() {
        return this.name
    }
}

使用 Groovy 时的一般经验法则是,当您指定一个字段时,foo您会仔细实现getFoo()(如果您确实必须这样做)。特别是您避免访问foo此方法中的字段,因为它遇到了这个无限递归调用问题。


推荐阅读