aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenning Baldersheim <balder@yahoo-inc.com>2021-10-04 17:20:40 +0200
committerHenning Baldersheim <balder@yahoo-inc.com>2021-10-05 14:49:20 +0200
commitc9982ddc5ce3083101541a301cca20b19b4914e8 (patch)
treef63fa1165e4b8572a7dd7c24ed056e851b97660d
parent5f5cb7aa87195c92b374dcbc742a5263559c8bb0 (diff)
Add option to attach a context to refer.
Introduce a lightweight debug mode.
-rw-r--r--container-core/src/test/java/com/yahoo/jdisc/http/HttpRequestTestCase.java1
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java175
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/Request.java2
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/SharedResource.java31
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/core/ActiveContainer.java4
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/core/ApplicationLoader.java4
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerSnapshot.java4
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/refcount/CloseableOnce.java27
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesByContextMap.java80
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java114
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DestructableResource.java11
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/refcount/References.java22
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/refcount/ReferencesByCount.java83
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/refcount/package-info.java4
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/service/CurrentContainer.java10
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerSnapshotTestCase.java2
16 files changed, 413 insertions, 161 deletions
diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/HttpRequestTestCase.java b/container-core/src/test/java/com/yahoo/jdisc/http/HttpRequestTestCase.java
index 43fc67cfabe..8cd19339d34 100644
--- a/container-core/src/test/java/com/yahoo/jdisc/http/HttpRequestTestCase.java
+++ b/container-core/src/test/java/com/yahoo/jdisc/http/HttpRequestTestCase.java
@@ -202,6 +202,7 @@ public class HttpRequestTestCase {
private static CurrentContainer mockContainer() {
final CurrentContainer currentContainer = mock(CurrentContainer.class);
when(currentContainer.newReference(any(URI.class))).thenReturn(mock(Container.class));
+ when(currentContainer.newReference(any(URI.class), any(Object.class))).thenReturn(mock(Container.class));
return currentContainer;
}
}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java b/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java
index 05c3582a541..a5b5dad2583 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java
@@ -2,16 +2,13 @@
package com.yahoo.jdisc;
import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.refcount.DebugReferencesByContextMap;
+import com.yahoo.jdisc.refcount.DebugReferencesWithStack;
+import com.yahoo.jdisc.refcount.DestructableResource;
+import com.yahoo.jdisc.refcount.ReferencesByCount;
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;
+import com.yahoo.jdisc.refcount.References;
/**
* This class provides a thread-safe implementation of the {@link SharedResource} interface, and should be used for
@@ -22,75 +19,33 @@ import java.util.logging.Logger;
*/
public abstract class AbstractResource implements SharedResource {
- private static final Logger log = Logger.getLogger(AbstractResource.class.getName());
+ private static final Debug debug = DEBUG;
- private final boolean debug = SharedResource.DEBUG;
- private final AtomicInteger refCount;
- private final Object monitor;
- private final Set<Throwable> activeReferences;
- private final ResourceReference initialCreationReference;
+ private final References references;
protected AbstractResource() {
- if (!debug) {
- this.refCount = new AtomicInteger(1);
- this.monitor = null;
- this.activeReferences = null;
- this.initialCreationReference = new NoDebugResourceReference(this);
+ DestructableResource destructable = new WrappedResource(this);
+ if (debug == Debug.SIMPLE) {
+ references = new DebugReferencesByContextMap(destructable, this);
+ } else if (debug == Debug.STACK) {
+ references = new DebugReferencesWithStack(destructable);
} 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);
+ references = new ReferencesByCount(destructable);
}
}
@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.FINE, referenceStack, () ->
- getClass().getName() + "@" + System.identityHashCode(this) + ".refer(): state={ " + state + " }");
- return new DebugResourceReference(this, referenceStack);
+ return refer(null);
}
-
@Override
- public final void release() {
- initialCreationReference.close();
+ public final ResourceReference refer(Object context) {
+ return references.refer(context);
}
- 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.FINE, releaseStack,
- () ->getClass().getName() + "@" + System.identityHashCode(this) + " release: state={ " + state + " }");
- if (doDestroy) {
- destroy();
- }
+ @Override
+ public final void release() {
+ references.release();
}
/**
@@ -100,105 +55,25 @@ public abstract class AbstractResource implements SharedResource {
* @return The current value of the reference counter.
*/
public final int retainCount() {
- if (!debug) {
- return refCount.get();
- }
-
- synchronized (monitor) {
- return activeReferences.size();
- }
+ return references.referenceCount();
}
/**
* <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;
- }
- }
- }
+ protected void destroy() { }
/**
* 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();
- }
- }
+ return references.currentState();
}
- private static class DebugResourceReference implements ResourceReference {
+ static private class WrappedResource implements DestructableResource {
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);
- }
+ WrappedResource(AbstractResource resource) { this.resource = resource; }
+ @Override public void close() { resource.destroy(); }
}
}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/Request.java b/jdisc_core/src/main/java/com/yahoo/jdisc/Request.java
index e2cde8e806c..eeb787e59dd 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/Request.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/Request.java
@@ -87,7 +87,7 @@ public class Request extends AbstractResource {
parentReference = null;
serverRequest = isServerRequest;
setUri(uri);
- container = current.newReference(uri);
+ container = current.newReference(uri, this);
creationTime = container.currentTimeMillis();
}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/SharedResource.java b/jdisc_core/src/main/java/com/yahoo/jdisc/SharedResource.java
index 20656bf7d1d..dee0f8ee410 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/SharedResource.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/SharedResource.java
@@ -28,10 +28,22 @@ import com.yahoo.jdisc.service.ServerProvider;
public interface SharedResource {
String SYSTEM_PROPERTY_NAME_DEBUG = "jdisc.debug.resources";
- boolean DEBUG = Boolean.valueOf(System.getProperty(SYSTEM_PROPERTY_NAME_DEBUG));
+ enum Debug {NO, SIMPLE, STACK}
+ Debug DEBUG = valueOfDebug();
+ private static Debug valueOfDebug() {
+ String val = System.getProperty(SYSTEM_PROPERTY_NAME_DEBUG);
+ if (val != null) {
+ val = val.toUpperCase();
+ if (Boolean.valueOf(val)) return Debug.SIMPLE;
+ try {
+ return Debug.valueOf(val);
+ } catch (IllegalArgumentException e) { }
+ }
+ return Debug.NO;
+ }
/**
- * <p>Increments the reference count of this resource. You call this method to prevent an object from being
+ * <p>Creates a reference to this resource. You call this method to prevent an object from being
* destroyed until you have finished using it.</p>
*
* <p>You MUST keep the returned {@link ResourceReference} object and release the reference by calling
@@ -43,6 +55,21 @@ public interface SharedResource {
ResourceReference refer();
/**
+ * <p>Creates a reference to this resource. You call this method to prevent an object from being
+ * destroyed until you have finished using it. You can attach a context that will live as long as the reference.</p>
+ *
+ * @param context A context to be associated with the reference. It should give some clue as to who referenced it.
+ * <p>You MUST keep the returned {@link ResourceReference} object and release the reference by calling
+ * {@link ResourceReference#close()} on it. A reference created by this method can NOT be released by calling
+ * {@link #release()}.</p>
+ *
+ * @see ResourceReference#close()
+ */
+ default ResourceReference refer(Object context) {
+ return refer();
+ }
+
+ /**
* <p>Releases the "main" reference to this resource (the implicit reference due to creation of the object).</p>
*
* <p>References obtained by calling {@link #refer()} must be released by calling {@link ResourceReference#close()}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ActiveContainer.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ActiveContainer.java
index a9fd2c747ff..1e76f34939f 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ActiveContainer.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ActiveContainer.java
@@ -106,7 +106,7 @@ public class ActiveContainer extends AbstractResource implements CurrentContaine
}
@Override
- public ContainerSnapshot newReference(URI uri) {
+ public ContainerSnapshot newReference(URI uri, Object context) {
String name = bindingSetSelector.select(uri);
if (name == null) {
throw new NoBindingSetSelectedException(uri);
@@ -116,7 +116,7 @@ public class ActiveContainer extends AbstractResource implements CurrentContaine
if (serverBindings == null || clientBindings == null) {
throw new BindingSetNotFoundException(name);
}
- return new ContainerSnapshot(this, serverBindings, clientBindings);
+ return new ContainerSnapshot(this, serverBindings, clientBindings, context);
}
}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ApplicationLoader.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ApplicationLoader.java
index f28731a47c7..34fcd2be9ba 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ApplicationLoader.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ApplicationLoader.java
@@ -81,12 +81,12 @@ public class ApplicationLoader implements BootstrapLoader, ContainerActivator, C
}
@Override
- public ContainerSnapshot newReference(URI uri) {
+ public ContainerSnapshot newReference(URI uri, Object context) {
ActiveContainer container = containerRef.get();
if (container == null) {
throw new ContainerNotReadyException();
}
- return container.newReference(uri);
+ return container.newReference(uri, context);
}
@Override
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerSnapshot.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerSnapshot.java
index 7b72e95ac09..becfa94e71f 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerSnapshot.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerSnapshot.java
@@ -28,13 +28,13 @@ class ContainerSnapshot extends AbstractResource implements Container {
private final BindingSet<RequestHandler> clientBindings;
ContainerSnapshot(ActiveContainer container, BindingSet<RequestHandler> serverBindings,
- BindingSet<RequestHandler> clientBindings)
+ BindingSet<RequestHandler> clientBindings, Object context)
{
this.timeoutMgr = container.timeoutManager();
this.container = container;
this.serverBindings = serverBindings;
this.clientBindings = clientBindings;
- this.containerReference = container.refer();
+ this.containerReference = container.refer(context);
}
@Override
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/CloseableOnce.java b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/CloseableOnce.java
new file mode 100644
index 00000000000..f5b41c5ee9f
--- /dev/null
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/CloseableOnce.java
@@ -0,0 +1,27 @@
+package com.yahoo.jdisc.refcount;
+
+import com.yahoo.jdisc.ResourceReference;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Ensures that a ResourceReference can only be closed exactly once.
+ *
+ * @author baldersheim
+ */
+abstract class CloseableOnce implements ResourceReference {
+ private final AtomicBoolean isReleased = new AtomicBoolean(false);
+
+ @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={ " + getReferences().currentState() + " }";
+ throw new IllegalStateException(message);
+ }
+ onClose();
+ }
+ abstract void onClose();
+ abstract References getReferences();
+}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesByContextMap.java b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesByContextMap.java
new file mode 100644
index 00000000000..e18980967c8
--- /dev/null
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesByContextMap.java
@@ -0,0 +1,80 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.refcount;
+
+import com.yahoo.jdisc.ResourceReference;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Does reference counting by putting a unique key together with optional context in map
+ * Used if system property jdisc.debug.resources=simple/true
+ *
+ * @author baldersheim
+ */
+public class DebugReferencesByContextMap implements References {
+ private final Map<Object, Object> contextMap = new HashMap<>();
+ private final DestructableResource resource;
+ private final Reference initialReference;
+ private long contextId = 1;
+
+ public DebugReferencesByContextMap(DestructableResource resource, Object context) {
+ this.resource = resource;
+ Long key = 0L;
+ initialReference = new Reference(this, key);
+ contextMap.put(key, context);
+ }
+
+ @Override
+ public void release() {
+ initialReference.close();
+ }
+
+ @Override
+ public int referenceCount() {
+ synchronized (contextMap) { return contextMap.size(); }
+ }
+
+ @Override
+ public ResourceReference refer(Object context) {
+ synchronized (contextMap) {
+ if (contextMap.isEmpty()) {
+ throw new IllegalStateException("Object is already destroyed, no more new references may be created."
+ + " State={ " + currentState() + " }");
+ }
+ Long key = contextId++;
+ contextMap.put(key, context != null ? context : key);
+ return new Reference(this, key);
+ }
+ }
+
+ private void removeRef(Long key) {
+ synchronized (contextMap) {
+ contextMap.remove(key);
+ if (contextMap.isEmpty()) {
+ resource.close();
+ }
+ }
+ }
+
+ @Override
+ public String currentState() {
+ synchronized (contextMap) {
+ return contextMap.toString();
+ }
+ }
+
+ private static class Reference extends CloseableOnce {
+ private final DebugReferencesByContextMap references;
+ private final Long key;
+
+ Reference(DebugReferencesByContextMap references, Long key) {
+ this.references = references;
+ this.key = key;
+ }
+
+ @Override final void onClose() { references.removeRef(key); }
+ @Override
+ final References getReferences() { return references; }
+ }
+}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java
new file mode 100644
index 00000000000..db6c266534f
--- /dev/null
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java
@@ -0,0 +1,114 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.refcount;
+
+import com.yahoo.jdisc.ResourceReference;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Does reference counting by putting stacktraces in a map together with an optional context.
+ * Intended only for debugging as it is slow.
+ * Used if system property jdisc.debug.resources=stack
+ *
+ * @author baldersheim
+ */
+public class DebugReferencesWithStack implements References {
+ private static final Logger log = Logger.getLogger(DebugReferencesWithStack.class.getName());
+ private final Map<Throwable, Object> activeReferences = new HashMap<>();
+ private final DestructableResource resource;
+ private final DebugResourceReference initialreference;
+
+ public DebugReferencesWithStack(DestructableResource resource) {
+ final Throwable referenceStack = new Throwable();
+ this.activeReferences.put(referenceStack, this);
+ this.resource = resource;
+ initialreference = new DebugResourceReference(this, referenceStack);
+ }
+
+ @Override
+ public void release() {
+ initialreference.close();
+ }
+
+ @Override
+ public int referenceCount() {
+ synchronized (activeReferences) {
+ return activeReferences.size();
+ }
+ }
+
+ @Override
+ public ResourceReference refer(Object context) {
+ final Throwable referenceStack = new Throwable();
+ synchronized (activeReferences) {
+ if (activeReferences.isEmpty()) {
+ throw new IllegalStateException("Object is already destroyed, no more new references may be created."
+ + " State={ " + currentState() + " }");
+ }
+ activeReferences.put(referenceStack, context);
+ }
+ log.log(Level.FINE, referenceStack, () ->
+ getClass().getName() + "@" + System.identityHashCode(this) + ".refer(): state={ " + currentState() + " }");
+ return new DebugResourceReference(this, referenceStack);
+ }
+
+ private void removeReferenceStack(final Throwable referenceStack, final Throwable releaseStack) {
+ final boolean doDestroy;
+ synchronized (activeReferences) {
+ final boolean wasThere = activeReferences.containsKey(referenceStack);
+ activeReferences.remove(referenceStack);
+ if (!wasThere) {
+ throw new IllegalStateException("Reference is already released and can only be released once."
+ + " reference=" + Arrays.toString(referenceStack.getStackTrace())
+ + ". State={ " + currentState() + "}");
+ }
+ doDestroy = activeReferences.isEmpty();
+ log.log(Level.FINE, releaseStack,
+ () -> getClass().getName() + "@" + System.identityHashCode(this) + " release: state={ " + currentState() + " }");
+ }
+
+ if (doDestroy) {
+ resource.close();
+ }
+ }
+
+ @Override
+ public String currentState() {
+ return "Active references: " + makeListOfActiveReferences();
+ }
+
+ private String makeListOfActiveReferences() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("[");
+ synchronized (activeReferences) {
+ for (var activeReference : activeReferences.entrySet()) {
+ builder.append(" ");
+ builder.append(Arrays.toString(activeReference.getKey().getStackTrace()));
+ }
+ }
+ builder.append(" ]");
+ return builder.toString();
+ }
+
+ private static class DebugResourceReference extends CloseableOnce {
+ private final DebugReferencesWithStack resource;
+ private final Throwable referenceStack;
+
+ public DebugResourceReference(DebugReferencesWithStack resource, final Throwable referenceStack) {
+ this.resource = resource;
+ this.referenceStack = referenceStack;
+ }
+
+ @Override
+ final void onClose() {
+ final Throwable releaseStack = new Throwable();
+ resource.removeReferenceStack(referenceStack, releaseStack);
+ }
+ @Override
+ final References getReferences() { return resource; }
+ }
+}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DestructableResource.java b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DestructableResource.java
new file mode 100644
index 00000000000..b373473f214
--- /dev/null
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DestructableResource.java
@@ -0,0 +1,11 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.refcount;
+
+public interface DestructableResource extends AutoCloseable {
+
+ /**
+ * Wrapper to allow access to protected AbstractResource.destroy()
+ */
+ @Override
+ void close();
+}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/References.java b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/References.java
new file mode 100644
index 00000000000..35b4e7c759e
--- /dev/null
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/References.java
@@ -0,0 +1,22 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.refcount;
+
+import com.yahoo.jdisc.ResourceReference;
+
+/**
+ * Interface for implementations of reference counting
+ * @author baldersheim
+ */
+public interface References {
+ /** Release the initial reference */
+ void release();
+ /** Returns number of held references */
+ int referenceCount();
+ /**
+ * Adds a reference and return an objects that when closed will return the reference.
+ * Supply a context that can provide link to the one holding the link. Useful for debugging
+ */
+ ResourceReference refer(Object context);
+
+ String currentState();
+}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/ReferencesByCount.java b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/ReferencesByCount.java
new file mode 100644
index 00000000000..0f417c81a8b
--- /dev/null
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/ReferencesByCount.java
@@ -0,0 +1,83 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.refcount;
+
+import com.yahoo.jdisc.ResourceReference;
+import com.yahoo.jdisc.SharedResource;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Does reference counting by using atomic counting of references
+ * Default in production
+ *
+ * @author baldersheim
+ */
+public class ReferencesByCount implements References {
+ private final AtomicInteger refCount;
+ private final DestructableResource resource;
+ private final NoDebugResourceReference initialReference;
+
+ public ReferencesByCount(DestructableResource resource) {
+ refCount = new AtomicInteger(1);
+ this.resource = resource;
+ initialReference = new NoDebugResourceReference(this);
+ }
+
+ @Override
+ public void release() {
+ initialReference.close();
+ }
+
+ @Override
+ public int referenceCount() {
+ return refCount.get();
+ }
+
+ @Override
+ public ResourceReference refer(Object context) {
+ addRef(1);
+ return new NoDebugResourceReference(this);
+ }
+
+ @Override
+ public String currentState() {
+ 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.";
+ }
+
+ private void removeRef() {
+ int refCount = addRef(-1);
+ if (refCount == 0) {
+ resource.close();
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ private static class NoDebugResourceReference extends CloseableOnce {
+ private final ReferencesByCount resource;
+
+ NoDebugResourceReference(final ReferencesByCount resource) {
+ this.resource = resource;
+ }
+
+ @Override final void onClose() { resource.removeRef(); }
+ @Override References getReferences() { return resource; }
+ }
+}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/package-info.java b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/package-info.java
new file mode 100644
index 00000000000..0797951df9e
--- /dev/null
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/package-info.java
@@ -0,0 +1,4 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+@com.yahoo.osgi.annotation.ExportPackage
+package com.yahoo.jdisc.refcount;
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/service/CurrentContainer.java b/jdisc_core/src/main/java/com/yahoo/jdisc/service/CurrentContainer.java
index 9b7af7e49f5..f0a39e0f045 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/service/CurrentContainer.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/service/CurrentContainer.java
@@ -27,11 +27,19 @@ public interface CurrentContainer {
*
* @param uri The identifier used to match this Request to an appropriate {@link ClientProvider} or {@link
* RequestHandler}. The hostname must be "localhost" or a fully qualified domain name.
+ * @param context that can be attached for reference tracking
* @return A reference to the current Container.
* @throws NoBindingSetSelectedException If no {@link BindingSet} was selected by the {@link BindingSetSelector}.
* @throws BindingSetNotFoundException If the named BindingSet was not found.
* @throws ContainerNotReadyException If no active Container was found, this can only happen during initial
* setup.
*/
- public Container newReference(URI uri);
+ default Container newReference(URI uri, Object context) {
+ return newReference(uri);
+ }
+
+ default Container newReference(URI uri) {
+ return newReference(uri, null);
+ }
+
}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerSnapshotTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerSnapshotTestCase.java
index ac2efafbba5..472ac095188 100644
--- a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerSnapshotTestCase.java
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerSnapshotTestCase.java
@@ -146,7 +146,7 @@ public class ContainerSnapshotTestCase {
}
});
ActiveContainer active = new ActiveContainer(driver.newContainerBuilder());
- ContainerSnapshot snapshot = new ContainerSnapshot(active, null, null);
+ ContainerSnapshot snapshot = new ContainerSnapshot(active, null, null, null);
assertSame(obj, snapshot.getInstance(Object.class));
assertEquals("foo", snapshot.getInstance(Key.get(String.class, Names.named("foo"))));
snapshot.release();