12 votes

Le pipeline Jenkins envoie "StackOverflowError : Fermetures/fonctions excessivement imbriquées"

J'ai les éléments suivants 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"
    }
}

Il lance l'exception suivante lorsque j'essaie de l'exécuter :

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)

Les variables sont les suivantes

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

Je ne comprends pas où se situe l'appel à la récursivité car getProjectPath ne fait qu'un appel fractionné standard.

J'ai essayé de le remplacer par .tokenize() mais il échoue toujours.

Il convient de noter que plusieurs de ces programmes peuvent être exécutés en même temps, mais que l'erreur se produit même s'ils sont exécutés séparément.

Pourriez-vous m'aider à comprendre pourquoi cette StackOverflowError se produit ?

20voto

Szymon Stepniak Points 17693

Appel getProjectPath() provoque cette exception. Cela se produit parce que si Groovy trouve une méthode getter pour un champ foo comme getFoo() il se rabat sur l'exécution de cette méthode à chaque fois qu'il voit un accès à foo valeur.

Qu'est-ce que cela signifie dans votre cas ? Lorsque vous appelez la méthode

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

il s'agit d'une récursion infinie car cette méthode est considérée comme :

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

afin qu'il n'atteigne jamais .split("/")[-1] - C'est pourquoi, en le remplaçant par tokenize() La méthode n'a rien changé.

Solution : renommer getProjectPath() ou projectPath nom de la variable.

Propriétés des classes Groovy

Une propriété est une caractéristique d'une classe visible de l'extérieur. Plutôt que d'utiliser simplement un champ public pour représenter ces caractéristiques (ce qui fournit une abstraction plus limitée et restreindrait les possibilités de refonte), la convention typique en Java est de suivre les conventions JavaBean, c'est-à-dire de représenter la propriété en utilisant une combinaison d'un champ de soutien privé et de getters/setters.

Source : [http://groovy-lang.org/objectorientation.html#properties](http://groovy-lang.org/objectorientation.html#properties)_

Cette partie de la documentation de Groovy explique ce comportement. Elle peut être simplifiée à l'aide d'un exemple - une classe comme :

class Person {
    String name
}

est compilé de la manière suivante :

class Person {
    private String name

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

    String getName() {
        return this.name
    }
}

La règle générale à suivre lorsque l'on travaille avec Groovy est que lorsque l'on spécifie un champ foo vous mettez en œuvre getFoo() soigneusement (si vous devez le faire). En particulier, vous évitez d'accéder au champ foo à l'intérieur de cette méthode, car elle se heurte à ce problème d'appel récursif infini.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X