1 // Copyright 2011 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.tools
.development
;
5 import com
.google
.appengine
.api
.backends
.dev
.LocalServerController
;
6 import com
.google
.appengine
.tools
.development
.AbstractContainerService
.LocalInitializationEnvironment
;
7 import com
.google
.apphosting
.api
.ApiProxy
;
8 import com
.google
.apphosting
.api
.ApiProxy
.Environment
;
9 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
;
10 import com
.google
.apphosting
.utils
.config
.BackendsXml
;
11 import com
.google
.common
.collect
.ImmutableMap
;
12 import com
.google
.common
.collect
.Maps
;
14 import org
.apache
.commons
.httpclient
.HttpClient
;
15 import org
.apache
.commons
.httpclient
.methods
.GetMethod
;
18 import java
.io
.IOException
;
19 import java
.io
.InputStream
;
20 import java
.lang
.reflect
.Method
;
21 import java
.net
.MalformedURLException
;
22 import java
.util
.HashMap
;
23 import java
.util
.List
;
25 import java
.util
.Map
.Entry
;
26 import java
.util
.TreeMap
;
27 import java
.util
.concurrent
.Semaphore
;
28 import java
.util
.concurrent
.TimeUnit
;
29 import java
.util
.logging
.Logger
;
31 import javax
.servlet
.ServletException
;
32 import javax
.servlet
.http
.HttpServletRequest
;
33 import javax
.servlet
.http
.HttpServletResponse
;
36 * Controls backend servers configured in appengine-web.xml. Each server is
37 * started on a separate port. All servers run the same code as the main app.
41 public abstract class AbstractBackendServers
implements BackendContainer
, LocalServerController
{
42 private static final String X_GOOGLE_DEV_APPSERVER_SKIPADMINCHECK
=
43 "X-Google-DevAppserver-SkipAdminCheck";
44 public static final String SYSTEM_PROPERTY_STATIC_PORT_NUM_PREFIX
=
45 "com.google.appengine.devappserver.";
47 private static final int AH_REQUEST_DEFAULT_TIMEOUT
= 30 * 1000;
48 private static final int AH_REQUEST_INFINITE_TIMEOUT
= 0;
50 private static final Integer DEFAULT_INSTANCES
= 1;
51 private static final String DEFAULT_INSTANCE_CLASS
= "B1";
52 private static final Integer DEFAULT_MAX_CONCURRENT_REQUESTS
= 10;
54 private enum BackendServerState
{
55 INITIALIZING
, SLEEPING
, RUNNING_START_REQUEST
, RUNNING
, STOPPING
, STOPPED
, SHUTDOWN
;
58 private static final int MAX_PENDING_QUEUE_LENGTH
= 20;
59 private static final int MAX_PENDING_QUEUE_TIME_MS
= 10 * 1000;
60 private static final int MAX_START_QUEUE_TIME_MS
= 30 * 1000;
62 private String address
;
64 private File externalResourceDir
;
65 private File webXmlLocation
;
66 private File appEngineWebXmlLocation
;
67 private Map
<String
, Object
> containerConfigProperties
;
68 private Map
<AbstractBackendServers
.ServerInstanceEntry
, ServerWrapper
> backendServers
=
69 ImmutableMap
.copyOf(new HashMap
<ServerInstanceEntry
, ServerWrapper
>());
70 private Map
<String
, String
> portMapping
=
71 ImmutableMap
.copyOf(new HashMap
<String
, String
>());
72 protected Logger logger
= Logger
.getLogger(AbstractBackendServers
.class.getName());
74 private Map
<String
, String
> serviceProperties
= new HashMap
<String
, String
>();
76 private DevAppServer devAppServer
;
78 public void init(File appDir
, File externalResourceDir
, File webXmlLocation
,
79 File appEngineWebXmlLocation
, String address
, Map
<String
, Object
> containerConfigProperties
,
80 DevAppServer devAppServer
) {
82 this.externalResourceDir
= externalResourceDir
;
83 this.webXmlLocation
= webXmlLocation
;
84 this.appEngineWebXmlLocation
= appEngineWebXmlLocation
;
85 this.address
= address
;
86 this.containerConfigProperties
= containerConfigProperties
;
87 this.devAppServer
= devAppServer
;
91 public void setServiceProperties(Map
<String
, String
> properties
) {
92 this.serviceProperties
= properties
;
96 public void shutdownAll() throws Exception
{
97 for (ServerWrapper server
: backendServers
.values()) {
98 logger
.finer("server shutdown: " + server
);
101 backendServers
= ImmutableMap
.copyOf(new HashMap
<ServerInstanceEntry
, ServerWrapper
>());
105 public TreeMap
<String
, BackendStateInfo
> getBackendState(String requestHostName
) {
106 TreeMap
<String
, BackendStateInfo
> serverInfoMap
= new TreeMap
<String
, BackendStateInfo
>();
107 for (ServerWrapper serverWrapper
: backendServers
.values()) {
108 String name
= serverWrapper
.serverEntry
.getName();
110 String listenAddress
;
111 if (requestHostName
== null) {
112 listenAddress
= portMapping
.get(serverWrapper
.getDnsPrefix());
114 listenAddress
= requestHostName
+ ":" + serverWrapper
.port
;
117 BackendStateInfo ssi
= serverInfoMap
.get(name
);
119 ssi
= new BackendStateInfo(serverWrapper
.serverEntry
);
120 serverInfoMap
.put(name
, ssi
);
122 if (serverWrapper
.isLoadBalanceServer()) {
123 ssi
.setState(serverWrapper
.serverState
.name().toLowerCase());
124 ssi
.setAddress(listenAddress
);
126 ssi
.add(new InstanceStateInfo(serverWrapper
.serverInstance
, listenAddress
,
127 serverWrapper
.serverState
.name().toLowerCase()));
130 return serverInfoMap
;
134 public synchronized void startBackend(String serverToStart
) throws IllegalStateException
{
135 if (!checkServerExists(serverToStart
)) {
136 String message
= String
.format("Tried to start unknown server %s", serverToStart
);
137 logger
.warning(message
);
138 throw new IllegalStateException(message
);
140 for (ServerWrapper server
: backendServers
.values()) {
141 if (server
.getName().equals(serverToStart
)) {
142 if (server
.getState() != BackendServerState
.STOPPED
) {
145 if (server
.isLoadBalanceServer()) {
146 server
.compareAndSetServerState(
147 BackendServerState
.RUNNING
, BackendServerState
.STOPPED
);
150 server
.compareAndSetServerState(
151 BackendServerState
.SLEEPING
, BackendServerState
.STOPPED
);
152 server
.sendStartRequest();
158 public synchronized void stopBackend(String serverToStop
) throws Exception
{
159 if (!checkServerExists(serverToStop
)) {
160 String message
= String
.format("Tried to stop unknown server %s", serverToStop
);
161 logger
.warning(message
);
162 throw new IllegalStateException(message
);
164 for (ServerWrapper server
: backendServers
.values()) {
165 if (server
.getName().equals(serverToStop
)) {
166 if (server
.getState() == BackendServerState
.STOPPED
) {
169 if (server
.isLoadBalanceServer()) {
170 server
.compareAndSetServerState(
171 BackendServerState
.STOPPED
, BackendServerState
.RUNNING
);
174 logger
.fine("Stopping server: " + server
.getDnsPrefix());
176 server
.startup(true);
182 public void startupAll(BackendsXml backendsXml
, ApiProxyLocal local
) throws Exception
{
183 if (backendsXml
== null) {
184 logger
.fine("Got null backendsXml config.");
187 List
<BackendsXml
.Entry
> servers
= backendsXml
.getBackends();
188 if (servers
.size() == 0) {
189 logger
.fine("No backends configured.");
193 if (backendServers
.size() != 0) {
194 throw new Exception("Tried to start backendservers but some are already running.");
196 logger
.finer("Found " + servers
.size() + " configured backends.");
198 Map
<AbstractBackendServers
.ServerInstanceEntry
, ServerWrapper
> serverMap
= Maps
.newHashMap();
199 for (BackendsXml
.Entry entry
: servers
) {
200 entry
= resolveDefaults(entry
);
202 for (int serverInstance
= -1; serverInstance
< entry
.getInstances(); serverInstance
++) {
203 int port
= checkForStaticPort(entry
.getName(), serverInstance
);
204 ServerWrapper serverWrapper
=
205 new ServerWrapper(ContainerUtils
.loadContainer(), entry
, serverInstance
, port
);
206 serverMap
.put(new ServerInstanceEntry(entry
.getName(), serverInstance
), serverWrapper
);
209 this.backendServers
= ImmutableMap
.copyOf(serverMap
);
211 String prettyAddress
= address
;
212 if ("0.0.0.0".equals(address
)) {
213 prettyAddress
= "127.0.0.1";
216 Map
<String
, String
> portMap
= Maps
.newHashMap();
217 for (ServerWrapper serverWrapper
: backendServers
.values()) {
219 "starting server: " + serverWrapper
.serverInstance
+ "." + serverWrapper
.getName()
220 + " on " + address
+ ":" + serverWrapper
.port
);
221 ApiProxy
.Delegate configured
= ApiProxy
.getDelegate();
223 ApiProxy
.setDelegate(local
);
224 serverWrapper
.startup(false);
226 ApiProxy
.setDelegate(configured
);
229 portMap
.put(serverWrapper
.getDnsPrefix(), prettyAddress
+ ":" + serverWrapper
.port
);
231 this.portMapping
= ImmutableMap
.copyOf(portMap
);;
233 for (ServerWrapper serverWrapper
: backendServers
.values()) {
234 if (serverWrapper
.isLoadBalanceServer()) {
237 serverWrapper
.sendStartRequest();
241 private BackendsXml
.Entry
resolveDefaults(BackendsXml
.Entry entry
) {
242 return new BackendsXml
.Entry(
244 entry
.getInstances() == null ? DEFAULT_INSTANCES
: entry
.getInstances(),
245 entry
.getInstanceClass() == null ? DEFAULT_INSTANCE_CLASS
: entry
.getInstanceClass(),
246 entry
.getMaxConcurrentRequests() == null ? DEFAULT_MAX_CONCURRENT_REQUESTS
:
247 entry
.getMaxConcurrentRequests(),
249 entry
.getState() == null ? BackendsXml
.State
.STOP
: entry
.getState());
253 * Forward a request to a specific server and instance. This will call the
254 * specified instance request dispatcher so the request is handled in the
255 * right server context.
257 protected abstract void forwardToServer(String requestedServer
, int instance
,
258 HttpServletRequest hrequest
,
259 HttpServletResponse hresponse
) throws IOException
, ServletException
;
262 * This method guards access to servers to limit the number of concurrent
263 * requests. Each request running on a server must acquire a serving permit.
264 * If no permits are available a 500 response should be sent.
266 * @param serverName The server for which to acquire a permit.
267 * @param instanceNumber The server instance for which to acquire a permit.
268 * @param allowQueueOnBackends If set to false the method will return
269 * instantly, if set to true (and the specified server allows pending
270 * queues) this method can block for up to 10 s waiting for a serving
271 * permit to become available.
272 * @return true if a permit was acquired, false otherwise
274 public boolean acquireServingPermit(
275 String serverName
, int instanceNumber
, boolean allowQueueOnBackends
) {
277 String
.format("trying to get serving permit for server %d.%s", instanceNumber
, serverName
));
279 ServerWrapper server
= getServerWrapper(serverName
, instanceNumber
);
280 int maxQueueTime
= 0;
282 synchronized (server
) {
283 if (!server
.acceptsConnections()) {
284 logger
.finest(server
+ ": got request but server is not in a serving state");
287 if (server
.getApproximateQueueLength() > MAX_PENDING_QUEUE_LENGTH
) {
288 logger
.finest(server
+ ": server queue is full");
291 if (server
.getState() == BackendServerState
.SLEEPING
) {
292 logger
.finest(server
+ ": waking up sleeping server");
293 server
.sendStartRequest();
296 if (server
.getState() == BackendServerState
.RUNNING_START_REQUEST
) {
297 maxQueueTime
= MAX_START_QUEUE_TIME_MS
;
298 } else if (allowQueueOnBackends
&& server
.getMaxPendingQueueSize() > 0) {
299 maxQueueTime
= MAX_PENDING_QUEUE_TIME_MS
;
302 boolean gotPermit
= server
.acquireServingPermit(maxQueueTime
);
303 logger
.finest(server
+ ": tried to get server permit, timeout=" + maxQueueTime
+ " success="
306 } catch (InterruptedException e
) {
308 instanceNumber
+ "." + serverName
+ ": got interrupted while waiting for serving permit");
314 * Reserves an instance for this request. For workers this method will return
315 * -1 if no free instances are available. For backends this method will assign
316 * this request to the instance with the shortest queue and block until that
317 * instance is ready to serve the request.
319 * @param requestedServer Name of the server the request is to.
320 * @return the instance id of an available server instance, or -1 if no
321 * instance is available.
323 public int getAndReserveFreeInstance(String requestedServer
) {
324 logger
.finest("trying to get serving permit for server " + requestedServer
);
326 ServerWrapper server
= getServerWrapper(requestedServer
, -1);
327 if (server
== null) {
330 if (!server
.acceptsConnections()) {
333 int instanceNum
= server
.getInstances();
334 for (int i
= 0; i
< instanceNum
; i
++) {
335 if (acquireServingPermit(requestedServer
, i
, false)) {
339 if (server
.getMaxPendingQueueSize() > 0) {
340 return addToShortestInstanceQueue(requestedServer
);
342 logger
.finest("no servers free");
348 * Will add this request to the queue of the instance with the approximate
349 * shortest queue. This method will block for up to 10 seconds until a permit
352 * @param requestedServer the server name
353 * @return the instance where the serving permit was reserved, or -1 if all
354 * instance queues are full
356 int addToShortestInstanceQueue(String requestedServer
) {
357 logger
.finest(requestedServer
+ ": no instances free, trying to find a queue");
358 int shortestQueue
= MAX_PENDING_QUEUE_LENGTH
;
359 ServerWrapper instanceWithShortestQueue
= null;
360 for (ServerWrapper server
: backendServers
.values()) {
361 if (!server
.acceptsConnections()) {
364 int serverQueue
= server
.getApproximateQueueLength();
365 if (shortestQueue
> serverQueue
) {
366 instanceWithShortestQueue
= server
;
367 shortestQueue
= serverQueue
;
372 if (shortestQueue
< MAX_PENDING_QUEUE_LENGTH
) {
373 logger
.finest("adding request to queue on instance: " + instanceWithShortestQueue
);
374 if (instanceWithShortestQueue
.acquireServingPermit(MAX_PENDING_QUEUE_TIME_MS
)) {
375 logger
.finest("ready to serve request on instance: " + instanceWithShortestQueue
);
376 return instanceWithShortestQueue
.serverInstance
;
379 } catch (InterruptedException e
) {
380 logger
.finer("interupted while queued at server " + instanceWithShortestQueue
);
386 * Method for returning a serving permit after a request has completed.
388 * @param serverName The server name
389 * @param instance The server instance
391 public void returnServingPermit(String serverName
, int instance
) {
392 ServerWrapper server
= getServerWrapper(serverName
, instance
);
393 server
.releaseServingPermit();
397 * Verifies if a specific server/instance is configured.
399 * @param serverName The server name
400 * @param instance The server instance
401 * @return true if the server/instance is configured, false otherwise.
403 public boolean checkInstanceExists(String serverName
, int instance
) {
404 return getServerWrapper(serverName
, instance
) != null;
408 * Verifies if a specific server is configured.
410 * @param serverName The server name
411 * @return true if the server is configured, false otherwise.
413 public boolean checkServerExists(String serverName
) {
414 return checkInstanceExists(serverName
, -1);
418 * Verifies if a specific server is stopped.
420 * @param serverName The server name
421 * @return true if the server is stopped, false otherwise.
423 public boolean checkServerStopped(String serverName
) {
424 return checkInstanceStopped(serverName
, -1);
428 * Verifies if a specific server/instance is stopped.
430 * @param serverName The server name
431 * @param instance The server instance
432 * @return true if the server/instance is stopped, false otherwise.
434 public boolean checkInstanceStopped(String serverName
, int instance
) {
435 return !getServerWrapper(serverName
, instance
).acceptsConnections();
439 * Allows the servers API to get the current mapping from server and instance
440 * to listening ports.
442 * @return The port map
444 public Map
<String
, String
> getPortMapping() {
445 return this.portMapping
;
449 * Returns the server instance serving on a specific local port
451 * @param port the local tcp port that received the request
452 * @return the server instance, or -1 if no server instance is running on that
455 public int getServerInstanceFromPort(int port
) {
456 ServerWrapper server
= getServerWrapperFromPort(port
);
457 if (server
!= null) {
458 return server
.serverInstance
;
465 * Returns the server serving on a specific local port
467 * @param port the local tcp port that received the request
468 * @return the server name, or null if no server instance is running on that
471 public String
getServerNameFromPort(int port
) {
472 ServerWrapper server
= getServerWrapperFromPort(port
);
473 if (server
!= null) {
474 return server
.getName();
481 * Convenience method for getting the ServerWrapper running on a specific port
483 private ServerWrapper
getServerWrapperFromPort(int port
) {
484 for (Entry
<ServerInstanceEntry
, ServerWrapper
> entry
: backendServers
.entrySet()) {
485 if (entry
.getValue().port
== port
) {
486 return entry
.getValue();
493 * Convenience method for getting the ServerWrapper for a specific
496 protected ServerWrapper
getServerWrapper(String serverName
, int instanceNumber
) {
497 return backendServers
.get(new ServerInstanceEntry(serverName
, instanceNumber
));
501 * Verifies if the specific port is statically configured, if not it will
502 * return 0 which instructs jetty to pick the port
504 * Ports can be statically configured by a system property at
505 * com.google.appengine.server.<server-name>.port for the server and
506 * com.google.appengine.server.<server-name>.<instance-id>.port for individual
509 * @param server the name of the configured server
510 * @param instance the instance number to configure
511 * @return the statically configured port, or 0 if none is configured
513 private int checkForStaticPort(String server
, int instance
) {
514 StringBuilder key
= new StringBuilder();
515 key
.append(SYSTEM_PROPERTY_STATIC_PORT_NUM_PREFIX
);
518 key
.append("." + instance
);
521 String configuredPort
= serviceProperties
.get(key
.toString());
522 if (configuredPort
!= null) {
523 return Integer
.parseInt(configuredPort
);
530 * Class that allows the key in the server map to be the
531 * (servername,instanceid) tuple. Overrides equals() and hashcode() to
532 * function as a hashtable key
535 static class ServerInstanceEntry
{
536 private final int instanceNumber
;
537 private final String serverName
;
541 * @param instanceNumber
543 public ServerInstanceEntry(String serverName
, int instanceNumber
) {
544 this.serverName
= serverName
;
545 this.instanceNumber
= instanceNumber
;
549 public boolean equals(Object o
) {
550 if (!(o
instanceof ServerInstanceEntry
)) {
554 ServerInstanceEntry that
= (ServerInstanceEntry
) o
;
555 if (this.serverName
!= null) {
556 if (!this.serverName
.equals(that
.serverName
)) {
560 if (that
.serverName
!= null) {
565 if (this.instanceNumber
!= that
.instanceNumber
) {
572 public int hashCode() {
574 hash
= 31 * hash
+ instanceNumber
;
575 if (serverName
!= null) {
576 hash
= 31 * hash
+ serverName
.hashCode();
582 public String
toString() {
583 return instanceNumber
+ "." + serverName
;
588 * Wraps a container service and contains extra information such as the
589 * instanceid of the current container as well as the port number it is
592 protected class ServerWrapper
{
594 private final ContainerService container
;
595 private final int serverInstance
;
597 private final BackendsXml
.Entry serverEntry
;
599 private BackendServerState serverState
= BackendServerState
.SHUTDOWN
;
601 private final Semaphore servingQueue
= new Semaphore(0, true);
603 public ServerWrapper(ContainerService containerService
,
604 BackendsXml
.Entry serverEntry
,
605 int instance
, int port
) {
606 this.container
= containerService
;
607 this.serverEntry
= serverEntry
;
608 this.serverInstance
= instance
;
610 containerService
.setEnvironmentVariableMismatchSeverity(
611 ContainerService
.EnvironmentVariableMismatchSeverity
.IGNORE
);
615 * Triggers an HTTP GET to /_ah/start
617 * This method will keep on trying until it receives a non-error response
618 * code from the server.
620 * @param timeoutInMs Timeout in milliseconds, 0 indicates no timeout.
623 private void sendStartRequest(int timeoutInMs
) {
626 String
.format("http://%s:%d/_ah/start", address
, ServerWrapper
.this.port
);
627 logger
.finer("sending start request to: " + urlString
);
629 HttpClient httpClient
= new HttpClient();
630 httpClient
.getParams().setConnectionManagerTimeout(timeoutInMs
);
632 GetMethod request
= new GetMethod(urlString
);
633 request
.addRequestHeader(X_GOOGLE_DEV_APPSERVER_SKIPADMINCHECK
, "true");
635 int returnCode
= httpClient
.executeMethod(request
);
637 byte[] buffer
= new byte[1024];
638 InputStream in
= request
.getResponseBodyAsStream();
639 while (in
.read(buffer
) != -1) {
641 if ((returnCode
>= 200 && returnCode
< 300) || returnCode
== 404) {
643 String
.format("backend server %d.%s request to /_ah/start completed, code=%d",
644 serverInstance
, serverEntry
.getName(), returnCode
));
645 compareAndSetServerState(
646 BackendServerState
.RUNNING
, BackendServerState
.RUNNING_START_REQUEST
);
647 servingQueue
.release(serverEntry
.getMaxConcurrentRequests());
649 logger
.warning("Start request to /_ah/start on server " + serverInstance
+ "."
650 + serverEntry
.getName() + " failed (HTTP status code=" + returnCode
653 sendStartRequest(timeoutInMs
);
656 request
.releaseConnection();
658 } catch (MalformedURLException e
) {
659 logger
.severe(String
.format(
660 "Unable to send start request to server: %d.%s, " + "MalformedURLException: %s",
661 serverInstance
, serverEntry
.getName(), e
.getMessage()));
662 } catch (Exception e
) {
663 logger
.warning(String
.format(
664 "Got exception while performing /_ah/start " + "request on server: %d.%s, %s: %s",
665 serverInstance
, serverEntry
.getName(), e
.getClass().getName(), e
.getMessage()));
670 * Shut down the server.
672 * Will trigger any shutdown hooks installed by the
673 * {@link com.google.appengine.api.LifecycleManager}
677 public void shutdown() throws Exception
{
678 synchronized (ServerWrapper
.this) {
679 if (serverState
== BackendServerState
.RUNNING
680 || serverState
== BackendServerState
.RUNNING_START_REQUEST
) {
681 triggerLifecycleShutdownHook();
683 getContainer().shutdown();
684 serverState
= BackendServerState
.SHUTDOWN
;
688 void startup(boolean setStateToStopped
) throws Exception
{
689 compareAndSetServerState(BackendServerState
.INITIALIZING
, BackendServerState
.SHUTDOWN
);
691 getContainer().configure(ContainerUtils
.getServerInfo(),
695 appEngineWebXmlLocation
,
698 containerConfigProperties
,
700 getContainer().startup();
702 this.port
= getContainer().getPort();
703 if (setStateToStopped
) {
704 compareAndSetServerState(BackendServerState
.STOPPED
, BackendServerState
.INITIALIZING
);
707 "server: " + serverInstance
+ "." + serverEntry
.getName() + " is running on port "
709 if (isLoadBalanceServer()) {
710 compareAndSetServerState(
711 BackendServerState
.RUNNING
, BackendServerState
.INITIALIZING
);
713 compareAndSetServerState(
714 BackendServerState
.SLEEPING
, BackendServerState
.INITIALIZING
);
719 void sendStartRequest() {
720 compareAndSetServerState(
721 BackendServerState
.RUNNING_START_REQUEST
, BackendServerState
.SLEEPING
);
722 if (serverInstance
>= 0) {
723 Thread requestThread
= new Thread(new Runnable() {
726 sendStartRequest(AH_REQUEST_INFINITE_TIMEOUT
);
729 requestThread
.setDaemon(true);
730 requestThread
.setName(
731 "BackendServersStartRequestThread." + serverInstance
+ "." + serverEntry
.getName());
732 requestThread
.start();
737 * This method will trigger any shutdown hooks registered with the current
740 * Some class loader trickery is required to make sure that we get the
741 * {@link com.google.appengine.api.LifecycleManager} responsible for this
744 private void triggerLifecycleShutdownHook() {
745 Environment prevEnvironment
= ApiProxy
.getCurrentEnvironment();
747 ClassLoader serverClassLoader
= getContainer().getAppContext().getClassLoader();
749 Class
<?
> lifeCycleManagerClass
=
750 Class
.forName("com.google.appengine.api.LifecycleManager", true, serverClassLoader
);
751 Method lifeCycleManagerGetter
= lifeCycleManagerClass
.getMethod("getInstance");
752 Object userThreadLifeCycleManager
= lifeCycleManagerGetter
.invoke(null, new Object
[0]);
754 Method beginShutdown
= lifeCycleManagerClass
.getMethod("beginShutdown", long.class);
755 AppEngineWebXml appEngineWebXml
= getContainer().getAppEngineWebXmlConfig();
756 ApiProxy
.setEnvironmentForCurrentThread(new LocalInitializationEnvironment(
757 appEngineWebXml
.getAppId(), appEngineWebXml
.getMajorVersionId()));
760 beginShutdown
.invoke(userThreadLifeCycleManager
, AH_REQUEST_DEFAULT_TIMEOUT
);
761 } catch (Exception e
) {
763 String
.format("got exception when running shutdown hook on server %d.%s",
764 serverInstance
, serverEntry
.getName()));
767 } catch (Exception e
) {
769 String
.format("Exception during reflective call to "
770 + "LifecycleManager.beginShutdown on server %d.%s, got %s: %s", serverInstance
,
771 serverEntry
.getName(), e
.getClass().getName(), e
.getMessage()));
773 ApiProxy
.setEnvironmentForCurrentThread(prevEnvironment
);
778 * Checks if the server is in a state where it can accept incoming requests.
780 * @return true if the server can accept incoming requests, false otherwise.
782 boolean acceptsConnections() {
783 synchronized (ServerWrapper
.this) {
784 return (serverState
== BackendServerState
.RUNNING
785 || serverState
== BackendServerState
.RUNNING_START_REQUEST
786 || serverState
== BackendServerState
.SLEEPING
);
791 * Updates the current server state and verifies that the previous state is
794 * @param newState The new state to change to
795 * @param acceptablePreviousStates Acceptable previous states
796 * @throws IllegalStateException If the current state is not one of the
797 * acceptable previous states
799 private void compareAndSetServerState(
800 BackendServerState newState
, BackendServerState
... acceptablePreviousStates
)
801 throws IllegalStateException
{
802 synchronized (ServerWrapper
.this) {
803 for (BackendServerState acceptableStates
: acceptablePreviousStates
) {
804 if (serverState
== acceptableStates
) {
805 serverState
= newState
;
810 StringBuilder error
= new StringBuilder();
811 error
.append("Tried to change state to " + newState
);
812 error
.append(" on server " + toString());
813 error
.append(" but previous state is not ");
814 for (int i
= 0; i
< acceptablePreviousStates
.length
; i
++) {
815 error
.append(acceptablePreviousStates
[i
].name() + " | ");
817 throw new IllegalStateException(error
.toString());
821 * Acquires a serving permit for this server.
823 * @param maxWaitTimeInMs Max wait time in ms
824 * @return true if a serving permit was acquired within the allowed time,
825 * false if not permit was required.
826 * @throws InterruptedException If the thread was interrupted while waiting.
828 boolean acquireServingPermit(int maxWaitTimeInMs
) throws InterruptedException
{
830 this + ": accuiring serving permit, available: " + servingQueue
.availablePermits());
831 return servingQueue
.tryAcquire(maxWaitTimeInMs
, TimeUnit
.MILLISECONDS
);
835 * Returns a serving permit to the pool of available permits
837 void releaseServingPermit() {
838 servingQueue
.release();
840 this + ": returned serving permit, available: " + servingQueue
.availablePermits());
844 * Returns the approximate number of threads waiting for serving permits.
846 * @return the number of waiting threads.
848 int getApproximateQueueLength() {
849 return servingQueue
.getQueueLength();
853 * Returns the number of requests that can be queued on this specific
854 * server. For servers without pending queue no queue (size=0) is allowed.
856 int getMaxPendingQueueSize() {
857 return serverEntry
.isFailFast() ?
0 : MAX_PENDING_QUEUE_LENGTH
;
861 * The dns prefix for this server, basically the first part of:
862 * <instance>.<server_name>.<app-id>.appspot.com for a specific instance,
863 * and <server_name>.<app-id>.appspot.com for just the server.
865 public String
getDnsPrefix() {
866 if (!isLoadBalanceServer()) {
867 return serverInstance
+ "." + getName();
874 return serverEntry
.getName();
877 public int getInstances() {
878 return serverEntry
.getInstances();
881 BackendServerState
getState() {
882 synchronized (ServerWrapper
.this) {
887 boolean isLoadBalanceServer() {
888 return serverInstance
== -1;
892 public String
toString() {
893 return serverInstance
+ "." + serverEntry
.getName() + " state=" + serverState
;
896 public ContainerService
getContainer() {