diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java |
Publish
Diffstat (limited to 'jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java')
-rw-r--r-- | jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java b/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java new file mode 100644 index 00000000000..9862e574009 --- /dev/null +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java @@ -0,0 +1,205 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc; + +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.service.ClientProvider; +import com.yahoo.jdisc.service.ServerProvider; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * <p>This class provides a thread-safe implementation of the {@link SharedResource} interface, and should be used for + * all subclasses of {@link RequestHandler}, {@link ClientProvider} and {@link ServerProvider}. Once the reference count + * of this resource reaches zero, the {@link #destroy()} method is called.</p> + * + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + */ +public abstract class AbstractResource implements SharedResource { + + private static final Logger log = Logger.getLogger(AbstractResource.class.getName()); + + private final boolean debug = SharedResource.DEBUG; + private final AtomicInteger refCount; + private final Object monitor; + private final Set<Throwable> activeReferences; + private final ResourceReference initialCreationReference; + + protected AbstractResource() { + if (!debug) { + this.refCount = new AtomicInteger(1); + this.monitor = null; + this.activeReferences = null; + this.initialCreationReference = new NoDebugResourceReference(this); + } else { + this.refCount = null; + this.monitor = new Object(); + this.activeReferences = new HashSet<>(); + final Throwable referenceStack = new Throwable(); + this.activeReferences.add(referenceStack); + this.initialCreationReference = new DebugResourceReference(this, referenceStack); + } + } + + @Override + public final ResourceReference refer() { + if (!debug) { + addRef(1); + return new NoDebugResourceReference(this); + } + + final Throwable referenceStack = new Throwable(); + final String state; + synchronized (monitor) { + if (activeReferences.isEmpty()) { + throw new IllegalStateException("Object is already destroyed, no more new references may be created." + + " State={ " + currentStateDebugWithLock() + " }"); + } + activeReferences.add(referenceStack); + state = currentStateDebugWithLock(); + } + log.log(Level.WARNING, + getClass().getName() + "@" + System.identityHashCode(this) + ".refer(): state={ " + state + " }", + referenceStack); + return new DebugResourceReference(this, referenceStack); + } + + public void release() { + initialCreationReference.close(); + } + + private void removeReferenceStack(final Throwable referenceStack, final Throwable releaseStack) { + final boolean doDestroy; + final String state; + synchronized (monitor) { + final boolean wasThere = activeReferences.remove(referenceStack); + state = currentStateDebugWithLock(); + if (!wasThere) { + throw new IllegalStateException("Reference is already released and can only be released once." + + " reference=" + Arrays.toString(referenceStack.getStackTrace()) + + ". State={ " + state + "}"); + } + doDestroy = activeReferences.isEmpty(); + } + log.log(Level.WARNING, + getClass().getName() + "@" + System.identityHashCode(this) + " release: state={ " + state + " }", + releaseStack); + if (doDestroy) { + destroy(); + } + } + + /** + * <p>Returns the reference count of this resource. This typically has no value for other than single-threaded unit- + * tests, as it is merely a snapshot of the counter.</p> + * + * @return The current value of the reference counter. + */ + public final int retainCount() { + if (!debug) { + return refCount.get(); + } + + synchronized (monitor) { + return activeReferences.size(); + } + } + + /** + * <p>This method signals that this AbstractResource can dispose of any internal resources, and commence with shut + * down of any internal threads. This will be called once the reference count of this resource reaches zero.</p> + */ + protected void destroy() { + + } + + private int addRef(int value) { + while (true) { + int prev = refCount.get(); + if (prev == 0) { + throw new IllegalStateException(getClass().getName() + ".addRef(" + value + "):" + + " Object is already destroyed." + + " Consider toggling the " + SharedResource.SYSTEM_PROPERTY_NAME_DEBUG + + " system property to get debugging assistance with reference tracking."); + } + int next = prev + value; + if (refCount.compareAndSet(prev, next)) { + return next; + } + } + } + + /** + * Returns a string describing the current state of references in human-friendly terms. May be used for debugging. + */ + public String currentState() { + if (!debug) { + return "Active references: " + refCount.get() + "." + + " Resource reference debugging is turned off. Consider toggling the " + + SharedResource.SYSTEM_PROPERTY_NAME_DEBUG + + " system property to get debugging assistance with reference tracking."; + } + synchronized (monitor) { + return currentStateDebugWithLock(); + } + } + + private String currentStateDebugWithLock() { + return "Active references: " + makeListOfActiveReferences(); + } + + private String makeListOfActiveReferences() { + final StringBuilder builder = new StringBuilder(); + builder.append("["); + for (final Throwable activeReference : activeReferences) { + builder.append(" "); + builder.append(Arrays.toString(activeReference.getStackTrace())); + } + builder.append(" ]"); + return builder.toString(); + } + + private static class NoDebugResourceReference implements ResourceReference { + private final AbstractResource resource; + private final AtomicBoolean isReleased = new AtomicBoolean(false); + + public NoDebugResourceReference(final AbstractResource resource) { + this.resource = resource; + } + + @Override + public final void close() { + final boolean wasReleasedBefore = isReleased.getAndSet(true); + if (wasReleasedBefore) { + final String message = "Reference is already released and can only be released once." + + " State={ " + resource.currentState() + " }"; + throw new IllegalStateException(message); + } + int refCount = resource.addRef(-1); + if (refCount == 0) { + resource.destroy(); + } + } + } + + private static class DebugResourceReference implements ResourceReference { + private final AbstractResource resource; + private final Throwable referenceStack; + + public DebugResourceReference(final AbstractResource resource, final Throwable referenceStack) { + this.resource = resource; + this.referenceStack = referenceStack; + } + + @Override + public final void close() { + final Throwable releaseStack = new Throwable(); + resource.removeReferenceStack(referenceStack, releaseStack); + } + } +} |