You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
5.7KB

  1. import errno
  2. import sys
  3. from types import FunctionType
  4. import eventlet
  5. from eventlet import greenio
  6. from eventlet import patcher
  7. from eventlet.green import select, threading, time
  8. import six
  9. __patched__ = ['call', 'check_call', 'Popen']
  10. to_patch = [('select', select), ('threading', threading), ('time', time)]
  11. if sys.version_info > (3, 4):
  12. from eventlet.green import selectors
  13. to_patch.append(('selectors', selectors))
  14. patcher.inject('subprocess', globals(), *to_patch)
  15. subprocess_orig = patcher.original("subprocess")
  16. subprocess_imported = sys.modules.get('subprocess', subprocess_orig)
  17. mswindows = sys.platform == "win32"
  18. if getattr(subprocess_orig, 'TimeoutExpired', None) is None:
  19. # Backported from Python 3.3.
  20. # https://bitbucket.org/eventlet/eventlet/issue/89
  21. class TimeoutExpired(Exception):
  22. """This exception is raised when the timeout expires while waiting for
  23. a child process.
  24. """
  25. def __init__(self, cmd, timeout, output=None):
  26. self.cmd = cmd
  27. self.timeout = timeout
  28. self.output = output
  29. def __str__(self):
  30. return ("Command '%s' timed out after %s seconds" %
  31. (self.cmd, self.timeout))
  32. else:
  33. TimeoutExpired = subprocess_imported.TimeoutExpired
  34. # This is the meat of this module, the green version of Popen.
  35. class Popen(subprocess_orig.Popen):
  36. """eventlet-friendly version of subprocess.Popen"""
  37. # We do not believe that Windows pipes support non-blocking I/O. At least,
  38. # the Python file objects stored on our base-class object have no
  39. # setblocking() method, and the Python fcntl module doesn't exist on
  40. # Windows. (see eventlet.greenio.set_nonblocking()) As the sole purpose of
  41. # this __init__() override is to wrap the pipes for eventlet-friendly
  42. # non-blocking I/O, don't even bother overriding it on Windows.
  43. if not mswindows:
  44. def __init__(self, args, bufsize=0, *argss, **kwds):
  45. self.args = args
  46. # Forward the call to base-class constructor
  47. subprocess_orig.Popen.__init__(self, args, 0, *argss, **kwds)
  48. # Now wrap the pipes, if any. This logic is loosely borrowed from
  49. # eventlet.processes.Process.run() method.
  50. for attr in "stdin", "stdout", "stderr":
  51. pipe = getattr(self, attr)
  52. if pipe is not None and type(pipe) != greenio.GreenPipe:
  53. # https://github.com/eventlet/eventlet/issues/243
  54. # AttributeError: '_io.TextIOWrapper' object has no attribute 'mode'
  55. mode = getattr(pipe, 'mode', '')
  56. if not mode:
  57. if pipe.readable():
  58. mode += 'r'
  59. if pipe.writable():
  60. mode += 'w'
  61. # ValueError: can't have unbuffered text I/O
  62. if bufsize == 0:
  63. bufsize = -1
  64. wrapped_pipe = greenio.GreenPipe(pipe, mode, bufsize)
  65. setattr(self, attr, wrapped_pipe)
  66. __init__.__doc__ = subprocess_orig.Popen.__init__.__doc__
  67. def wait(self, timeout=None, check_interval=0.01):
  68. # Instead of a blocking OS call, this version of wait() uses logic
  69. # borrowed from the eventlet 0.2 processes.Process.wait() method.
  70. if timeout is not None:
  71. endtime = time.time() + timeout
  72. try:
  73. while True:
  74. status = self.poll()
  75. if status is not None:
  76. return status
  77. if timeout is not None and time.time() > endtime:
  78. raise TimeoutExpired(self.args, timeout)
  79. eventlet.sleep(check_interval)
  80. except OSError as e:
  81. if e.errno == errno.ECHILD:
  82. # no child process, this happens if the child process
  83. # already died and has been cleaned up
  84. return -1
  85. else:
  86. raise
  87. wait.__doc__ = subprocess_orig.Popen.wait.__doc__
  88. if not mswindows:
  89. # don't want to rewrite the original _communicate() method, we
  90. # just want a version that uses eventlet.green.select.select()
  91. # instead of select.select().
  92. _communicate = FunctionType(
  93. six.get_function_code(six.get_unbound_function(
  94. subprocess_orig.Popen._communicate)),
  95. globals())
  96. try:
  97. _communicate_with_select = FunctionType(
  98. six.get_function_code(six.get_unbound_function(
  99. subprocess_orig.Popen._communicate_with_select)),
  100. globals())
  101. _communicate_with_poll = FunctionType(
  102. six.get_function_code(six.get_unbound_function(
  103. subprocess_orig.Popen._communicate_with_poll)),
  104. globals())
  105. except AttributeError:
  106. pass
  107. # Borrow subprocess.call() and check_call(), but patch them so they reference
  108. # OUR Popen class rather than subprocess.Popen.
  109. def patched_function(function):
  110. new_function = FunctionType(six.get_function_code(function), globals())
  111. if six.PY3:
  112. new_function.__kwdefaults__ = function.__kwdefaults__
  113. new_function.__defaults__ = function.__defaults__
  114. return new_function
  115. call = patched_function(subprocess_orig.call)
  116. check_call = patched_function(subprocess_orig.check_call)
  117. # check_output is Python 2.7+
  118. if hasattr(subprocess_orig, 'check_output'):
  119. __patched__.append('check_output')
  120. check_output = patched_function(subprocess_orig.check_output)
  121. del patched_function
  122. # Keep exceptions identity.
  123. # https://github.com/eventlet/eventlet/issues/413
  124. CalledProcessError = subprocess_imported.CalledProcessError
  125. del subprocess_imported