All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.gatling.grpc.internal.guava.common.base.FinalizableReferenceQueue Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2007 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package io.gatling.grpc.internal.guava.common.base;

import io.gatling.grpc.internal.guava.common.annotations.GwtIncompatible;
import io.gatling.grpc.internal.guava.common.annotations.J2ktIncompatible;
import io.gatling.grpc.internal.guava.common.annotations.VisibleForTesting;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;

/**
 * A reference queue with an associated background thread that dequeues references and invokes
 * {@link FinalizableReference#finalizeReferent()} on them.
 *
 * 

Keep a strong reference to this object until all of the associated referents have been * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code * finalizeReferent()} on the remaining references. * *

As an example of how this is used, imagine you have a class {@code MyServer} that creates a * {@link java.net.ServerSocket ServerSocket}, and you would like to ensure that the {@code * ServerSocket} is closed even if the {@code MyServer} object is garbage-collected without calling * its {@code close} method. You could use a finalizer to accomplish this, but that has a * number of well-known problems. Here is how you might use this class instead: * *

{@code
 * public class MyServer implements Closeable {
 *   private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
 *   // You might also share this between several objects.
 *
 *   private static final Set> references = Sets.newConcurrentHashSet();
 *   // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
 *
 *   private final ServerSocket serverSocket;
 *
 *   private MyServer(...) {
 *     ...
 *     this.serverSocket = new ServerSocket(...);
 *     ...
 *   }
 *
 *   public static MyServer create(...) {
 *     MyServer myServer = new MyServer(...);
 *     final ServerSocket serverSocket = myServer.serverSocket;
 *     Reference reference = new FinalizablePhantomReference(myServer, frq) {
 *       public void finalizeReferent() {
 *         references.remove(this):
 *         if (!serverSocket.isClosed()) {
 *           ...log a message about how nobody called close()...
 *           try {
 *             serverSocket.close();
 *           } catch (IOException e) {
 *             ...
 *           }
 *         }
 *       }
 *     };
 *     references.add(reference);
 *     return myServer;
 *   }
 *
 *   public void close() {
 *     serverSocket.close();
 *   }
 * }
 * }
* * @author Bob Lee * @since 2.0 */ @J2ktIncompatible @GwtIncompatible @ElementTypesAreNonnullByDefault public class FinalizableReferenceQueue implements Closeable { /* * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a * map built by MapMaker) no longer has a strong reference to this object, the garbage collector * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the * Finalizer to stop. * * If this library is loaded in the system class loader, FinalizableReferenceQueue can load * Finalizer directly with no problems. * * If this library is loaded in an application class loader, it's important that Finalizer not * have a strong reference back to the class loader. Otherwise, you could have a graph like this: * * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance * * Even if no other references to classes from the application class loader remain, the Finalizer * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the * Finalizer running, and as a result, the application class loader can never be reclaimed. * * This means that dynamically loaded web applications and OSGi bundles can't be unloaded. * * If the library is loaded in an application class loader, we try to break the cycle by loading * Finalizer in its own independent class loader: * * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue -> * etc. -> Decoupled class loader -> Finalizer * * Now, Finalizer no longer keeps an indirect strong reference to the static * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed * at which point the Finalizer thread will stop and its decoupled class loader can also be * reclaimed. * * If any of this fails along the way, we fall back to loading Finalizer directly in the * application class loader. * * NOTE: The tests for this behavior (FinalizableReferenceQueueClassLoaderUnloadingTest) fail * strangely when run in JDK 9. We are considering this a known issue. Please see * https://github.com/google/guava/issues/3086 for more information. */ private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName()); private static final String FINALIZER_CLASS_NAME = "io.gatling.grpc.internal.guava.common.base.internal.Finalizer"; /** Reference to Finalizer.startFinalizer(). */ private static final Method startFinalizer; static { Class finalizer = loadFinalizer(new SystemLoader(), new DecoupledLoader(), new DirectLoader()); startFinalizer = getStartFinalizer(finalizer); } /** The actual reference queue that our background thread will poll. */ final ReferenceQueue queue; final PhantomReference frqRef; /** Whether or not the background thread started successfully. */ final boolean threadStarted; /** Constructs a new queue. */ public FinalizableReferenceQueue() { // We could start the finalizer lazily, but I'd rather it blow up early. queue = new ReferenceQueue<>(); frqRef = new PhantomReference<>(this, queue); boolean threadStarted = false; try { startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef); threadStarted = true; } catch (IllegalAccessException impossible) { throw new AssertionError(impossible); // startFinalizer() is public } catch (Throwable t) { logger.log( Level.INFO, "Failed to start reference finalizer thread." + " Reference cleanup will only occur when new references are created.", t); } this.threadStarted = threadStarted; } @Override public void close() { frqRef.enqueue(); cleanUp(); } /** * Repeatedly dequeues references from the queue and invokes {@link * FinalizableReference#finalizeReferent()} on them until the queue is empty. This method is a * no-op if the background thread was created successfully. */ void cleanUp() { if (threadStarted) { return; } Reference reference; while ((reference = queue.poll()) != null) { /* * This is for the benefit of phantom references. Weak and soft references will have already * been cleared by this point. */ reference.clear(); try { ((FinalizableReference) reference).finalizeReferent(); } catch (Throwable t) { logger.log(Level.SEVERE, "Error cleaning up after reference.", t); } } } /** * Iterates through the given loaders until it finds one that can load Finalizer. * * @return Finalizer.class */ private static Class loadFinalizer(FinalizerLoader... loaders) { for (FinalizerLoader loader : loaders) { Class finalizer = loader.loadFinalizer(); if (finalizer != null) { return finalizer; } } throw new AssertionError(); } /** Loads Finalizer.class. */ interface FinalizerLoader { /** * Returns Finalizer.class or null if this loader shouldn't or can't load it. * * @throws SecurityException if we don't have the appropriate privileges */ @CheckForNull Class loadFinalizer(); } /** * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path, * we needn't create a separate loader. */ static class SystemLoader implements FinalizerLoader { // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable // finding Finalizer on the system class path even if it is there. @VisibleForTesting static boolean disabled; @Override @CheckForNull public Class loadFinalizer() { if (disabled) { return null; } ClassLoader systemLoader; try { systemLoader = ClassLoader.getSystemClassLoader(); } catch (SecurityException e) { logger.info("Not allowed to access system class loader."); return null; } if (systemLoader != null) { try { return systemLoader.loadClass(FINALIZER_CLASS_NAME); } catch (ClassNotFoundException e) { // Ignore. Finalizer is simply in a child class loader. return null; } } else { return null; } } } /** * Try to load Finalizer in its own class loader. If Finalizer's thread had a direct reference to * our class loader (which could be that of a dynamically loaded web application or OSGi bundle), * it would prevent our class loader from getting garbage collected. */ static class DecoupledLoader implements FinalizerLoader { private static final String LOADING_ERROR = "Could not load Finalizer in its own class loader. Loading Finalizer in the current class " + "loader instead. As a result, you will not be able to garbage collect this class " + "loader. To support reclaiming this class loader, either resolve the underlying " + "issue, or move Guava to your system class path."; @Override @CheckForNull public Class loadFinalizer() { try { /* * We use URLClassLoader because it's the only concrete class loader implementation in the * JDK. If we used our own ClassLoader subclass, Finalizer would indirectly reference this * class loader: * * Finalizer.class -> CustomClassLoader -> CustomClassLoader.class -> This class loader * * System class loader will (and must) be the parent. */ ClassLoader finalizerLoader = newLoader(getBaseUrl()); return finalizerLoader.loadClass(FINALIZER_CLASS_NAME); } catch (Exception e) { logger.log(Level.WARNING, LOADING_ERROR, e); return null; } } /** Gets URL for base of path containing Finalizer.class. */ URL getBaseUrl() throws IOException { // Find URL pointing to Finalizer.class file. String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class"; URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath); if (finalizerUrl == null) { throw new FileNotFoundException(finalizerPath); } // Find URL pointing to base of class path. String urlString = finalizerUrl.toString(); if (!urlString.endsWith(finalizerPath)) { throw new IOException("Unsupported path style: " + urlString); } urlString = urlString.substring(0, urlString.length() - finalizerPath.length()); return new URL(finalizerUrl, urlString); } /** Creates a class loader with the given base URL as its classpath. */ URLClassLoader newLoader(URL base) { // We use the bootstrap class loader as the parent because Finalizer by design uses // only standard Java classes. That also means that FinalizableReferenceQueueTest // doesn't pick up the wrong version of the Finalizer class. return new URLClassLoader(new URL[] {base}, null); } } /** * Loads Finalizer directly using the current class loader. We won't be able to garbage collect * this class loader, but at least the world doesn't end. */ static class DirectLoader implements FinalizerLoader { @Override public Class loadFinalizer() { try { return Class.forName(FINALIZER_CLASS_NAME); } catch (ClassNotFoundException e) { throw new AssertionError(e); } } } /** Looks up Finalizer.startFinalizer(). */ static Method getStartFinalizer(Class finalizer) { try { return finalizer.getMethod( "startFinalizer", Class.class, ReferenceQueue.class, PhantomReference.class); } catch (NoSuchMethodException e) { throw new AssertionError(e); } } }