package jenkins.model.lazy; import hudson.Extension; import hudson.ExtensionList; import hudson.ExtensionPoint; import jenkins.util.SystemProperties; import hudson.model.Run; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jenkins.model.lazy.LazyBuildMixIn.RunMixIn; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Reference (by default a {@link SoftReference}) to a build object. * * <p> * To be able to re-retrieve the referent in case it is lost, this class * remembers its ID (the job name is provided by the context because a {@link BuildReference} * belongs to one and only {@link AbstractLazyLoadRunMap}.) * * <p> * We use this ID for equality/hashCode so that we can have a collection of {@link BuildReference} * and find things in it. * * @author Kohsuke Kawaguchi * @since 1.485 (but as of 1.548 not a {@link SoftReference}) */ public final class BuildReference<R> { private static final Logger LOGGER = Logger.getLogger(BuildReference.class.getName()); final String id; private volatile Holder<R> holder; public BuildReference(String id, R referent) { this.id = id; this.holder = findHolder(referent); } /** * Gets the build if still in memory. * @return the actual build, or null if it has been collected * @see Holder#get */ public @CheckForNull R get() { Holder<R> h = holder; // capture return h!=null ? h.get() : null; } /** * Clear the reference to make a particular R object effectively unreachable. * * @see RunMixIn#dropLinks() */ /*package*/ void clear() { holder = null; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BuildReference<?> that = (BuildReference) o; return id.equals(that.id); } @Override public int hashCode() { return id.hashCode(); } @Override public String toString() { R r = get(); return r != null ? r.toString() : id; } /** * An abstraction of {@link Reference}. * @since 1.548 */ public interface Holder<R> { /** * Gets a build. * @return the build reference, or null if collected */ @CheckForNull R get(); } /** * Extensible factory for creating build references. * @since 1.548 */ public interface HolderFactory extends ExtensionPoint { /** * Constructs a single build reference. * @param <R> the type of thing (generally {@link Run}) * @param referent the thing to load * @return a reference, or null to consult the next factory */ @CheckForNull <R> Holder<R> make(@Nonnull R referent); } private static <R> Holder<R> findHolder(R referent) { if (referent == null) { // AbstractBuild.NONE return new DefaultHolderFactory.NoHolder<R>(); } for (HolderFactory f : ExtensionList.lookup(HolderFactory.class)) { Holder<R> h = f.make(referent); if (h != null) { LOGGER.log(Level.FINE, "created build reference for {0} using {1}", new Object[] {referent, f}); return h; } } return new DefaultHolderFactory().make(referent); } /** * Default factory if none other are installed. * Its behavior can be controlled via the system property {@link DefaultHolderFactory#MODE_PROPERTY}: * <dl> * <dt><code>soft</code> (default) * <dd>Use {@link SoftReference}s. Builds will be kept around so long as memory pressure is not too high. * <dt><code>weak</code> * <dd>Use {@link WeakReference}s. Builds will be kept only until the next full garbage collection cycle. * <dt><code>strong</code> * <dd>Use strong references. Builds will still be loaded lazily, but once loaded, will not be released. * <dt><code>none</code> * <dd>Do not hold onto builds at all. Mainly offered as an option for the purpose of reproducing lazy-loading bugs. * </dl> */ @Restricted(NoExternalUse.class) @Extension(ordinal=Double.NEGATIVE_INFINITY) public static final class DefaultHolderFactory implements HolderFactory { public static final String MODE_PROPERTY = "jenkins.model.lazy.BuildReference.MODE"; private static final String mode = SystemProperties.getString(MODE_PROPERTY); @Override public <R> Holder<R> make(R referent) { if (mode == null || mode.equals("soft")) { return new SoftHolder<R>(referent); } else if (mode.equals("weak")) { return new WeakHolder<R>(referent); } else if (mode.equals("strong")) { return new StrongHolder<R>(referent); } else if (mode.equals("none")) { return new NoHolder<R>(); } else { throw new IllegalStateException("unrecognized value of " + MODE_PROPERTY + ": " + mode); } } private static final class SoftHolder<R> extends SoftReference<R> implements Holder<R> { SoftHolder(R referent) { super(referent); } } private static final class WeakHolder<R> extends WeakReference<R> implements Holder<R> { WeakHolder(R referent) { super(referent); } } private static final class StrongHolder<R> implements Holder<R> { private final R referent; StrongHolder(R referent) { this.referent = referent; } @Override public R get() {return referent;} } private static final class NoHolder<R> implements Holder<R> { @Override public R get() {return null;} } } }