/**
* Copyright 2015 Google Inc. All Rights Reserved.
*
* 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 com.google.apphosting.vmruntime;
import static com.google.appengine.repackaged.com.google.common.base.Preconditions.checkArgument;
import static com.google.appengine.repackaged.com.google.common.base.Preconditions.checkState;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableList;
import com.google.appengine.repackaged.com.google.common.collect.Lists;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Thread factory creating threads with a request specific thread local environment.
*
*/
public class VmRequestThreadFactory implements ThreadFactory {
private static final Logger logger =
Logger.getLogger(VmRequestThreadFactory.class.getCanonicalName());
private final Environment requestEnvironment;
// Accesses to createdThreads must be synchronized on "mutex".
private final Object mutex;
private final List<Thread> createdThreads;
private volatile boolean allowNewRequestThreadCreation;
/**
* Create a new VmRequestThreadFactory.
*
* @param requestEnvironment The request environment to install on each thread.
*/
public VmRequestThreadFactory(Environment requestEnvironment) {
this.mutex = new Object();
this.requestEnvironment = requestEnvironment;
this.createdThreads = Lists.newLinkedList();
this.allowNewRequestThreadCreation = true;
}
/**
* Create a new {@link Thread} that executes {@code runnable} for the duration of the current
* request. This thread will be interrupted at the end of the current request.
*
* @param runnable The object whose run method is invoked when this thread is started. If null,
* this classes run method does nothing.
*
* @throws ApiProxy.ApiProxyException If called outside of a running request.
* @throws IllegalStateException If called after the request thread stops.
*/
@Override
public Thread newThread(final Runnable runnable) {
checkState(requestEnvironment != null,
"Request threads can only be created within the context of a running request.");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
if (runnable == null) {
return;
}
checkState(allowNewRequestThreadCreation,
"Cannot start new threads after the request thread stops.");
ApiProxy.setEnvironmentForCurrentThread(requestEnvironment);
runnable.run();
}
});
checkState(
allowNewRequestThreadCreation, "Cannot create new threads after the request thread stops.");
synchronized (mutex) {
createdThreads.add(thread);
}
return thread;
}
/**
* Returns an immutable copy of the current request thread list.
*/
public List<Thread> getRequestThreads() {
synchronized (mutex) {
return ImmutableList.copyOf(createdThreads);
}
}
/**
* Interrupt all request threads created by the current request.
*/
public void interruptRequestThreads() {
allowNewRequestThreadCreation = false;
synchronized (mutex) {
for (Thread thread : createdThreads) {
if (thread.isAlive()) {
logger.warning(String.format(
"Request thread %s is still alive, forcing interrupt.", thread.getName()));
}
thread.interrupt();
}
}
}
/**
* Waits at most {@code millis} milliseconds for all threads created by this factory to finish.
*
* @param millis The time to wait in milliseconds.
*
* @return True if all threads created by this factory joined successfully, false otherwise.
*
* @throws IllegalArgumentException if the value of {@code millis} is zero or negative
*/
public boolean join(long millis) {
checkArgument(millis >= 0, "Timeout value is negative.");
long beDoneBy = System.currentTimeMillis() + millis;
try {
synchronized (mutex) {
for (Thread thread : createdThreads) {
long waitTimeLeft = System.currentTimeMillis() - beDoneBy;
if (waitTimeLeft <= 0) {
return false;
}
thread.join(waitTimeLeft);
}
}
return true;
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Interrupted while waiting for request threads to complete.", e);
}
return false;
}
}