1.9.30 sync.
[gae.git] / python / google / appengine / tools / devappserver2 / go_runtime.py
blob47741564eb1f7fd276ec9dc2dd8dea0ca73d80b8
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."""
21 import logging
22 import os
23 import os.path
24 import sys
25 import threading
27 from google.appengine.api import appinfo
28 from google.appengine.tools.devappserver2 import application_configuration
29 from google.appengine.tools.devappserver2 import go_application
30 from google.appengine.tools.devappserver2 import http_runtime
31 from google.appengine.tools.devappserver2 import instance
33 _REBUILD_CONFIG_CHANGES = frozenset(
34 [application_configuration.SKIP_FILES_CHANGED,
35 application_configuration.NOBUILD_FILES_CHANGED])
38 class _GoBuildFailureRuntimeProxy(instance.RuntimeProxy):
39 """Serves an error page for a Go application build failure."""
41 def __init__(self, failure_exception):
42 self._failure_exception = failure_exception
44 def start(self):
45 pass
47 def quit(self):
48 pass
50 def handle(self, environ, start_response, url_map, match, request_id,
51 request_type):
52 """Serves a request by displaying an error page.
54 Args:
55 environ: An environ dict for the request as defined in PEP-333.
56 start_response: A function with semantics defined in PEP-333.
57 url_map: An appinfo.URLMap instance containing the configuration for the
58 handler matching this request.
59 match: A re.MatchObject containing the result of the matched URL pattern.
60 request_id: A unique string id associated with the request.
61 request_type: The type of the request. See instance.*_REQUEST module
62 constants.
64 Yields:
65 A sequence of strings containing the body of the HTTP response.
66 """
67 start_response('500 Internal Server Error',
68 [('Content-Type', 'text/plain; charset=utf-8')])
69 yield 'The Go application could not be built.\n'
70 yield '\n'
71 yield str(self._failure_exception)
74 class GoRuntimeInstanceFactory(instance.InstanceFactory):
75 """A factory that creates new Go runtime Instances."""
77 START_URL_MAP = appinfo.URLMap(
78 url='/_ah/start',
79 script='_go_app',
80 login='admin')
81 WARMUP_URL_MAP = appinfo.URLMap(
82 url='/_ah/warmup',
83 script='_go_app',
84 login='admin')
85 FILE_CHANGE_INSTANCE_RESTART_POLICY = instance.ALWAYS
87 def __init__(self, request_data, runtime_config_getter, module_configuration):
88 """Initializer for GoRuntimeInstanceFactory.
90 Args:
91 request_data: A wsgi_request_info.WSGIRequestInfo that will be provided
92 with request information for use by API stubs.
93 runtime_config_getter: A function that can be called without arguments
94 and returns the runtime_config_pb2.RuntimeConfig containing the
95 configuration for the runtime.
96 module_configuration: An application_configuration.ModuleConfiguration
97 instance respresenting the configuration of the module that owns the
98 runtime.
99 """
100 super(GoRuntimeInstanceFactory, self).__init__(request_data, 1)
101 self._runtime_config_getter = runtime_config_getter
102 self._module_configuration = module_configuration
103 self._application_lock = threading.Lock()
104 self._go_application = go_application.GoApplication(
105 self._module_configuration)
106 self._modified_since_last_build = False
107 self._last_build_error = None
109 def get_restart_directories(self):
110 """Returns a list of directories changes in which should trigger a restart.
112 Returns:
113 A list of src directory paths in the GOPATH. Changes (i.e. files added,
114 deleted or modified) in these directories will trigger a restart of all
115 instances created with this factory.
117 try:
118 go_path = os.environ['GOPATH']
119 except KeyError:
120 return []
121 else:
122 if sys.platform.startswith('win32'):
123 roots = go_path.split(';')
124 else:
125 roots = go_path.split(':')
126 dirs = [os.path.join(r, 'src') for r in roots]
127 return [d for d in dirs if os.path.isdir(d)]
129 def files_changed(self):
130 """Called when a file relevant to the factory *might* have changed."""
131 with self._application_lock:
132 self._modified_since_last_build = True
134 def configuration_changed(self, config_changes):
135 """Called when the configuration of the module has changed.
137 Args:
138 config_changes: A set containing the changes that occured. See the
139 *_CHANGED constants in the application_configuration module.
141 if config_changes & _REBUILD_CONFIG_CHANGES:
142 with self._application_lock:
143 self._modified_since_last_build = True
145 def new_instance(self, instance_id, expect_ready_request=False):
146 """Create and return a new Instance.
148 Args:
149 instance_id: A string or integer representing the unique (per module) id
150 of the instance.
151 expect_ready_request: If True then the instance will be sent a special
152 request (i.e. /_ah/warmup or /_ah/start) before it can handle external
153 requests.
155 Returns:
156 The newly created instance.Instance.
159 def instance_config_getter():
160 runtime_config = self._runtime_config_getter()
161 runtime_config.instance_id = str(instance_id)
162 return runtime_config
164 with self._application_lock:
165 try:
166 if self._go_application.maybe_build(self._modified_since_last_build):
167 if self._last_build_error:
168 logging.info('Go application successfully built.')
169 self._last_build_error = None
170 except go_application.BuildError as e:
171 logging.error('Failed to build Go application: %s', e)
172 # Deploy a failure proxy now and each time a new instance is requested.
173 self._last_build_error = e
175 self._modified_since_last_build = False
177 if self._last_build_error:
178 logging.debug('Deploying new instance of failure proxy.')
179 proxy = _GoBuildFailureRuntimeProxy(self._last_build_error)
180 else:
181 proxy = http_runtime.HttpRuntimeProxy(
182 self._go_application.go_executable,
183 instance_config_getter,
184 self._module_configuration,
185 self._go_application.get_environment())
187 return instance.Instance(self.request_data,
188 instance_id,
189 proxy,
190 self.max_concurrent_requests,
191 self.max_background_threads,
192 expect_ready_request)