blob: d8da770273884331e1c514d242afaf5a0cb4682e [file] [log] [blame]
/*
* 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;
}
}
}