diff options
author | Henning Baldersheim <balder@yahoo-inc.com> | 2021-10-04 17:20:40 +0200 |
---|---|---|
committer | Henning Baldersheim <balder@yahoo-inc.com> | 2021-10-05 14:49:20 +0200 |
commit | c9982ddc5ce3083101541a301cca20b19b4914e8 (patch) | |
tree | f63fa1165e4b8572a7dd7c24ed056e851b97660d /jdisc_core/src/main/java/com/yahoo/jdisc/refcount | |
parent | 5f5cb7aa87195c92b374dcbc742a5263559c8bb0 (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')
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; |