codekingpro/portable-devtools
114k
1import errno2import sys3from types import FunctionType4 5import eventlet6from eventlet import greenio7from eventlet import patcher8from eventlet.green import select, threading, time9import six10 11 12__patched__ = ['call', 'check_call', 'Popen']13to_patch = [('select', select), ('threading', threading), ('time', time)]14 15if sys.version_info > (3, 4):16 from eventlet.green import selectors17 to_patch.append(('selectors', selectors))18 19patcher.inject('subprocess', globals(), *to_patch)20subprocess_orig = patcher.original("subprocess")21subprocess_imported = sys.modules.get('subprocess', subprocess_orig)22mswindows = sys.platform == "win32"23 24 25if getattr(subprocess_orig, 'TimeoutExpired', None) is None:26 # Backported from Python 3.3.27 # https://bitbucket.org/eventlet/eventlet/issue/8928 class TimeoutExpired(Exception):29 """This exception is raised when the timeout expires while waiting for30 a child process.31 """32 33 def __init__(self, cmd, timeout, output=None):34 self.cmd = cmd35 self.timeout = timeout36 self.output = output37 38 def __str__(self):39 return ("Command '%s' timed out after %s seconds" %40 (self.cmd, self.timeout))41else:42 TimeoutExpired = subprocess_imported.TimeoutExpired43 44 45# This is the meat of this module, the green version of Popen.46class Popen(subprocess_orig.Popen):47 """eventlet-friendly version of subprocess.Popen"""48 # We do not believe that Windows pipes support non-blocking I/O. At least,49 # the Python file objects stored on our base-class object have no50 # setblocking() method, and the Python fcntl module doesn't exist on51 # Windows. (see eventlet.greenio.set_nonblocking()) As the sole purpose of52 # this __init__() override is to wrap the pipes for eventlet-friendly53 # non-blocking I/O, don't even bother overriding it on Windows.54 if not mswindows:55 def __init__(self, args, bufsize=0, *argss, **kwds):56 self.args = args57 # Forward the call to base-class constructor58 subprocess_orig.Popen.__init__(self, args, 0, *argss, **kwds)59 # Now wrap the pipes, if any. This logic is loosely borrowed from60 # eventlet.processes.Process.run() method.61 for attr in "stdin", "stdout", "stderr":62 pipe = getattr(self, attr)63 if pipe is not None and type(pipe) != greenio.GreenPipe:64 # https://github.com/eventlet/eventlet/issues/24365 # AttributeError: '_io.TextIOWrapper' object has no attribute 'mode'66 mode = getattr(pipe, 'mode', '')67 if not mode:68 if pipe.readable():69 mode += 'r'70 if pipe.writable():71 mode += 'w'72 # ValueError: can't have unbuffered text I/O73 if bufsize == 0:74 bufsize = -175 wrapped_pipe = greenio.GreenPipe(pipe, mode, bufsize)76 setattr(self, attr, wrapped_pipe)77 __init__.__doc__ = subprocess_orig.Popen.__init__.__doc__78 79 def wait(self, timeout=None, check_interval=0.01):80 # Instead of a blocking OS call, this version of wait() uses logic81 # borrowed from the eventlet 0.2 processes.Process.wait() method.82 if timeout is not None:83 endtime = time.time() + timeout84 try:85 while True:86 status = self.poll()87 if status is not None:88 return status89 if timeout is not None and time.time() > endtime:90 raise TimeoutExpired(self.args, timeout)91 eventlet.sleep(check_interval)92 except OSError as e:93 if e.errno == errno.ECHILD:94 # no child process, this happens if the child process95 # already died and has been cleaned up96 return -197 else:98 raise99 wait.__doc__ = subprocess_orig.Popen.wait.__doc__100 101 if not mswindows:102 # don't want to rewrite the original _communicate() method, we103 # just want a version that uses eventlet.green.select.select()104 # instead of select.select().105 _communicate = FunctionType(106 six.get_function_code(six.get_unbound_function(107 subprocess_orig.Popen._communicate)),108 globals())109 try:110 _communicate_with_select = FunctionType(111 six.get_function_code(six.get_unbound_function(112 subprocess_orig.Popen._communicate_with_select)),113 globals())114 _communicate_with_poll = FunctionType(115 six.get_function_code(six.get_unbound_function(116 subprocess_orig.Popen._communicate_with_poll)),117 globals())118 except AttributeError:119 pass120 121 122# Borrow subprocess.call() and check_call(), but patch them so they reference123# OUR Popen class rather than subprocess.Popen.124def patched_function(function):125 new_function = FunctionType(six.get_function_code(function), globals())126 if six.PY3:127 new_function.__kwdefaults__ = function.__kwdefaults__128 new_function.__defaults__ = function.__defaults__129 return new_function130 131 132call = patched_function(subprocess_orig.call)133check_call = patched_function(subprocess_orig.check_call)134# check_output is Python 2.7+135if hasattr(subprocess_orig, 'check_output'):136 __patched__.append('check_output')137 check_output = patched_function(subprocess_orig.check_output)138del patched_function139 140# Keep exceptions identity.141# https://github.com/eventlet/eventlet/issues/413142CalledProcessError = subprocess_imported.CalledProcessError143del subprocess_imported144 