001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2023–2024 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 012 * specific language governing permissions and limitations under the License. 013 */ 014package org.microbean.bean; 015 016import java.lang.invoke.MethodHandles; 017import java.lang.invoke.VarHandle; 018 019import java.lang.System.Logger; 020 021import java.lang.ref.WeakReference; 022 023import java.util.Objects; 024 025import java.util.function.Consumer; 026 027import static java.lang.System.Logger.Level.DEBUG; 028 029/** 030 * An {@link AutoCloseable} {@link WeakReference} that formally disposes of referents after they have been {@linkplain 031 * #clear() cleared} by the Java Virtual Machine during garbage collection. 032 * 033 * <p>Note that garbage collection may never happen.</p> 034 * 035 * @param <R> the type of the reference 036 * 037 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 038 * 039 * @see #DisposableReference(Object, Consumer) 040 */ 041public final class DisposableReference<R> extends WeakReference<R> implements AutoCloseable { 042 043 044 /* 045 * Static fields. 046 */ 047 048 049 /** 050 * A {@link Logger} for instances of this class. 051 * 052 * <p>The {@link Logger}'s name is equal to this class' {@linkplain Class#getName() name}.</p> 053 */ 054 private static final Logger LOGGER = System.getLogger(DisposableReference.class.getName()); 055 056 /** 057 * A {@link VarHandle} providing access to the {@link #disposed} field. 058 * 059 * @nullability This field is never {@code null}. 060 * 061 * @see #disposed 062 * 063 * @see #dispose() 064 * 065 * @see #disposed() 066 */ 067 private static final VarHandle DISPOSED; 068 069 static { 070 try { 071 DISPOSED = MethodHandles.lookup().findVarHandle(DisposableReference.class, "disposed", boolean.class); 072 } catch (final NoSuchFieldException | IllegalAccessException e) { 073 throw (Error)new ExceptionInInitializerError(e.getMessage()).initCause(e); 074 } 075 } 076 077 078 /* 079 * Instance fields. 080 */ 081 082 083 /** 084 * The referent initially retrievable via the {@link #get()} method, but in a way that does not prevent weak 085 * reachability. 086 * 087 * @nullability This field may be {@code null}. 088 * 089 * @see #DisposableReference(Object, Consumer) 090 */ 091 private final R referent; 092 093 /** 094 * A {@link Consumer} used to dispose of the {@linkplain #referent referent} at the appropriate time. 095 * 096 * @nullability This field may be {@code null}. 097 */ 098 private final Consumer<? super R> disposer; 099 100 /** 101 * Whether or not the {@link #dispose()} method has been called successfully. 102 * 103 * @see #dispose() 104 * 105 * @see #disposed() 106 * 107 * @see #DISPOSED 108 */ 109 private volatile boolean disposed; 110 111 112 /* 113 * Constructors. 114 */ 115 116 117 /** 118 * Creates a new {@link DisposableReference}. 119 * 120 * @param referent the referent; may be {@code null} 121 * 122 * @param disposer a thread-safe {@link Consumer} whose {@link Consumer#accept(Object) accept(Object)} method, which 123 * must be idempotent, will be invoked from a separate thread to dispose of the referent after it has been {@linkplain 124 * #clear() cleared} by the Java Virtual Machine during garbage collection; may be {@code null} in which case no 125 * destruction will take place 126 */ 127 public DisposableReference(final R referent, final Consumer<? super R> disposer) { 128 super(Objects.requireNonNull(referent, "referent"), ReferenceQueue.INSTANCE); 129 this.referent = referent; 130 this.disposer = disposer == null ? DisposableReference::noopDispose : disposer; 131 } 132 133 134 /* 135 * Instance methods. 136 */ 137 138 139 /** 140 * Calls {@link #enqueue()}. 141 * 142 * @see #enqueue() 143 */ 144 @Override // AutoCloseable 145 public final void close() { 146 this.enqueue(); // idempotent 147 } 148 149 /** 150 * Calls {@link #refersTo(Object) refersTo(null)} and returns the result. 151 * 152 * @return the result of an invocation of {@link #refersTo(Object) refersTo(null)} 153 * 154 * @see #refersTo(Object) 155 */ 156 public final boolean closed() { 157 return this.refersTo(null); 158 } 159 160 /** 161 * If there has been no prior successful invocation of this method, calls the {@link Consumer#accept(Object) 162 * accept(Object)} method on the {@link Consumer} representing the disposer {@linkplain #DisposableReference(Object, 163 * Consumer) supplied at construction time}, thus notionally disposeing the {@linkplain #DisposableReference(Object, 164 * Consumer) referent supplied at construction time}, and returns {@code true}. 165 * 166 * <p>Destruction does not imply {@linkplain #close() closing}, and closing does not imply destruction (though it 167 * normally will eventually lead to it).</p> 168 * 169 * <p>If destruction does not result in any {@link RuntimeException} or {@link Error} being thrown, then calling this 170 * method again, from any thread, will have no effect and it will return {@code false}.</p> 171 * 172 * <p>If the first invocation of this method from any thread succeeds, then it will return {@code true}, and all other 173 * invocations of this method from any thread will return {@code false}, and will have no effect.</p> 174 * 175 * <p>This method is often called from a thread dedicated to disposeing {@linkplain #enqueue() enqueued} {@link 176 * DisposableReference}s, so the {@link Consumer} {@linkplain #DisposableReference(Object, Consumer) supplied at 177 * construction time} must be thread-safe.</p> 178 * 179 * @return {@code true} if this invocation of this method caused destruction to happen successfully; {@code false} in 180 * all other cases 181 * 182 * @exception RuntimeException if an invocation of the {@link Consumer#accept(Object) accept(Object)} method on the 183 * {@link Consumer} {@linkplain #DisposableReference(Object, Consumer) supplied at construction time} fails 184 * 185 * @idempotency This method is idempotent. 186 * 187 * @threadsafety This method is safe for concurrent use by multiple threads. 188 * 189 * @see #DisposableReference(Object, Consumer) 190 */ 191 public final boolean dispose() { 192 if (DISPOSED.compareAndSet(this, false, true)) { // volatile write; assume disposer success 193 try { 194 this.disposer.accept(this.referent); 195 } catch (RuntimeException | Error e) { 196 this.disposed = false; // volatile write; oops; our optimism was misplaced 197 throw e; 198 } 199 return true; 200 } else { 201 return false; 202 } 203 } 204 205 /** 206 * Returns {@code true} if and only if there has been a prior successful invocation of {@link #dispose()} that 207 * returned {@code true}. 208 * 209 * @return {@code true} if and only if there has been a prior successful invocation of {@link #dispose()} that 210 * returned {@code true} 211 * 212 * @see #dispose() 213 */ 214 public final boolean disposed() { 215 return this.disposed; // volatile read 216 } 217 218 219 /* 220 * Static methods. 221 */ 222 223 224 private static final <R> void noopDispose(final R r) { 225 if (LOGGER.isLoggable(DEBUG)) { 226 LOGGER.log(DEBUG, "DisposableReference referent " + r + " has been cleared"); 227 } 228 } 229 230 231 /* 232 * Inner and nested classes. 233 */ 234 235 236 private static final class ReferenceQueue extends java.lang.ref.ReferenceQueue<Object> implements Runnable { 237 238 239 /* 240 * Static fields. 241 */ 242 243 244 private static final ReferenceQueue INSTANCE = new ReferenceQueue(); 245 246 static { 247 final Thread t = new Thread(ReferenceQueue.INSTANCE, "DisposableReference disposer"); 248 t.setDaemon(true); 249 t.setPriority(3); // a little less important than the default 250 t.start(); 251 } 252 253 254 /* 255 * Constructors. 256 */ 257 258 259 private ReferenceQueue() { 260 super(); 261 } 262 263 264 /* 265 * Instance methods. 266 */ 267 268 269 @Override // Runnable 270 public final void run() { 271 while (!Thread.currentThread().isInterrupted()) { 272 try { 273 ((DisposableReference<?>)this.remove()).dispose(); 274 } catch (final InterruptedException e) { 275 Thread.currentThread().interrupt(); 276 } 277 } 278 } 279 280 } 281 282}