1 package com
.google
.appengine
.tools
.development
;
3 import com
.google
.appengine
.tools
.development
.AbstractContainerService
.LocalInitializationEnvironment
;
4 import com
.google
.appengine
.tools
.development
.InstanceStateHolder
.InstanceState
;
5 import com
.google
.apphosting
.api
.ApiProxy
;
6 import com
.google
.apphosting
.api
.ApiProxy
.Environment
;
7 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
;
8 import com
.google
.apphosting
.utils
.config
.WebModule
;
10 import org
.apache
.commons
.httpclient
.HttpClient
;
11 import org
.apache
.commons
.httpclient
.methods
.GetMethod
;
13 import java
.io
.InputStream
;
14 import java
.lang
.reflect
.Method
;
15 import java
.net
.MalformedURLException
;
16 import java
.util
.logging
.Level
;
17 import java
.util
.logging
.Logger
;
20 * Utility functions to access a server instance.
22 public class InstanceHelper
{
23 private static final String X_GOOGLE_DEV_APPSERVER_SKIPADMINCHECK
=
24 "X-Google-DevAppserver-SkipAdminCheck";
25 private static final int AH_REQUEST_INFINITE_TIMEOUT
= 0;
26 private static final int AH_REQUEST_DEFAULT_TIMEOUT
= 30 * 1000;
28 private static final Logger LOGGER
= Logger
.getLogger(InstanceHelper
.class.getName());
30 private final String serverOrBackendName
;
31 private final int instance
;
32 private final InstanceStateHolder instanceStateHolder
;
33 private final ContainerService containerService
;
36 * Constructs an {@link InstanceHelper}.
38 * @param serverOrBackendName For server instances the server name and for backend instances the
40 * @param instance The instance number or -1 for load balancing servers and automatic servers.
41 * @param instanceStateHolder Holder for the instances state.
42 * @param containerService The container service for the instance.
44 InstanceHelper(String serverOrBackendName
, int instance
, InstanceStateHolder instanceStateHolder
,
45 ContainerService containerService
) {
46 this.serverOrBackendName
= serverOrBackendName
;
47 this.instance
= instance
;
48 this.instanceStateHolder
= instanceStateHolder
;
49 this.containerService
= containerService
;
53 * Triggers an HTTP GET to /_ah/start in a background thread
55 * This method will keep on trying until it receives a non-error response
56 * code from the server.
58 * @param runOnSuccess {@link Runnable#run} invoked when the startup request succeeds.
60 void sendStartRequest(final Runnable runOnSuccess
) {
61 if (LOGGER
.isLoggable(Level
.FINER
)) {
62 LOGGER
.log(Level
.FINER
, "Entering send start request for serverOrBackendName="
63 + serverOrBackendName
+ " instance=" + instance
,
64 new Exception("Start sendStartRequest"));
68 throw new IllegalStateException("Attempt to send a start request to server/backend "
69 + serverOrBackendName
+ " instance " + instance
);
72 InstanceState unchangedState
=
73 instanceStateHolder
.testAndSetIf(InstanceState
.RUNNING_START_REQUEST
,
74 InstanceState
.SLEEPING
);
75 if (unchangedState
== null) {
76 Thread requestThread
= new Thread(new Runnable() {
79 sendStartRequest(AH_REQUEST_INFINITE_TIMEOUT
, runOnSuccess
);
82 requestThread
.setDaemon(true);
83 requestThread
.setName(
84 "BackendServersStartRequestThread." + instance
+ "." + serverOrBackendName
);
85 requestThread
.start();
86 } else if (unchangedState
!= InstanceState
.RUNNING_START_REQUEST
87 && unchangedState
!= InstanceState
.RUNNING
) {
88 InstanceStateHolder
.reportInvalidStateChange(serverOrBackendName
, instance
,
89 unchangedState
, InstanceState
.RUNNING_START_REQUEST
, InstanceState
.SLEEPING
);
94 * Triggers an HTTP GET to /_ah/start
96 * This method will keep on trying until it receives a non-error response
97 * code from the server.
99 * @param timeoutInMs Timeout in milliseconds, 0 indicates no timeout.
100 * @param runOnSuccess {@link Runnable#run} invoked when the startup request succeeds.
102 private void sendStartRequest(int timeoutInMs
, Runnable runOnSuccess
) {
104 String urlString
= String
.format("http://%s:%d/_ah/start", containerService
.getAddress(),
105 containerService
.getPort());
106 LOGGER
.finer("sending start request to: " + urlString
);
108 HttpClient httpClient
= new HttpClient();
109 httpClient
.getParams().setConnectionManagerTimeout(timeoutInMs
);
111 GetMethod request
= new GetMethod(urlString
);
112 request
.addRequestHeader(X_GOOGLE_DEV_APPSERVER_SKIPADMINCHECK
, "true");
114 int returnCode
= httpClient
.executeMethod(request
);
116 byte[] buffer
= new byte[1024];
117 InputStream in
= request
.getResponseBodyAsStream();
118 while (in
.read(buffer
) != -1) {
120 if ((returnCode
>= 200 && returnCode
< 300) || returnCode
== 404) {
122 String
.format("backend server %d.%s request to /_ah/start completed, code=%d",
123 instance
, serverOrBackendName
, returnCode
));
124 instanceStateHolder
.testAndSet(InstanceState
.RUNNING
,
125 InstanceState
.RUNNING_START_REQUEST
);
128 LOGGER
.warning("Start request to /_ah/start on server " + instance
+ "."
129 + serverOrBackendName
+ " failed (HTTP status code=" + returnCode
132 sendStartRequest(timeoutInMs
, runOnSuccess
);
135 request
.releaseConnection();
137 } catch (MalformedURLException e
) {
138 LOGGER
.severe(String
.format(
139 "Unable to send start request to server: %d.%s, " + "MalformedURLException: %s",
140 instance
, serverOrBackendName
, e
.getMessage()));
141 } catch (Exception e
) {
142 LOGGER
.warning(String
.format(
143 "Got exception while performing /_ah/start " + "request on server: %d.%s, %s: %s",
144 instance
, serverOrBackendName
, e
.getClass().getName(), e
.getMessage()));
149 * This method will trigger any shutdown hooks registered with the current
152 * Some class loader trickery is required to make sure that we get the
153 * {@link com.google.appengine.api.LifecycleManager} responsible for this
156 private void triggerLifecycleShutdownHookImpl() {
157 Environment prevEnvironment
= ApiProxy
.getCurrentEnvironment();
159 ClassLoader serverClassLoader
= containerService
.getAppContext().getClassLoader();
161 Class
<?
> lifeCycleManagerClass
=
162 Class
.forName("com.google.appengine.api.LifecycleManager", true, serverClassLoader
);
163 Method lifeCycleManagerGetter
= lifeCycleManagerClass
.getMethod("getInstance");
164 Object userThreadLifeCycleManager
= lifeCycleManagerGetter
.invoke(null, new Object
[0]);
166 Method beginShutdown
= lifeCycleManagerClass
.getMethod("beginShutdown", long.class);
167 AppEngineWebXml appEngineWebXml
= containerService
.getAppEngineWebXmlConfig();
168 String moduleName
= WebModule
.getModuleName(appEngineWebXml
);
169 ApiProxy
.setEnvironmentForCurrentThread(new LocalInitializationEnvironment(
170 appEngineWebXml
.getAppId(), moduleName
, appEngineWebXml
.getMajorVersionId(), instance
,
171 containerService
.getPort()));
174 beginShutdown
.invoke(userThreadLifeCycleManager
, AH_REQUEST_DEFAULT_TIMEOUT
);
175 } catch (Exception e
) {
177 String
.format("got exception when running shutdown hook on server %d.%s",
178 instance
, serverOrBackendName
));
181 } catch (Exception e
) {
183 String
.format("Exception during reflective call to "
184 + "LifecycleManager.beginShutdown on server %d.%s, got %s: %s", instance
,
185 serverOrBackendName
, e
.getClass().getName(), e
.getMessage()));
187 ApiProxy
.setEnvironmentForCurrentThread(prevEnvironment
);
192 * Shut down the server.
194 * Will trigger any shutdown hooks installed by the
195 * {@link com.google.appengine.api.LifecycleManager}
199 void shutdown() throws Exception
{
200 synchronized (instanceStateHolder
) {
201 if (instanceStateHolder
.test(InstanceState
.RUNNING
, InstanceState
.RUNNING_START_REQUEST
)) {
202 triggerLifecycleShutdownHookImpl();
204 containerService
.shutdown();
205 instanceStateHolder
.set(InstanceState
.SHUTDOWN
);