aboutsummaryrefslogtreecommitdiffstats
path: root/jdisc_core/src/main/java/com/yahoo/jdisc/refcount
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 /jdisc_core/src/main/java/com/yahoo/jdisc/refcount
parent5f5cb7aa87195c92b374dcbc742a5263559c8bb0 (diff)
Add option to attach a context to refer.
Introduce a lightweight debug mode.
Diffstat (limited to 'jdisc_core/src/main/java/com/yahoo/jdisc/refcount')
-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
7 files changed, 341 insertions, 0 deletions
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;