| #!/usr/bin/env python |
| # |
| # Copyright 2007 Google Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| |
| """Background Threads API. |
| |
| .. deprecated:: 1.9.1 |
| Use Modules library instead. |
| |
| An API for creating background threads. |
| |
| Background threads created using this API do not inherit the context of their |
| creator and do not need to end before the creator request completes. |
| """ |
| |
| |
| __all__ = ['start_new_background_thread', |
| 'BackgroundThread', |
| 'Error', |
| 'FrontendsNotSupported', |
| 'BackgroundThreadLimitReachedError', |
| ] |
| |
| import collections |
| import sys |
| import threading |
| |
| from google.appengine.api import apiproxy_stub_map |
| from google.appengine.api.system import system_service_pb |
| from google.appengine.runtime import apiproxy_errors |
| from google.appengine.runtime import background |
| |
| |
| class Error(Exception): |
| """Base exception class for this module.""" |
| |
| |
| class FrontendsNotSupported(Error): |
| """Error raised when a background thread is requested on a front end.""" |
| |
| |
| class BackgroundThreadLimitReachedError(Error): |
| """Error raised when no further active background threads can be created.""" |
| |
| ERROR_MAP = collections.defaultdict(lambda: Error, { |
| system_service_pb.SystemServiceError.BACKEND_REQUIRED: |
| FrontendsNotSupported, |
| system_service_pb.SystemServiceError.LIMIT_REACHED: |
| BackgroundThreadLimitReachedError, |
| }) |
| |
| |
| def start_new_background_thread(target, args, kwargs=None): |
| """Starts a new background thread. |
| |
| Creates a new background thread which will call target(*args, **kwargs). |
| |
| Args: |
| target: A callable for the new thread to run. |
| args: Position arguments to be passed to target. |
| kwargs: Keyword arguments to be passed to target. |
| |
| Returns: |
| The thread ID of the background thread. |
| """ |
| |
| if kwargs is None: |
| kwargs = {} |
| request = system_service_pb.StartBackgroundRequestRequest() |
| response = system_service_pb.StartBackgroundRequestResponse() |
| try: |
| apiproxy_stub_map.MakeSyncCall('system', 'StartBackgroundRequest', request, |
| response) |
| except apiproxy_errors.ApplicationError as error: |
| raise ERROR_MAP[error.application_error](error.error_detail) |
| else: |
| return background.EnqueueBackgroundThread( |
| response.request_id(), |
| target, |
| args, |
| kwargs) |
| |
| |
| class BackgroundThread(threading.Thread): |
| """A threading.Thread-like interface for background threads.""" |
| |
| def start(self): |
| """Starts this background thread.""" |
| if not self._Thread__initialized: |
| raise RuntimeError('thread.__init__() not called') |
| if self._Thread__started.is_set(): |
| raise RuntimeError('threads can only be started once') |
| with threading._active_limbo_lock: |
| threading._limbo[self] = self |
| try: |
| start_new_background_thread(self.__bootstrap, ()) |
| except Exception: |
| with threading._active_limbo_lock: |
| del threading._limbo[self] |
| raise |
| self._Thread__started.wait() |
| |
| def __bootstrap(self): |
| try: |
| self._set_ident() |
| self._Thread__started.set() |
| threading._active_limbo_lock.acquire() |
| threading._active[self._Thread__ident] = self |
| del threading._limbo[self] |
| threading._active_limbo_lock.release() |
| |
| if threading._trace_hook: |
| sys.settrace(threading._trace_hook) |
| if threading._profile_hook: |
| sys.setprofile(threading._profile_hook) |
| |
| try: |
| self.run() |
| finally: |
| self._Thread__exc_clear() |
| finally: |
| with threading._active_limbo_lock: |
| self._Thread__stop() |
| try: |
| del threading._active[threading._get_ident()] |
| except: |
| pass |