From b3e2ebd59e7d12c37295f1b8002116b2d7f10aaa Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 13 Feb 2025 15:30:14 -0500 Subject: [PATCH 1/5] Placeholder for batch diagnostic tool for `StackOverflowError`s --- pom.xml | 3 +- .../pickles/serialization/RiverReader.java | 2 +- .../RiverReaderStackOverflowErrorDiag.java | 40 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java diff --git a/pom.xml b/pom.xml index 0bccf1fa..9d14feb2 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,8 @@ org.jboss.marshalling jboss-marshalling-river - 2.2.2.Final + + 2.3.0.Final-SNAPSHOT org.jenkins-ci.plugins.workflow diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReader.java b/src/main/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReader.java index 2a4264fc..7307005f 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReader.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReader.java @@ -114,7 +114,7 @@ public RiverReader(File f, ClassLoader classLoader, FlowExecutionOwner owner) th this.owner = owner; } - private int parseHeader(DataInputStream din) throws IOException { + static int parseHeader(DataInputStream din) throws IOException { if (din.readLong() != RiverWriter.HEADER) { throw new IOException("Invalid stream header"); } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java b/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java new file mode 100644 index 00000000..8ef223ec --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * + * Copyright 2025 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jenkinsci.plugins.workflow.support.pickles.serialization; + +import java.nio.file.Path; +import org.junit.Test; + +// mvnd test-compile exec:java -Dexec.mainClass=org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverReaderStackOverflowErrorDiag -Dexec.classpathScope=test -Dexec.arguments=/path/to/program.dat +public final class RiverReaderStackOverflowErrorDiag { + + public static void main(String[] args) throws Exception { + var programDat = Path.of(args[0]); + // TODO probably need to specify a megawar also + // TODO parseHeader, readPickles, read main object + System.err.println("Will load " + programDat.toRealPath()); + } + +} From c7247663a14203a42188fc8371ca3485da3ca33c Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 20 Feb 2025 18:14:04 -0500 Subject: [PATCH 2/5] Attempting to load outside Jenkins is not going well: `InvalidClassException: WorkflowScript; Class does not extend stream superclass` --- .../RiverReaderStackOverflowErrorDiag.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java b/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java index 8ef223ec..e2a6cc4f 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java @@ -24,8 +24,16 @@ package org.jenkinsci.plugins.workflow.support.pickles.serialization; +import groovy.lang.GroovyShell; +import groovy.lang.Script; +import java.io.DataInputStream; +import java.nio.file.Files; import java.nio.file.Path; -import org.junit.Test; +import org.jboss.marshalling.Marshalling; +import org.jboss.marshalling.MarshallingConfiguration; +import org.jboss.marshalling.SimpleClassResolver; +import org.jboss.marshalling.river.RiverMarshallerFactory; +import static org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverReader.parseHeader; // mvnd test-compile exec:java -Dexec.mainClass=org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverReaderStackOverflowErrorDiag -Dexec.classpathScope=test -Dexec.arguments=/path/to/program.dat public final class RiverReaderStackOverflowErrorDiag { @@ -33,8 +41,19 @@ public final class RiverReaderStackOverflowErrorDiag { public static void main(String[] args) throws Exception { var programDat = Path.of(args[0]); // TODO probably need to specify a megawar also - // TODO parseHeader, readPickles, read main object System.err.println("Will load " + programDat.toRealPath()); + Script script = new GroovyShell().parse("public class WorkflowScript extends Script implements Serializable {def run() {}}"); + var classLoader = script.getClass().getClassLoader(); + try (var in = Files.newInputStream(programDat)) { + var din = new DataInputStream(in); + parseHeader(din); // ignore offset, not bothering to read pickles + var config = new MarshallingConfiguration(); + config.setClassResolver(new SimpleClassResolver(classLoader)); + var eu = new RiverMarshallerFactory().createUnmarshaller(config); + eu.start(Marshalling.createByteInput(din)); + var g = eu.readObject(); + System.err.println("Loaded " + g); + } } } From 43954995fb5f3f67f30dbf6fe0f87be1dd9af739 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 20 Feb 2025 18:57:19 -0500 Subject: [PATCH 3/5] More promising, but clearly needs a megawar. --- .../RiverReaderStackOverflowErrorDiag.java | 65 ++++++++++++------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java b/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java index e2a6cc4f..907cc64a 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java @@ -24,36 +24,51 @@ package org.jenkinsci.plugins.workflow.support.pickles.serialization; -import groovy.lang.GroovyShell; -import groovy.lang.Script; -import java.io.DataInputStream; import java.nio.file.Files; import java.nio.file.Path; -import org.jboss.marshalling.Marshalling; -import org.jboss.marshalling.MarshallingConfiguration; -import org.jboss.marshalling.SimpleClassResolver; -import org.jboss.marshalling.river.RiverMarshallerFactory; -import static org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverReader.parseHeader; +import org.apache.commons.io.FileUtils; +import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import static org.junit.Assume.assumeNotNull; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsSessionRule; -// mvnd test-compile exec:java -Dexec.mainClass=org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverReaderStackOverflowErrorDiag -Dexec.classpathScope=test -Dexec.arguments=/path/to/program.dat public final class RiverReaderStackOverflowErrorDiag { - public static void main(String[] args) throws Exception { - var programDat = Path.of(args[0]); - // TODO probably need to specify a megawar also - System.err.println("Will load " + programDat.toRealPath()); - Script script = new GroovyShell().parse("public class WorkflowScript extends Script implements Serializable {def run() {}}"); - var classLoader = script.getClass().getClassLoader(); - try (var in = Files.newInputStream(programDat)) { - var din = new DataInputStream(in); - parseHeader(din); // ignore offset, not bothering to read pickles - var config = new MarshallingConfiguration(); - config.setClassResolver(new SimpleClassResolver(classLoader)); - var eu = new RiverMarshallerFactory().createUnmarshaller(config); - eu.start(Marshalling.createByteInput(din)); - var g = eu.readObject(); - System.err.println("Loaded " + g); - } + private static Path input; + + @BeforeClass public static void args() { + var path = System.getenv("BUILD_DIR"); + assumeNotNull("Define $BUILD_DIR to run", path); + input = Path.of(path); + } + + @Rule public JenkinsSessionRule rr = new JenkinsSessionRule(); + + @Test public void run() throws Throwable { + var jobDir = rr.getHome().toPath().resolve("jobs/xxx"); + var buildDir = jobDir.resolve("builds/1"); + FileUtils.copyDirectory(input.toFile(), buildDir.toFile()); + Files.writeString(jobDir.resolve("config.xml"), ""); // minimal WorkflowJob + var buildXml = buildDir.resolve("build.xml"); + Files.writeString(buildXml, Files.readString(buildXml). + replace("true", "false"). + replace("true", "false"). + replaceFirst("1:[0-9]+", "")); + System.err.println("Loading " + input); + rr.then(r -> { + var build = r.jenkins.getItemByFullName("xxx", WorkflowJob.class).getBuildByNumber(1); + try { + ((CpsFlowExecution) build.getExecution()).programPromise.get(); + System.err.println("Loaded."); + } catch (Exception x) { + x.printStackTrace(); + build.writeWholeLogTo(System.err); + } + }); } } From bbd142a806c3eb4ce1e63ca203fa5c5390f4861a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 20 Feb 2025 19:07:15 -0500 Subject: [PATCH 4/5] Adding some plugins to the test classpath that may be used in `program.dat` --- pom.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pom.xml b/pom.xml index 9d14feb2..1ddfa524 100644 --- a/pom.xml +++ b/pom.xml @@ -173,5 +173,20 @@ pipeline-stage-step test + + org.csanchez.jenkins.plugins + kubernetes + test + + + org.jenkins-ci.plugins + pipeline-maven + test + + + org.jenkinsci.plugins + pipeline-model-definition + test + From a202fd2b5d1b27ecb98c7047cc0b2363500cd70a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 20 Feb 2025 19:23:31 -0500 Subject: [PATCH 5/5] Seem to have gotten it working :tada: --- pom.xml | 18 +++++++++++++++++- .../RiverReaderStackOverflowErrorDiag.java | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1ddfa524..8138024d 100644 --- a/pom.xml +++ b/pom.xml @@ -76,10 +76,16 @@ io.jenkins.tools.bom bom-${jenkins.baseline}.x - 3850.vb_c5319efa_e29 + 4023.va_eeb_b_4e45f07 import pom + + org.jenkins-ci.plugins.workflow + workflow-cps + + 999999-SNAPSHOT + @@ -188,5 +194,15 @@ pipeline-model-definition test + + io.jenkins.plugins + warnings-ng + test + + + org.jenkins-ci.plugins + parallel-test-executor + test + diff --git a/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java b/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java index 907cc64a..78a1f1cb 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverReaderStackOverflowErrorDiag.java @@ -58,8 +58,10 @@ public final class RiverReaderStackOverflowErrorDiag { replace("true", "false"). replace("true", "false"). replaceFirst("1:[0-9]+", "")); + Files.writeString(buildDir.resolve("log"), "\n"); System.err.println("Loading " + input); rr.then(r -> { + // TODO turn down logging on OldDataMonitor (we do not care about missing classes in build.xml) var build = r.jenkins.getItemByFullName("xxx", WorkflowJob.class).getBuildByNumber(1); try { ((CpsFlowExecution) build.getExecution()).programPromise.get();