App Engine SDK 1.8.4 release.
[gae.git] / java / src / main / com / google / appengine / tools / development / InstanceHelper.java
blobf9cfe41a01ac1b8ded8e2189d8d42dde1bf1f1af
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;
19 /**
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;
35 /**
36 * Constructs an {@link InstanceHelper}.
38 * @param serverOrBackendName For server instances the server name and for backend instances the
39 * backend name.
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;
52 /**
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"));
67 if (instance < 0) {
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() {
77 @Override
78 public void run() {
79 sendStartRequest(AH_REQUEST_INFINITE_TIMEOUT, runOnSuccess);
81 });
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);
93 /**
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) {
103 try {
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");
113 try {
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) {
121 LOGGER.fine(
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);
126 runOnSuccess.run();
127 } else {
128 LOGGER.warning("Start request to /_ah/start on server " + instance + "."
129 + serverOrBackendName + " failed (HTTP status code=" + returnCode
130 + "). Retrying...");
131 Thread.sleep(1000);
132 sendStartRequest(timeoutInMs, runOnSuccess);
134 } finally {
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
150 * server.
152 * Some class loader trickery is required to make sure that we get the
153 * {@link com.google.appengine.api.LifecycleManager} responsible for this
154 * server instance.
156 private void triggerLifecycleShutdownHookImpl() {
157 Environment prevEnvironment = ApiProxy.getCurrentEnvironment();
158 try {
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()));
173 try {
174 beginShutdown.invoke(userThreadLifeCycleManager, AH_REQUEST_DEFAULT_TIMEOUT);
175 } catch (Exception e) {
176 LOGGER.warning(
177 String.format("got exception when running shutdown hook on server %d.%s",
178 instance, serverOrBackendName));
179 e.printStackTrace();
181 } catch (Exception e) {
182 LOGGER.severe(
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()));
186 } finally {
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}
197 * @throws Exception
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);