从 Artifactory 删除大文件夹时,Jenkins 出现意外异常 java.nio.channels.ClosedByInterruptException

标签 jenkins groovy jenkins-pipeline artifactory jenkins-groovy

在我的 Jenkins 共享库中,我创建了一个名为 ArtifactManager 的类,它在删除分支时从 Artifactory 执行 docker 清理。

当需要删除一个非常大的 docker 镜像目录(~50 GB)时,我会遇到意外中断:

java.nio.channels.ClosedByInterruptException
    at java.base/java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:199)
    at java.base/sun.nio.ch.FileChannelImpl.endBlocking(FileChannelImpl.java:162)
    at java.base/sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:285)
    at org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverWriter.<init>(RiverWriter.java:109)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.saveProgram(CpsThreadGroup.java:560)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.saveProgram(CpsThreadGroup.java:537)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.saveProgramIfPossible(CpsThreadGroup.java:520)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:444)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$400(CpsThreadGroup.java:97)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:315)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:279)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:67)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:139)
    at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
    at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)
Finished: FAILURE

我的代码未包含在堆栈跟踪中... 执行 HTTP 请求的代码是:

def deleteArtifact(String pathOnServer){
    def responseCode, data
    Boolean deleted = true
    Logger.printInfo(steps, "Deleting $server/$pathOnServer artifact from server")
    steps.withCredentials([steps.usernamePassword(credentialsId: 'artifactory_user', passwordVariable: 'PASSWORD', usernameVariable: 'USERNAME')]){
        (responseCode, data) = sendApiRequest("$server/$pathOnServer", "DELETE")
    }
    //if !2xxSuccessful() -> Http response codes family - 1xx: Informational, 2xx: Success, 3xx: Redirection, 4xx: Client Error, 5xx: Server Error
    if((responseCode / 100 as int) != 2){
        deleted = false
        Logger.printError(steps, "Failed to delete: `$pathOnServer` from `$server`. Status Code: $responseCode")
        Logger.printError(steps, "Data: $data")
    }
    return deleted
}

def sendApiRequest(String query, String httpMethod, String contentType ="", String data =""){
    def responseCode
    def responseData
    def conn = new URL("${this.protocol}://$query").openConnection()

    //Trying to increase timeout
    conn.setConnectTimeout(15 * 60 *60 * 1000);
    conn.setReadTimeout(15 * 60 *60 * 1000);

    def auth = "${steps.env.USERNAME}:${steps.env.PASSWORD}".getBytes().encodeBase64().toString()
    conn.setRequestProperty("Authorization", "Basic ${auth}")

    conn.setRequestMethod(httpMethod)
    if(contentType) conn.setRequestProperty( "Content-Type", contentType); 
    if(data){
        conn.setDoOutput(true)
        conn.getOutputStream().write(data.getBytes("UTF-8"));
        //Note: the POST will start when you try to read a value from the HttpURLConnection, such as responseCode, inputStream.text, or getHeaderField('...'). (https://stackoverflow.com/a/47489805/10025322)
    }
    responseCode = conn.getResponseCode()
    try{
        responseData = conn.getInputStream().getText()
    }catch(IOException e){
        responseData = e.getMessage()
    }
    conn = null
    return [responseCode, responseData]
}

我还尝试使用不同的库来执行请求,但当 Artifactory 响应缓慢时仍然出现异常(删除巨大的目录):

   def sendApiRequest(String query, String httpMethod, String contentType ="", String data =""){
        def responseCode
        def responseData
        def http = new HTTPBuilder("${this.protocol}://$query")
        http.request(Method.valueOf(httpMethod)) {
            headers.'Authorization' = "Basic ${steps.env.USERNAME}:${steps.env.PASSWORD}".getBytes().encodeBase64().toString()
            if (contentType) {
                headers.'Content-Type' = contentType
                requestContentType = contentType
            }
            if (data) {
                body = data
            }
            response.success = { resp, reader ->
                responseCode = resp.statusLine.statusCode
                responseData = reader.text
            }
            response.failure = { resp, reader ->
                responseCode = resp.statusLine.statusCode
                responseData = reader.text
            }
        }
        echo("RESPONSE_CODE: " + responseCode.toString() + " RESPONSE_DATA: " + responseData.toString())
        return [responseCode, responseData]
    }

我发现出现ClosedByInterruptException可能是由于以下原因:

  1. 运行代码的线程被中断。
  2. 由于网络错误,与远程服务器的连接已关闭。
  3. 服务器明确关闭了连接。
  4. 读/写操作超时。
  5. 底层 I/O 操作发生中断或失败。
  6. 操作进行时 JVM 被关闭。

知道如何处理/解决方法吗?

最佳答案

由于 Jenkins 核心中的错误,所提供的代码无法工作(此问题已在 jenkins >= 2.332.1LTS2.335 中标记为已修复)

作为解决方法,您可以使用:

def sendApiRequest(String query, String httpMethod, String contentType ="", String data =""){
    /*
        Caller must wrap function call with 'withCredentials' statement 
        with 'USERNAME' and 'PASSWORD' environment varaiables
    */
    String apiCmd = """curl -s -w '###%{http_code}' -X '${httpMethod}' \
                '${this.protocol}://$query' \
                ${contentType ? "-H 'Content-Type: $contentType'" : ''} \
                -u "\$USERNAME:\$PASSWORD" \
                ${data ? "-d '${data}'" : ''}"""


    def response = steps.sh(returnStdout: true, script: apiCmd, label: "Send API request to Artifactory").trim()
    
    def responseCode = response.split("###")[1]
    def responseData = response.split("###")[0]

    try{
        responseCode = responseCode.toInteger()
    } catch (NumberFormatException e) {
        throw new RuntimeException("Invalid status code: `$responseCode` cannot cast to integer")
    }

    return [responseCode, responseData]
}

关于从 Artifactory 删除大文件夹时,Jenkins 出现意外异常 java.nio.channels.ClosedByInterruptException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75353474/

相关文章:

jenkins - 根据参数值执行构建步骤

git - 如何在 Jenkins 执行 shell 中捕获错误?

grails - Groovy GSP Grails数字格式替换

jenkins - 如何告诉 Jenkins 保留临时持久的 sh 任务?

authentication - 如何在配置为使用 LDAP 作为用户数据库的 Jenkins 上添加外部用户?

php - Jenkins checkstyle.xml 不匹配任何内容 : 'build' exists but not 'build/logs/checkstyle. xml

linux - 将 linux 命令的值存储到 groovy 脚本(soapui)中的变量

gradle - 尝试遍历数组并将每个值传递给 gradle 任务

使用来自声明性管道的 jenkins 凭据进行 Git 推送

jenkins - 如何传递选择参数以调用 Jenkins 管道内的作业