From e59d12ed20f5036c631a0fe75d3052152bc8d8c4 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Fri, 18 Mar 2022 10:35:21 -0500 Subject: [PATCH] Introduce reference support submodule --- pom.xml | 1 + ref/pom.xml | 49 +++++ .../smallrye/common/ref/CleanerReference.java | 37 ++++ .../smallrye/common/ref/PhantomReference.java | 59 ++++++ .../java/io/smallrye/common/ref/Reapable.java | 17 ++ .../java/io/smallrye/common/ref/Reaper.java | 17 ++ .../io/smallrye/common/ref/Reference.java | 135 ++++++++++++++ .../io/smallrye/common/ref/References.java | 176 ++++++++++++++++++ .../io/smallrye/common/ref/SoftReference.java | 78 ++++++++ .../smallrye/common/ref/StrongReference.java | 53 ++++++ .../io/smallrye/common/ref/WeakReference.java | 78 ++++++++ .../io/smallrye/common/ref/package-info.java | 6 + 12 files changed, 706 insertions(+) create mode 100644 ref/pom.xml create mode 100644 ref/src/main/java/io/smallrye/common/ref/CleanerReference.java create mode 100644 ref/src/main/java/io/smallrye/common/ref/PhantomReference.java create mode 100644 ref/src/main/java/io/smallrye/common/ref/Reapable.java create mode 100644 ref/src/main/java/io/smallrye/common/ref/Reaper.java create mode 100644 ref/src/main/java/io/smallrye/common/ref/Reference.java create mode 100644 ref/src/main/java/io/smallrye/common/ref/References.java create mode 100644 ref/src/main/java/io/smallrye/common/ref/SoftReference.java create mode 100644 ref/src/main/java/io/smallrye/common/ref/StrongReference.java create mode 100644 ref/src/main/java/io/smallrye/common/ref/WeakReference.java create mode 100644 ref/src/main/java/io/smallrye/common/ref/package-info.java diff --git a/pom.xml b/pom.xml index 83f5a81f..3b0ccd09 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,7 @@ io net os + ref version vertx-context diff --git a/ref/pom.xml b/ref/pom.xml new file mode 100644 index 00000000..918b8d17 --- /dev/null +++ b/ref/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + io.smallrye.common + smallrye-common-parent + 1.10.1-SNAPSHOT + + + smallrye-common-ref + + SmallRye Common: References + + + + ${project.groupId} + smallrye-common-constraint + + + + + + coverage + + @{jacocoArgLine} + + + + + org.jacoco + jacoco-maven-plugin + + + report + verify + + report + + + + + + + + + diff --git a/ref/src/main/java/io/smallrye/common/ref/CleanerReference.java b/ref/src/main/java/io/smallrye/common/ref/CleanerReference.java new file mode 100644 index 00000000..0142744a --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/CleanerReference.java @@ -0,0 +1,37 @@ +package io.smallrye.common.ref; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A special version of {@link PhantomReference} that is strongly retained until it is reaped by the collection thread. + */ +public class CleanerReference extends PhantomReference { + private static final Set> set = Collections + .newSetFromMap(new ConcurrentHashMap, Boolean>()); + + /** + * Construct a new instance with a reaper. + * + * @param referent the referent + * @param attachment the attachment + * @param reaper the reaper to use + */ + public CleanerReference(final T referent, final A attachment, final Reaper reaper) { + super(referent, attachment, reaper); + set.add(this); + } + + void clean() { + set.remove(this); + } + + public final int hashCode() { + return super.hashCode(); + } + + public final boolean equals(final Object obj) { + return super.equals(obj); + } +} diff --git a/ref/src/main/java/io/smallrye/common/ref/PhantomReference.java b/ref/src/main/java/io/smallrye/common/ref/PhantomReference.java new file mode 100644 index 00000000..67722743 --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/PhantomReference.java @@ -0,0 +1,59 @@ +package io.smallrye.common.ref; + +import java.lang.ref.ReferenceQueue; + +/** + * A reapable phantom reference with an attachment. If a {@link Reaper} is given, then it will be used to asynchronously + * clean up the referent. + * + * @param the reference value type + * @param the attachment type + * + * @see java.lang.ref.PhantomReference + */ +public class PhantomReference extends java.lang.ref.PhantomReference implements Reference, Reapable { + private final A attachment; + private final Reaper reaper; + + /** + * Construct a new instance with an explicit reference queue. + * + * @param referent the referent + * @param attachment the attachment + * @param q the reference queue to use + */ + public PhantomReference(final T referent, final A attachment, final ReferenceQueue q) { + super(referent, q); + this.attachment = attachment; + reaper = null; + } + + /** + * Construct a new instance with a reaper. + * + * @param referent the referent + * @param attachment the attachment + * @param reaper the reaper to use + */ + public PhantomReference(final T referent, final A attachment, final Reaper reaper) { + super(referent, References.ReaperThread.REAPER_QUEUE); + this.reaper = reaper; + this.attachment = attachment; + } + + public A getAttachment() { + return attachment; + } + + public Type getType() { + return Type.PHANTOM; + } + + public Reaper getReaper() { + return reaper; + } + + public String toString() { + return "phantom reference"; + } +} diff --git a/ref/src/main/java/io/smallrye/common/ref/Reapable.java b/ref/src/main/java/io/smallrye/common/ref/Reapable.java new file mode 100644 index 00000000..216c9bf8 --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/Reapable.java @@ -0,0 +1,17 @@ +package io.smallrye.common.ref; + +/** + * A reference which is reapable (can be automatically collected). + * + * @param the reference type + * @param the reference attachment type + */ +interface Reapable { + + /** + * Get the associated reaper. + * + * @return the reaper + */ + Reaper getReaper(); +} diff --git a/ref/src/main/java/io/smallrye/common/ref/Reaper.java b/ref/src/main/java/io/smallrye/common/ref/Reaper.java new file mode 100644 index 00000000..a07b7dd7 --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/Reaper.java @@ -0,0 +1,17 @@ +package io.smallrye.common.ref; + +/** + * A cleaner for a dead object. + * + * @param the reference type + * @param the reference attachment type + */ +public interface Reaper { + + /** + * Perform the cleanup action for a reference. + * + * @param reference the reference + */ + void reap(Reference reference); +} diff --git a/ref/src/main/java/io/smallrye/common/ref/Reference.java b/ref/src/main/java/io/smallrye/common/ref/Reference.java new file mode 100644 index 00000000..2365c7c6 --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/Reference.java @@ -0,0 +1,135 @@ +package io.smallrye.common.ref; + +import java.util.EnumSet; + +/** + * An enhanced reference type with a type-safe attachment. + * + * @param the reference value type + * @param the attachment type + * + * @see java.lang.ref.Reference + */ +public interface Reference { + + /** + * Get the value, or {@code null} if the reference has been cleared. + * + * @return the value + */ + T get(); + + /** + * Get the attachment, if any. + * + * @return the attachment + */ + A getAttachment(); + + /** + * Clear the reference. + */ + void clear(); + + /** + * Get the type of the reference. + * + * @return the type + */ + Type getType(); + + /** + * A reference type. + * + * @apiviz.exclude + */ + enum Type { + + /** + * A strong reference. + */ + STRONG, + /** + * A weak reference. + */ + WEAK, + /** + * A phantom reference. + */ + PHANTOM, + /** + * A soft reference. + */ + SOFT, + /** + * A {@code null} reference. + */ + NULL, + ; + + private static final int fullSize = values().length; + + /** + * Determine whether the given set is fully populated (or "full"), meaning it contains all possible values. + * + * @param set the set + * + * @return {@code true} if the set is full, {@code false} otherwise + */ + public static boolean isFull(final EnumSet set) { + return set != null && set.size() == fullSize; + } + + /** + * Determine whether this instance is equal to one of the given instances. + * + * @param v1 the first instance + * + * @return {@code true} if one of the instances matches this one, {@code false} otherwise + */ + public boolean in(final Type v1) { + return this == v1; + } + + /** + * Determine whether this instance is equal to one of the given instances. + * + * @param v1 the first instance + * @param v2 the second instance + * + * @return {@code true} if one of the instances matches this one, {@code false} otherwise + */ + public boolean in(final Type v1, final Type v2) { + return this == v1 || this == v2; + } + + /** + * Determine whether this instance is equal to one of the given instances. + * + * @param v1 the first instance + * @param v2 the second instance + * @param v3 the third instance + * + * @return {@code true} if one of the instances matches this one, {@code false} otherwise + */ + public boolean in(final Type v1, final Type v2, final Type v3) { + return this == v1 || this == v2 || this == v3; + } + + /** + * Determine whether this instance is equal to one of the given instances. + * + * @param values the possible values + * + * @return {@code true} if one of the instances matches this one, {@code false} otherwise + */ + public boolean in(final Type... values) { + if (values != null) + for (Type value : values) { + if (this == value) + return true; + } + return false; + } + } +} diff --git a/ref/src/main/java/io/smallrye/common/ref/References.java b/ref/src/main/java/io/smallrye/common/ref/References.java new file mode 100644 index 00000000..1ac18f1b --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/References.java @@ -0,0 +1,176 @@ +package io.smallrye.common.ref; + +import static java.security.AccessController.doPrivileged; + +import java.lang.ref.ReferenceQueue; +import java.security.PrivilegedAction; +import java.util.concurrent.atomic.AtomicInteger; + +import io.smallrye.common.constraint.Assert; + +/** + * A set of utility methods for reference types. + */ +public final class References { + private References() { + } + + private static final Reference NULL = new StrongReference<>(null); + + static final class ReaperThread extends Thread { + static final ReferenceQueue REAPER_QUEUE = new ReferenceQueue(); + + static { + final AtomicInteger cnt = new AtomicInteger(1); + final PrivilegedAction action = () -> { + final ReaperThread thr = new ReaperThread(); + thr.setName("Reference Reaper #" + cnt.getAndIncrement()); + thr.setDaemon(true); + thr.start(); + return null; + }; + for (int i = 0; i < 3; i++) { + doPrivileged(action); + } + } + + public void run() { + for (;;) + try { + final java.lang.ref.Reference ref = REAPER_QUEUE.remove(); + if (ref instanceof CleanerReference) { + ((CleanerReference) ref).clean(); + } + if (ref instanceof Reapable) { + reap((Reapable) ref); + } + } catch (InterruptedException ignored) { + // we consume interrupts. + } catch (Throwable cause) { + // ignore failures. + } + } + + @SuppressWarnings({ "unchecked" }) + private static void reap(final Reapable reapable) { + reapable.getReaper().reap((Reference) reapable); + } + } + + /** + * Create a reference of a given type with the provided value and attachment. If the reference type is + * {@link Reference.Type#STRONG} or {@link Reference.Type#NULL} then the reaper argument is ignored. If + * the reference type is {@link Reference.Type#NULL} then the value and attachment arguments are ignored. + * + * @param type the reference type + * @param value the reference value + * @param attachment the attachment value + * @param reaper the reaper to use, if any + * @param the reference value type + * @param the reference attachment type + * @return the reference + */ + public static Reference create(Reference.Type type, T value, A attachment, Reaper reaper) { + Assert.checkNotNullParam("type", type); + if (value == null) { + type = Reference.Type.NULL; + } + switch (type) { + case STRONG: + return new StrongReference(value, attachment); + case WEAK: + return new WeakReference(value, attachment, reaper); + case PHANTOM: + return new PhantomReference(value, attachment, reaper); + case SOFT: + return new SoftReference(value, attachment, reaper); + case NULL: + return attachment == null ? getNullReference() : new StrongReference<>(null, attachment); + default: + throw Assert.impossibleSwitchCase(type); + } + } + + /** + * Create a reference of a given type with the provided value and attachment. If the reference type is + * {@link Reference.Type#STRONG} or {@link Reference.Type#NULL} then the reference queue argument is ignored. If + * the reference type is {@link Reference.Type#NULL} then the value and attachment arguments are ignored. + * + * @param type the reference type + * @param value the reference value + * @param attachment the attachment value + * @param referenceQueue the reference queue to use, if any + * @param the reference value type + * @param the reference attachment type + * @return the reference + */ + public static Reference create(Reference.Type type, T value, A attachment, + ReferenceQueue referenceQueue) { + Assert.checkNotNullParam("type", type); + if (referenceQueue == null) + return create(type, value, attachment); + if (value == null) { + type = Reference.Type.NULL; + } + switch (type) { + case STRONG: + return new StrongReference(value, attachment); + case WEAK: + return new WeakReference(value, attachment, referenceQueue); + case PHANTOM: + return new PhantomReference(value, attachment, referenceQueue); + case SOFT: + return new SoftReference(value, attachment, referenceQueue); + case NULL: + return attachment == null ? getNullReference() : new StrongReference<>(null, attachment); + default: + throw Assert.impossibleSwitchCase(type); + } + } + + /** + * Create a reference of a given type with the provided value and attachment. If the reference type is + * {@link Reference.Type#PHANTOM} then this method will return a {@code null} reference because + * such references are not constructable without a queue or reaper. If the reference type is + * {@link Reference.Type#NULL} then the value and attachment arguments are ignored. + * + * @param type the reference type + * @param value the reference value + * @param attachment the attachment value + * @param the reference value type + * @param the reference attachment type + * @return the reference + */ + public static Reference create(Reference.Type type, T value, A attachment) { + Assert.checkNotNullParam("type", type); + if (value == null) { + type = Reference.Type.NULL; + } + switch (type) { + case STRONG: + return new StrongReference(value, attachment); + case WEAK: + return new WeakReference(value, attachment); + case SOFT: + return new SoftReference(value, attachment); + case PHANTOM: + case NULL: + return attachment == null ? getNullReference() : new StrongReference<>(null, attachment); + default: + throw Assert.impossibleSwitchCase(type); + } + } + + /** + * Get a {@code null} reference. This reference type is always cleared and does not retain an attachment; as such + * there is only one single instance of it. + * + * @param the reference value type + * @param the attachment value type + * @return the {@code null} reference + */ + @SuppressWarnings({ "unchecked" }) + public static Reference getNullReference() { + return (Reference) NULL; + } +} diff --git a/ref/src/main/java/io/smallrye/common/ref/SoftReference.java b/ref/src/main/java/io/smallrye/common/ref/SoftReference.java new file mode 100644 index 00000000..93b82209 --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/SoftReference.java @@ -0,0 +1,78 @@ +package io.smallrye.common.ref; + +import java.lang.ref.ReferenceQueue; + +/** + * A reapable soft reference with an attachment. If a {@link Reaper} is given, then it will be used to asynchronously + * clean up the referent. + * + * @param the reference value type + * @param the attachment type + * + * @see java.lang.ref.SoftReference + */ +public class SoftReference extends java.lang.ref.SoftReference implements Reference, Reapable { + private final A attachment; + private final Reaper reaper; + + /** + * Construct a new instance. + * + * @param referent the referent + */ + public SoftReference(final T referent) { + this(referent, null, (ReferenceQueue) null); + } + + /** + * Construct a new instance. + * + * @param referent the referent + * @param attachment the attachment + */ + public SoftReference(final T referent, final A attachment) { + this(referent, attachment, (ReferenceQueue) null); + } + + /** + * Construct a new instance with an explicit reference queue. + * + * @param referent the referent + * @param attachment the attachment + * @param q the reference queue to use + */ + public SoftReference(final T referent, final A attachment, final ReferenceQueue q) { + super(referent, q); + reaper = null; + this.attachment = attachment; + } + + /** + * Construct a new instance with a reaper. + * + * @param referent the referent + * @param attachment the attachment + * @param reaper the reaper to use + */ + public SoftReference(final T referent, final A attachment, final Reaper reaper) { + super(referent, References.ReaperThread.REAPER_QUEUE); + this.reaper = reaper; + this.attachment = attachment; + } + + public Reaper getReaper() { + return reaper; + } + + public A getAttachment() { + return attachment; + } + + public Type getType() { + return Type.SOFT; + } + + public String toString() { + return "soft reference to " + get(); + } +} diff --git a/ref/src/main/java/io/smallrye/common/ref/StrongReference.java b/ref/src/main/java/io/smallrye/common/ref/StrongReference.java new file mode 100644 index 00000000..bf312d2b --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/StrongReference.java @@ -0,0 +1,53 @@ +package io.smallrye.common.ref; + +/** + * A strong reference with an attachment. Since strong references are always reachable, a reaper may not be used. + * + * @param the reference value type + * @param the attachment type + */ +public class StrongReference implements Reference { + + private volatile T referent; + private final A attachment; + + /** + * Construct a new instance. + * + * @param referent the referent + * @param attachment the attachment + */ + public StrongReference(final T referent, final A attachment) { + this.referent = referent; + this.attachment = attachment; + } + + /** + * Construct a new instance. + * + * @param referent the referent + */ + public StrongReference(final T referent) { + this(referent, null); + } + + public T get() { + return referent; + } + + public void clear() { + referent = null; + } + + public A getAttachment() { + return attachment; + } + + public Type getType() { + return Type.STRONG; + } + + public String toString() { + return "strong reference to " + get(); + } +} diff --git a/ref/src/main/java/io/smallrye/common/ref/WeakReference.java b/ref/src/main/java/io/smallrye/common/ref/WeakReference.java new file mode 100644 index 00000000..688970a2 --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/WeakReference.java @@ -0,0 +1,78 @@ +package io.smallrye.common.ref; + +import java.lang.ref.ReferenceQueue; + +/** + * A reapable weak reference with an attachment. If a {@link Reaper} is given, then it will be used to asynchronously + * clean up the referent. + * + * @param the reference value type + * @param the attachment type + * + * @see java.lang.ref.WeakReference + */ +public class WeakReference extends java.lang.ref.WeakReference implements Reference, Reapable { + private final A attachment; + private final Reaper reaper; + + /** + * Construct a new instance. + * + * @param referent the referent + */ + public WeakReference(final T referent) { + this(referent, null, (Reaper) null); + } + + /** + * Construct a new instance. + * + * @param referent the referent + * @param attachment the attachment + */ + public WeakReference(final T referent, final A attachment) { + this(referent, attachment, (Reaper) null); + } + + /** + * Construct a new instance with an explicit reference queue. + * + * @param referent the referent + * @param attachment the attachment + * @param q the reference queue to use + */ + public WeakReference(final T referent, final A attachment, final ReferenceQueue q) { + super(referent, q); + this.attachment = attachment; + reaper = null; + } + + /** + * Construct a new instance with a reaper. + * + * @param referent the referent + * @param attachment the attachment + * @param reaper the reaper to use + */ + public WeakReference(final T referent, final A attachment, final Reaper reaper) { + super(referent, References.ReaperThread.REAPER_QUEUE); + this.attachment = attachment; + this.reaper = reaper; + } + + public A getAttachment() { + return attachment; + } + + public Type getType() { + return Type.WEAK; + } + + public Reaper getReaper() { + return reaper; + } + + public String toString() { + return "weak reference to " + get(); + } +} diff --git a/ref/src/main/java/io/smallrye/common/ref/package-info.java b/ref/src/main/java/io/smallrye/common/ref/package-info.java new file mode 100644 index 00000000..ce13421f --- /dev/null +++ b/ref/src/main/java/io/smallrye/common/ref/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes which implement reference types which can be cleaned up automatically by a background thread. See + * {@link io.smallrye.common.ref.Reference Reference} and its subtypes, and {@link io.smallrye.common.ref.Reaper Reaper} for + * more information. + */ +package io.smallrye.common.ref; \ No newline at end of file