1 // Copyright 2011 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.tools
.development
;
5 import com
.google
.apphosting
.api
.ApiProxy
;
7 import java
.security
.AccessControlContext
;
8 import java
.security
.AccessController
;
9 import java
.security
.PrivilegedAction
;
10 import java
.util
.Date
;
12 import java
.util
.concurrent
.ThreadFactory
;
13 import java
.util
.logging
.Level
;
14 import java
.util
.logging
.Logger
;
17 * This {@link ThreadFactory} creates {@link Thread} objects that
18 * replicate the current request's environment, and are interrupted
19 * when the current request completes.
22 public class RequestThreadFactory
implements ThreadFactory
{
23 private static final Logger logger
= Logger
.getLogger(RequestThreadFactory
.class.getName());
25 private static final int THREAD_STARTUP_LATENCY_MS
= 20;
27 private static final int ONLINE_REQUEST_DEADLINE_MS
= 60000;
28 private static final int OFFLINE_REQUEST_DEADLINE_MS
= 600000;
31 public Thread
newThread(final Runnable runnable
) {
32 final boolean callerNativeMode
= DevSocketImplFactory
.isNativeSocketMode();
34 final AccessControlContext context
= AccessController
.getContext();
35 return AccessController
.doPrivileged(new PrivilegedAction
<Thread
>() {
38 final ApiProxy
.Environment environment
= ApiProxy
.getCurrentEnvironment();
39 DevSocketImplFactory
.setSocketNativeMode(callerNativeMode
);
41 Thread thread
= new Thread() {
43 * If the thread is started, install a
44 * {@link RequestEndListener} to interrupt the thread
45 * at the end of the request. We don't yet enforce
46 * request deadlines in the DevAppServer so we don't
47 * need to handle other interrupt cases yet.
52 Thread
.sleep(THREAD_STARTUP_LATENCY_MS
);
53 } catch (InterruptedException ex
) {
54 logger
.log(Level
.INFO
, "Interrupted while simulating thread startup latency", ex
);
55 Thread
.currentThread().interrupt();
58 final Thread thread
= this;
59 RequestEndListenerHelper
.register(new RequestEndListener() {
61 public void onRequestEnd(ApiProxy
.Environment environment
) {
62 if (thread
.isAlive()) {
63 logger
.info("Interrupting request thread: " + thread
);
65 logger
.info("Waiting up to 100ms for thread to complete: " + thread
);
68 } catch (InterruptedException ex
) {
69 logger
.info("Interrupted while waiting.");
71 if (thread
.isAlive()) {
72 logger
.info("Interrupting request thread again: " + thread
);
74 long remaining
= getRemainingDeadlineMillis(environment
);
75 logger
.info("Waiting up to " + remaining
+
76 " ms for thread to complete: " + thread
);
78 thread
.join(remaining
);
79 } catch (InterruptedException ex
) {
80 logger
.info("Interrupted while waiting.");
82 if (thread
.isAlive()) {
83 Throwable stack
= new Throwable();
84 stack
.setStackTrace(thread
.getStackTrace());
85 logger
.log(Level
.SEVERE
,
86 "Thread left running: " + thread
+ ". " +
87 "In production this will cause the request to fail.",
98 ApiProxy
.setEnvironmentForCurrentThread(environment
);
99 AccessController
.doPrivileged(new PrivilegedAction
<Object
>() {
101 public Object
run() {
108 System
.setProperty("devappserver-thread-" + thread
.getName(), "true");
114 private long getRemainingDeadlineMillis(ApiProxy
.Environment environment
) {
115 int requestTimeMillis
;
116 Map
<String
, Object
> attributes
= environment
.getAttributes();
117 Date startTime
= (Date
) attributes
.get(LocalEnvironment
.START_TIME_ATTR
);
118 if (startTime
!= null) {
119 startTime
= new Date();
121 Boolean offline
= (Boolean
) attributes
.get(ApiProxyLocalImpl
.IS_OFFLINE_REQUEST_KEY
);
122 if (offline
!= null && offline
) {
123 requestTimeMillis
= OFFLINE_REQUEST_DEADLINE_MS
;
125 requestTimeMillis
= ONLINE_REQUEST_DEADLINE_MS
;
127 return requestTimeMillis
- (System
.currentTimeMillis() - startTime
.getTime());