Force the immediate release of reference counted resources on AppEngine

to avoid executors backed by request-scoped threads from becoming
zombies
See
https://cloud.google.com/appengine/docs/java/javadoc/com/google/appengine/api/ThreadManager.html#currentRequestThreadFactory--
This commit is contained in:
Louis Ryan 2016-07-21 17:45:34 -07:00
parent 3a13aa5665
commit e276359f0e
1 changed files with 26 additions and 18 deletions

View File

@ -141,27 +141,35 @@ public final class SharedResourceHolder {
Preconditions.checkState(cached.refcount > 0, "Refcount has already reached zero"); Preconditions.checkState(cached.refcount > 0, "Refcount has already reached zero");
cached.refcount--; cached.refcount--;
if (cached.refcount == 0) { if (cached.refcount == 0) {
Preconditions.checkState(cached.destroyTask == null, "Destroy task already scheduled"); if (GrpcUtil.IS_RESTRICTED_APPENGINE) {
// Schedule a delayed task to destroy the resource. // AppEngine must immediately release shared resources, particularly executors
if (destroyer == null) { // which could retain request-scoped threads which become zombies after the request
destroyer = destroyerFactory.createScheduledExecutor(); // completes.
} resource.close(instance);
cached.destroyTask = destroyer.schedule(new LogExceptionRunnable(new Runnable() { instances.remove(resource);
@Override } else {
public void run() { Preconditions.checkState(cached.destroyTask == null, "Destroy task already scheduled");
synchronized (SharedResourceHolder.this) { // Schedule a delayed task to destroy the resource.
// Refcount may have gone up since the task was scheduled. Re-check it. if (destroyer == null) {
if (cached.refcount == 0) { destroyer = destroyerFactory.createScheduledExecutor();
resource.close(instance); }
instances.remove(resource); cached.destroyTask = destroyer.schedule(new LogExceptionRunnable(new Runnable() {
if (instances.isEmpty()) { @Override
destroyer.shutdown(); public void run() {
destroyer = null; 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);
}), DESTROY_DELAY_SECONDS, TimeUnit.SECONDS); }
} }
// Always returning null // Always returning null
return null; return null;