/tango/gevent_executor.py

https://gitlab.com/AntBar/pytango · Python · 182 lines · 127 code · 36 blank · 19 comment · 10 complexity · 9d70513010978808c4108cc486944c17 MD5 · raw file

  1. # ------------------------------------------------------------------------------
  2. # This file is part of PyTango (http://pytango.rtfd.io)
  3. #
  4. # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain
  5. # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France
  6. #
  7. # Distributed under the terms of the GNU Lesser General Public License,
  8. # either version 3 of the License, or (at your option) any later version.
  9. # See LICENSE.txt for more info.
  10. # ------------------------------------------------------------------------------
  11. # Future imports
  12. from __future__ import absolute_import
  13. # Imports
  14. import sys
  15. import six
  16. import functools
  17. from collections import namedtuple
  18. # Gevent imports
  19. import gevent.event
  20. import gevent.queue
  21. import gevent.monkey
  22. import gevent.threadpool
  23. # Bypass gevent monkey patching
  24. ThreadSafeEvent = gevent.monkey.get_original('threading', 'Event')
  25. # Tango imports
  26. from .green import AbstractExecutor
  27. __all__ = ("get_global_executor", "set_global_executor", "GeventExecutor")
  28. # Global executor
  29. _EXECUTOR = None
  30. _THREAD_POOL = None
  31. def get_global_executor():
  32. global _EXECUTOR
  33. if _EXECUTOR is None:
  34. _EXECUTOR = GeventExecutor()
  35. return _EXECUTOR
  36. def set_global_executor(executor):
  37. global _EXECUTOR
  38. _EXECUTOR = executor
  39. def get_global_threadpool():
  40. global _THREAD_POOL
  41. if _THREAD_POOL is None:
  42. _THREAD_POOL = ThreadPool(maxsize=10**4)
  43. return _THREAD_POOL
  44. ExceptionInfo = namedtuple('ExceptionInfo', 'type value traceback')
  45. def wrap_error(func):
  46. @functools.wraps(func)
  47. def wrapper(*args, **kwargs):
  48. try:
  49. return func(*args, **kwargs)
  50. except:
  51. return ExceptionInfo(*sys.exc_info())
  52. return wrapper
  53. def unwrap_error(source):
  54. destination = gevent.event.AsyncResult()
  55. def link(source):
  56. if isinstance(source.value, ExceptionInfo):
  57. try:
  58. destination.set_exception(
  59. source.value.value, exc_info=source.value)
  60. # Gevent 1.0 compatibility
  61. except TypeError:
  62. destination.set_exception(source.value.value)
  63. return
  64. destination(source)
  65. source.rawlink(link)
  66. return destination
  67. class ThreadPool(gevent.threadpool.ThreadPool):
  68. def spawn(self, fn, *args, **kwargs):
  69. wrapped = wrap_error(fn)
  70. raw = super(ThreadPool, self).spawn(wrapped, *args, **kwargs)
  71. return unwrap_error(raw)
  72. # Gevent task and event loop
  73. class GeventTask:
  74. def __init__(self, func, *args, **kwargs):
  75. self.func = func
  76. self.args = args
  77. self.kwargs = kwargs
  78. self.value = None
  79. self.exception = None
  80. self.done = ThreadSafeEvent()
  81. self.started = ThreadSafeEvent()
  82. def run(self):
  83. self.started.set()
  84. try:
  85. self.value = self.func(*self.args, **self.kwargs)
  86. except:
  87. self.exception = sys.exc_info()
  88. finally:
  89. self.done.set()
  90. def spawn(self):
  91. return gevent.spawn(self.run)
  92. def result(self):
  93. self.done.wait()
  94. if self.exception:
  95. six.reraise(*self.exception)
  96. return self.value
  97. # Gevent executor
  98. class GeventExecutor(AbstractExecutor):
  99. """Gevent tango executor"""
  100. asynchronous = True
  101. default_wait = True
  102. def __init__(self, loop=None, subexecutor=None):
  103. super(GeventExecutor, self).__init__()
  104. if loop is None:
  105. loop = gevent.get_hub().loop
  106. if subexecutor is None:
  107. subexecutor = get_global_threadpool()
  108. self.loop = loop
  109. self.subexecutor = subexecutor
  110. def delegate(self, fn, *args, **kwargs):
  111. """Return the given operation as a gevent future."""
  112. return self.subexecutor.spawn(fn, *args, **kwargs)
  113. def access(self, accessor, timeout=None):
  114. """Return a result from an gevent future."""
  115. return accessor.get(timeout=timeout)
  116. def create_watcher(self):
  117. try:
  118. return self.loop.async_()
  119. except AttributeError:
  120. return getattr(self.loop, 'async')()
  121. def submit(self, fn, *args, **kwargs):
  122. task = GeventTask(fn, *args, **kwargs)
  123. watcher = self.create_watcher()
  124. watcher.start(task.spawn)
  125. watcher.send()
  126. task.started.wait()
  127. # The watcher has to be stopped in order to be garbage-collected.
  128. # This step is crucial since the watcher holds a reference to the
  129. # `task.spawn` method which itself holds a reference to the task.
  130. # It's also important to wait for the task to be spawned before
  131. # stopping the watcher, otherwise the task won't run.
  132. watcher.stop()
  133. return task
  134. def execute(self, fn, *args, **kwargs):
  135. """Execute an operation and return the result."""
  136. if self.in_executor_context():
  137. return fn(*args, **kwargs)
  138. task = self.submit(fn, *args, **kwargs)
  139. return task.result()