App Engine Python SDK version 1.8.9
[gae.git] / python / google / appengine / tools / devappserver2 / go_runtime.py
blob2d44ae8acad3b7065dd9e6b4354af8f36aec62b2
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Serves content for "script" handlers using the Go runtime."""
20 import logging
21 import os
22 import os.path
23 import sys
24 import threading
26 from google.appengine.api import appinfo
27 from google.appengine.tools.devappserver2 import application_configuration
28 from google.appengine.tools.devappserver2 import go_application
29 from google.appengine.tools.devappserver2 import http_runtime
30 from google.appengine.tools.devappserver2 import instance
32 _REBUILD_CONFIG_CHANGES = frozenset(
33 [application_configuration.SKIP_FILES_CHANGED,
34 application_configuration.NOBUILD_FILES_CHANGED])
37 class _GoBuildFailureRuntimeProxy(instance.RuntimeProxy):
38 """Serves an error page for a Go application build failure."""
40 def __init__(self, failure_exception):
41 self._failure_exception = failure_exception
43 def start(self):
44 pass
46 def quit(self):
47 pass
49 def handle(self, environ, start_response, url_map, match, request_id,
50 request_type):
51 """Serves a request by displaying an error page.
53 Args:
54 environ: An environ dict for the request as defined in PEP-333.
55 start_response: A function with semantics defined in PEP-333.
56 url_map: An appinfo.URLMap instance containing the configuration for the
57 handler matching this request.
58 match: A re.MatchObject containing the result of the matched URL pattern.
59 request_id: A unique string id associated with the request.
60 request_type: The type of the request. See instance.*_REQUEST module
61 constants.
63 Yields:
64 A sequence of strings containing the body of the HTTP response.
65 """
66 start_response('500 Internal Server Error',
67 [('Content-Type', 'text/plain; charset=utf-8')])
68 yield 'The Go application could not be built.\n'
69 yield '\n'
70 yield str(self._failure_exception)
73 class GoRuntimeInstanceFactory(instance.InstanceFactory):
74 """A factory that creates new Go runtime Instances."""
76 START_URL_MAP = appinfo.URLMap(
77 url='/_ah/start',
78 script='_go_app',
79 login='admin')
80 WARMUP_URL_MAP = appinfo.URLMap(
81 url='/_ah/warmup',
82 script='_go_app',
83 login='admin')
84 FILE_CHANGE_INSTANCE_RESTART_POLICY = instance.ALWAYS
86 def __init__(self, request_data, runtime_config_getter, module_configuration):
87 """Initializer for GoRuntimeInstanceFactory.
89 Args:
90 request_data: A wsgi_request_info.WSGIRequestInfo that will be provided
91 with request information for use by API stubs.
92 runtime_config_getter: A function that can be called without arguments
93 and returns the runtime_config_pb2.RuntimeConfig containing the
94 configuration for the runtime.
95 module_configuration: An application_configuration.ModuleConfiguration
96 instance respresenting the configuration of the module that owns the
97 runtime.
98 """
99 super(GoRuntimeInstanceFactory, self).__init__(request_data, 1)
100 self._runtime_config_getter = runtime_config_getter
101 self._module_configuration = module_configuration
102 self._application_lock = threading.Lock()
103 self._go_application = go_application.GoApplication(
104 self._module_configuration)
105 self._modified_since_last_build = False
106 self._last_build_error = None
108 def get_restart_directories(self):
109 """Returns a list of directories changes in which should trigger a restart.
111 Returns:
112 A list of src directory paths in the GOPATH. Changes (i.e. files added,
113 deleted or modified) in these directories will trigger a restart of all
114 instances created with this factory.
116 try:
117 go_path = os.environ['GOPATH']
118 except KeyError:
119 return []
120 else:
121 if sys.platform.startswith('win32'):
122 roots = go_path.split(';')
123 else:
124 roots = go_path.split(':')
125 dirs = [os.path.join(r, 'src') for r in roots]
126 return [d for d in dirs if os.path.isdir(d)]
128 def files_changed(self):
129 """Called when a file relevant to the factory *might* have changed."""
130 with self._application_lock:
131 self._modified_since_last_build = True
133 def configuration_changed(self, config_changes):
134 """Called when the configuration of the module has changed.
136 Args:
137 config_changes: A set containing the changes that occured. See the
138 *_CHANGED constants in the application_configuration module.
140 if config_changes & _REBUILD_CONFIG_CHANGES:
141 with self._application_lock:
142 self._modified_since_last_build = True
144 def new_instance(self, instance_id, expect_ready_request=False):
145 """Create and return a new Instance.
147 Args:
148 instance_id: A string or integer representing the unique (per module) id
149 of the instance.
150 expect_ready_request: If True then the instance will be sent a special
151 request (i.e. /_ah/warmup or /_ah/start) before it can handle external
152 requests.
154 Returns:
155 The newly created instance.Instance.
158 def instance_config_getter():
159 runtime_config = self._runtime_config_getter()
160 runtime_config.instance_id = str(instance_id)
161 return runtime_config
163 with self._application_lock:
164 try:
165 if self._go_application.maybe_build(self._modified_since_last_build):
166 if self._last_build_error:
167 logging.info('Go application successfully built.')
168 self._last_build_error = None
169 except go_application.BuildError as e:
170 logging.error('Failed to build Go application: %s', e)
171 # Deploy a failure proxy now and each time a new instance is requested.
172 self._last_build_error = e
174 self._modified_since_last_build = False
176 if self._last_build_error:
177 logging.debug('Deploying new instance of failure proxy.')
178 proxy = _GoBuildFailureRuntimeProxy(self._last_build_error)
179 else:
180 proxy = http_runtime.HttpRuntimeProxy(
181 self._go_application.go_executable,
182 instance_config_getter,
183 self._module_configuration,
184 self._go_application.get_environment())
186 return instance.Instance(self.request_data,
187 instance_id,
188 proxy,
189 self.max_concurrent_requests,
190 self.max_background_threads,
191 expect_ready_request)