From 5cec09d7edc74b40a58c6ded83e86e18241be3a0 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Sun, 24 Mar 2024 07:32:17 -0400 Subject: [PATCH] `RunListener.allowLoad` + `AbstractLazyLoadRunMap.recognizeNumber` (#9050) --- core/src/main/java/hudson/model/RunMap.java | 46 ++++++++++++++++-- core/src/main/java/hudson/model/ViewJob.java | 3 +- .../hudson/model/listeners/RunListener.java | 16 +++++++ .../model/lazy/AbstractLazyLoadRunMap.java | 48 +++++++++++++++++-- .../jenkins/model/lazy/LazyBuildMixIn.java | 6 +-- .../test/java/jenkins/model/lazy/FakeMap.java | 2 +- 6 files changed, 109 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/hudson/model/RunMap.java b/core/src/main/java/hudson/model/RunMap.java index b7bb97037940..508578afe441 100644 --- a/core/src/main/java/hudson/model/RunMap.java +++ b/core/src/main/java/hudson/model/RunMap.java @@ -28,7 +28,10 @@ import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.ASC; import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.DESC; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Util; +import hudson.model.listeners.RunListener; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; @@ -38,6 +41,7 @@ import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.SortedMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -64,6 +68,8 @@ public final class RunMap> extends AbstractLazyLoadRunMap */ private final SortedMap view = Collections.unmodifiableSortedMap(this); + private final @CheckForNull Job job; + private Constructor cons; /** Normally overwritten by {@link LazyBuildMixIn#onLoad} or {@link LazyBuildMixIn#onCreatedFromScratch}, in turn created during {@link Job#onLoad}. */ @@ -76,20 +82,38 @@ public final class RunMap> extends AbstractLazyLoadRunMap /** * @deprecated as of 1.485 - * Use {@link #RunMap(File, Constructor)}. + * Use {@link #RunMap(Job, Constructor)}. */ @Deprecated public RunMap() { - super(null); // will be set later + job = null; + initBaseDir(null); // will be set later + } + + @Restricted(NoExternalUse.class) + public RunMap(@NonNull Job job) { + this.job = Objects.requireNonNull(job); } /** * @param cons * Used to create new instance of {@link Run}. + * @since TODO */ + public RunMap(@NonNull Job job, Constructor cons) { + this.job = Objects.requireNonNull(job); + this.cons = cons; + initBaseDir(job.getBuildDir()); + } + + /** + * @deprecated Use {@link #RunMap(Job, Constructor)}. + */ + @Deprecated public RunMap(File baseDir, Constructor cons) { - super(baseDir); + job = null; this.cons = cons; + initBaseDir(baseDir); } public boolean remove(R run) { @@ -224,6 +248,22 @@ protected BuildReference createReference(R r) { return r.createReference(); } + @Override + protected boolean allowLoad(int buildNumber) { + if (job == null) { + LOGGER.fine(() -> "deprecated constructor without Job used on " + dir); + return true; + } + for (RunListener l : RunListener.all()) { + if (!l.allowLoad(job, buildNumber)) { + LOGGER.finer(() -> l + " declined to load " + buildNumber + " in " + job); + return false; + } + } + LOGGER.finest(() -> "no RunListener declined to load " + buildNumber + " in " + job + " so proceeding"); + return true; + } + @Override protected R retrieve(File d) throws IOException { if (new File(d, "build.xml").exists()) { diff --git a/core/src/main/java/hudson/model/ViewJob.java b/core/src/main/java/hudson/model/ViewJob.java index 5e8227450bde..0da32700c1bd 100644 --- a/core/src/main/java/hudson/model/ViewJob.java +++ b/core/src/main/java/hudson/model/ViewJob.java @@ -26,6 +26,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.Descriptor.FormException; +import java.io.File; import java.io.IOException; import java.util.LinkedHashSet; import java.util.Set; @@ -62,7 +63,7 @@ public abstract class ViewJob, RunT extends Run /** * All {@link Run}s. Copy-on-write semantics. */ - protected transient volatile /*almost final*/ RunMap runs = new RunMap<>(null, null); + protected transient volatile /*almost final*/ RunMap runs = new RunMap<>((File) null, null); private transient volatile boolean notLoaded = true; diff --git a/core/src/main/java/hudson/model/listeners/RunListener.java b/core/src/main/java/hudson/model/listeners/RunListener.java index 0ecbf725c792..648a319be5d4 100644 --- a/core/src/main/java/hudson/model/listeners/RunListener.java +++ b/core/src/main/java/hudson/model/listeners/RunListener.java @@ -35,6 +35,7 @@ import hudson.model.AbstractBuild; import hudson.model.BuildListener; import hudson.model.Environment; +import hudson.model.Job; import hudson.model.JobProperty; import hudson.model.Run; import hudson.model.Run.RunnerAbortedException; @@ -46,8 +47,11 @@ import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import jenkins.model.lazy.AbstractLazyLoadRunMap; import jenkins.util.Listeners; import org.jvnet.tiger_types.Types; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.Beta; /** * Receives notifications about builds. @@ -171,6 +175,18 @@ public Environment setUpEnvironment(AbstractBuild build, Launcher launcher, Buil */ public void onDeleted(R r) {} + /** + * Allows listeners to veto build loading. + * @param job the job from which a build might be loaded + * @param buildNumber the proposed build number + * @return false to veto build loading + * @see AbstractLazyLoadRunMap#recognizeNumber + */ + @Restricted(Beta.class) + public boolean allowLoad(@NonNull Job job, int buildNumber) { + return true; + } + /** * Registers this object as an active listener so that it can start getting * callbacks invoked. diff --git a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java index 95e647cfdfdc..ec2486bc3d3c 100644 --- a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java +++ b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java @@ -31,6 +31,7 @@ import hudson.model.Job; import hudson.model.Run; import hudson.model.RunMap; +import hudson.model.listeners.RunListener; import java.io.File; import java.io.IOException; import java.util.AbstractCollection; @@ -54,6 +55,7 @@ import java.util.regex.Pattern; import jenkins.util.MemoryReductionUtil; import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.Beta; import org.kohsuke.accmod.restrictions.NoExternalUse; /** @@ -290,8 +292,7 @@ private Index(Index rhs) { protected File dir; @Restricted(NoExternalUse.class) // subclassing other than by RunMap does not guarantee compatibility - protected AbstractLazyLoadRunMap(File dir) { - initBaseDir(dir); + protected AbstractLazyLoadRunMap() { } @Restricted(NoExternalUse.class) @@ -348,7 +349,12 @@ private void loadNumberOnDisk() { continue; } try { - list.add(Integer.parseInt(s)); + int buildNumber = Integer.parseInt(s); + if (allowLoad(buildNumber)) { + list.add(buildNumber); + } else { + LOGGER.fine(() -> "declining to consider " + buildNumber + " in " + dir); + } } catch (NumberFormatException e) { // matched BUILD_NUMBER but not an int? } @@ -357,6 +363,35 @@ private void loadNumberOnDisk() { numberOnDisk = list; } + @Restricted(NoExternalUse.class) + protected boolean allowLoad(int buildNumber) { + return true; + } + + /** + * Permits a previous blocked build number to be eligible for loading. + * @param buildNumber a build number + * @see RunListener#allowLoad + */ + @Restricted(Beta.class) + public final void recognizeNumber(int buildNumber) { + if (new File(dir, Integer.toString(buildNumber)).isDirectory()) { + synchronized (this) { + SortedIntList list = new SortedIntList(numberOnDisk); + if (list.contains(buildNumber)) { + LOGGER.fine(() -> "already knew about " + buildNumber + " in " + dir); + } else { + list.add(buildNumber); + list.sort(); + numberOnDisk = list; + LOGGER.fine(() -> "recognizing " + buildNumber + " in " + dir); + } + } + } else { + LOGGER.fine(() -> "no such subdirectory " + buildNumber + " in " + dir); + } + } + @Override public Comparator comparator() { return Collections.reverseOrder(); @@ -540,7 +575,12 @@ public R getByNumber(int n) { return v; } } - return load(n, null); + if (allowLoad(n)) { + return load(n, null); + } else { + LOGGER.fine(() -> "declining to load " + n + " in " + dir); + return null; + } } } diff --git a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java index 3b6309eef7b0..570048c5327f 100644 --- a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java +++ b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java @@ -66,8 +66,8 @@ public abstract class LazyBuildMixIn & Queue.Task & private static final Logger LOGGER = Logger.getLogger(LazyBuildMixIn.class.getName()); - @SuppressWarnings("deprecation") // [JENKINS-15156] builds accessed before onLoad or onCreatedFromScratch called - private @NonNull RunMap builds = new RunMap<>(); + // [JENKINS-15156] builds accessed before onLoad or onCreatedFromScratch called + private @NonNull RunMap builds = new RunMap<>(asJob()); /** * Initializes this mixin. @@ -142,7 +142,7 @@ public void onLoad(ItemGroup parent, String name) throws IOExcep } private RunMap createBuildRunMap() { - RunMap r = new RunMap<>(asJob().getBuildDir(), new RunMap.Constructor() { + RunMap r = new RunMap<>(asJob(), new RunMap.Constructor() { @Override public RunT create(File dir) throws IOException { return loadBuild(dir); diff --git a/core/src/test/java/jenkins/model/lazy/FakeMap.java b/core/src/test/java/jenkins/model/lazy/FakeMap.java index ae2198e39121..4dbe93dcc479 100644 --- a/core/src/test/java/jenkins/model/lazy/FakeMap.java +++ b/core/src/test/java/jenkins/model/lazy/FakeMap.java @@ -34,7 +34,7 @@ */ public class FakeMap extends AbstractLazyLoadRunMap { public FakeMap(File dir) { - super(dir); + initBaseDir(dir); } @Override