/* * Copyright 2014, Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package io.grpc.internal; import com.google.common.base.Preconditions; import java.util.IdentityHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.ThreadSafe; /** * A holder for shared resource singletons. * * <p>Components like client channels and servers need certain resources, e.g. a thread pool, to * run. If the user has not provided such resources, these components will use a default one, which * is shared as a static resource. This class holds these default resources and manages their * life-cycles. * * <p>A resource is identified by the reference of a {@link Resource} object, which is typically a * singleton, provided to the get() and release() methods. Each Resource object (not its class) maps * to an object cached in the holder. * * <p>Resources are ref-counted and shut down after a delay when the ref-count reaches zero. */ @ThreadSafe public final class SharedResourceHolder { static final long DESTROY_DELAY_SECONDS = 1; // The sole holder instance. private static final SharedResourceHolder holder = new SharedResourceHolder( new ScheduledExecutorFactory() { @Override public ScheduledExecutorService createScheduledExecutor() { return Executors.newSingleThreadScheduledExecutor( GrpcUtil.getThreadFactory("grpc-shared-destroyer-%d", true)); } }); private final IdentityHashMap<Resource<?>, Instance> instances = new IdentityHashMap<Resource<?>, Instance>(); private final ScheduledExecutorFactory destroyerFactory; private ScheduledExecutorService destroyer; // Visible to tests that would need to create instances of the holder. SharedResourceHolder(ScheduledExecutorFactory destroyerFactory) { this.destroyerFactory = destroyerFactory; } /** * Try to get an existing instance of the given resource. If an instance does not exist, create a * new one with the given factory. * * @param resource the singleton object that identifies the requested static resource */ public static <T> T get(Resource<T> resource) { return holder.getInternal(resource); } /** * Releases an instance of the given resource. * * <p>The instance must have been obtained from {@link #get(Resource)}. Otherwise will throw * IllegalArgumentException. * * <p>Caller must not release a reference more than once. It's advisory that you clear the * reference to the instance with the null returned by this method. * * @param resource the singleton Resource object that identifies the released static resource * @param instance the released static resource * * @return a null which the caller can use to clear the reference to that instance. */ public static <T> T release(final Resource<T> resource, final T instance) { return holder.releaseInternal(resource, instance); } /** * Visible to unit tests. * * @see #get(Resource) */ @SuppressWarnings("unchecked") synchronized <T> T getInternal(Resource<T> resource) { Instance instance = instances.get(resource); if (instance == null) { instance = new Instance(resource.create()); instances.put(resource, instance); } if (instance.destroyTask != null) { instance.destroyTask.cancel(false); instance.destroyTask = null; } instance.refcount++; return (T) instance.payload; } /** * Visible to unit tests. */ synchronized <T> T releaseInternal(final Resource<T> resource, final T instance) { final Instance cached = instances.get(resource); if (cached == null) { throw new IllegalArgumentException("No cached instance found for " + resource); } Preconditions.checkArgument(instance == cached.payload, "Releasing the wrong instance"); Preconditions.checkState(cached.refcount > 0, "Refcount has already reached zero"); cached.refcount--; if (cached.refcount == 0) { if (GrpcUtil.IS_RESTRICTED_APPENGINE) { // AppEngine must immediately release shared resources, particularly executors // which could retain request-scoped threads which become zombies after the request // completes. resource.close(instance); instances.remove(resource); } else { Preconditions.checkState(cached.destroyTask == null, "Destroy task already scheduled"); // Schedule a delayed task to destroy the resource. if (destroyer == null) { destroyer = destroyerFactory.createScheduledExecutor(); } cached.destroyTask = destroyer.schedule(new LogExceptionRunnable(new Runnable() { @Override public void run() { synchronized (SharedResourceHolder.this) { // Refcount may have gone up since the task was scheduled. Re-check it. if (cached.refcount == 0) { resource.close(instance); instances.remove(resource); if (instances.isEmpty()) { destroyer.shutdown(); destroyer = null; } } } } }), DESTROY_DELAY_SECONDS, TimeUnit.SECONDS); } } // Always returning null return null; } /** * Defines a resource, and the way to create and destroy instances of it. */ public interface Resource<T> { /** * Create a new instance of the resource. */ T create(); /** * Destroy the given instance. */ void close(T instance); } interface ScheduledExecutorFactory { ScheduledExecutorService createScheduledExecutor(); } private static class Instance { final Object payload; int refcount; ScheduledFuture<?> destroyTask; Instance(Object payload) { this.payload = payload; } } }