diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index b0a8a1936cb5b9eca5c8227e89c3938f8e3ce869..6b1448feff8309db0c7c60173f2f640a5f4a7fdd 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -162,7 +162,7 @@ public abstract class AbstractProject

,R extends A * {@link Run#getPreviousBuild()} */ @Restricted(NoExternalUse.class) - protected transient RunMap builds = new RunMap(); + protected transient RunMap builds; /** * The quiet period. Null to delegate to the system default. @@ -271,6 +271,12 @@ public abstract class AbstractProject

,R extends A super.onCreatedFromScratch(); // solicit initial contributions, especially from TransientProjectActionFactory updateTransientActions(); + assert builds==null; + builds = new RunMap(getBuildDir(), new Constructor() { + public R create(File dir) throws IOException { + return loadBuild(dir); + } + }); } @Override diff --git a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java index 21290307684d5352b04f460b0b96a1c3ad14638d..eec9532eaeb5ca3ba1ae51862830cc04c00bb860 100644 --- a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java +++ b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java @@ -203,8 +203,8 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i private void loadIdOnDisk() { String[] buildDirs = dir.list(createDirectoryFilter()); if (buildDirs==null) { + // the job may just have been created buildDirs=EMPTY_STRING_ARRAY; - LOGGER.log(Level.WARNING, "failed to load list of builds from {0}", dir); } // wrap into ArrayList to enable mutation Arrays.sort(buildDirs); @@ -625,6 +625,7 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i protected R load(String id, Index editInPlace) { + assert dir != null; R v = load(new File(dir, id), editInPlace); if (v==null && editInPlace!=null) { // remember the failure. diff --git a/test/src/test/java/hudson/model/AbstractProjectTest.java b/test/src/test/java/hudson/model/AbstractProjectTest.java index e44dc949a9538f9bd4ed26340c74e6dc0a384fae..c9333779b0e6c32b99972471d8338bcd7468557a 100644 --- a/test/src/test/java/hudson/model/AbstractProjectTest.java +++ b/test/src/test/java/hudson/model/AbstractProjectTest.java @@ -47,6 +47,9 @@ import org.jvnet.hudson.test.recipes.PresetData.DataSet; import java.io.File; import java.util.concurrent.Future; import org.apache.commons.io.FileUtils; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; /** * @author Kohsuke Kawaguchi @@ -280,4 +283,47 @@ public class AbstractProjectTest extends HudsonTestCase { assertSymlinkForBuild(lastSuccessful, 1); assertSymlinkForBuild(lastStable, 1); } + + // Force garbage collection of ref's referent. Taken from NetBeans code. + public static void forceGc(final Reference ref) { + ArrayList alloc = new ArrayList(); + int size = 100000; + System.out.print("forcing garbage collection..."); + for (int i = 0; i < 50; i++) { + if (ref.get() == null) { + System.out.println("succeeded"); + return; + } + try { + System.gc(); + System.runFinalization(); + } catch (OutOfMemoryError error) { + // OK + } + try { + alloc.add(new byte[size]); + size = (int)(((double)size) * 1.3); + } catch (OutOfMemoryError error) { + size = size / 2; + } + try { + if (i % 3 == 0) { + Thread.sleep(321); + } + } catch (InterruptedException t) { + // ignore + } + } + System.out.println("failed"); + } + + @Bug(15156) + public void testGetBuildAfterGc() throws Exception { + FreeStyleProject job = createFreeStyleProject(); + job.scheduleBuild2(0, new Cause.UserCause()).get(); + // If this fails to garbage-collect, the test run will not have any value, but it will + // at least not fail. + forceGc(new WeakReference(job.getLastBuild())); + assertTrue(job.getLastBuild() != null); + } }