
This patch consists of the following changes: * Splitting eventlet.greenio into base, py2 and py3 parts (eventlet.greenio should be exporing the same public objects). This change is motivated by the size and the number of conditions present in the current greenio code * Connected to the first point: implementing almost completely new GreenPipe callable utilizing parts of old GreenPipe code but dropping _fileobject/SocketIO inheritance in favour of io.FileIO and making use of patched _pyio.open function which wraps raw file-like object in various readers and writers (they take care of the buffering, encoding/decoding etc.) * Implementing (from scratch or updating existing versions) green versions of the following modules: * http.* (needed by Python 3's urllib) * selectors (Python >= 3.4, used in subprocess module) * urllib.* (needed by various tests and we were already exposing green urllib) * Modifying some tests to make tests pass, which includes: * unicode/bytestring issues * modifying wsgi_test_conntimeout.py to not pass bufsize and close arguments to ExplodingSocketFile - on Python 3 it inherits from SocketIO, which doesn't deal with buffering at all as far as I can see * Random cleaning up and reorganizing * Requiring Python 3.x tests to pass for the whole build to pass Known issues: * code repetition * naming inconsistencies * possibly breaking some external code using private eventlet.greenio attributes Closes https://github.com/eventlet/eventlet/issues/108 Affects https://github.com/eventlet/eventlet/issues/6 (I'd call it an experimental support) Should help for https://github.com/eventlet/eventlet/issues/145 Should help for https://github.com/eventlet/eventlet/issues/157
109 lines
4.4 KiB
Python
109 lines
4.4 KiB
Python
import errno
|
|
import sys
|
|
from types import FunctionType
|
|
|
|
import eventlet
|
|
from eventlet import greenio
|
|
from eventlet import patcher
|
|
from eventlet.green import select, threading, time
|
|
from eventlet.support import six
|
|
|
|
|
|
to_patch = [('select', select), ('threading', threading), ('time', time)]
|
|
|
|
if sys.version_info > (3, 4):
|
|
from eventlet.green import selectors
|
|
to_patch.append(('selectors', selectors))
|
|
|
|
patcher.inject('subprocess', globals(), *to_patch)
|
|
subprocess_orig = __import__("subprocess")
|
|
|
|
|
|
if getattr(subprocess_orig, 'TimeoutExpired', None) is None:
|
|
# Backported from Python 3.3.
|
|
# https://bitbucket.org/eventlet/eventlet/issue/89
|
|
class TimeoutExpired(Exception):
|
|
"""This exception is raised when the timeout expires while waiting for
|
|
a child process.
|
|
"""
|
|
|
|
def __init__(self, cmd, timeout, output=None):
|
|
self.cmd = cmd
|
|
self.timeout = timeout
|
|
self.output = output
|
|
|
|
def __str__(self):
|
|
return ("Command '%s' timed out after %s seconds" %
|
|
(self.cmd, self.timeout))
|
|
|
|
|
|
# This is the meat of this module, the green version of Popen.
|
|
class Popen(subprocess_orig.Popen):
|
|
"""eventlet-friendly version of subprocess.Popen"""
|
|
# We do not believe that Windows pipes support non-blocking I/O. At least,
|
|
# the Python file objects stored on our base-class object have no
|
|
# setblocking() method, and the Python fcntl module doesn't exist on
|
|
# Windows. (see eventlet.greenio.set_nonblocking()) As the sole purpose of
|
|
# this __init__() override is to wrap the pipes for eventlet-friendly
|
|
# non-blocking I/O, don't even bother overriding it on Windows.
|
|
if not subprocess_orig.mswindows:
|
|
def __init__(self, args, bufsize=0, *argss, **kwds):
|
|
self.args = args
|
|
# Forward the call to base-class constructor
|
|
subprocess_orig.Popen.__init__(self, args, 0, *argss, **kwds)
|
|
# Now wrap the pipes, if any. This logic is loosely borrowed from
|
|
# eventlet.processes.Process.run() method.
|
|
for attr in "stdin", "stdout", "stderr":
|
|
pipe = getattr(self, attr)
|
|
if pipe is not None and not type(pipe) == greenio.GreenPipe:
|
|
wrapped_pipe = greenio.GreenPipe(pipe, pipe.mode, bufsize)
|
|
setattr(self, attr, wrapped_pipe)
|
|
__init__.__doc__ = subprocess_orig.Popen.__init__.__doc__
|
|
|
|
def wait(self, timeout=None, check_interval=0.01):
|
|
# Instead of a blocking OS call, this version of wait() uses logic
|
|
# borrowed from the eventlet 0.2 processes.Process.wait() method.
|
|
if timeout is not None:
|
|
endtime = time.time() + timeout
|
|
try:
|
|
while True:
|
|
status = self.poll()
|
|
if status is not None:
|
|
return status
|
|
if timeout is not None and time.time() > endtime:
|
|
raise TimeoutExpired(self.args, timeout)
|
|
eventlet.sleep(check_interval)
|
|
except OSError as e:
|
|
if e.errno == errno.ECHILD:
|
|
# no child process, this happens if the child process
|
|
# already died and has been cleaned up
|
|
return -1
|
|
else:
|
|
raise
|
|
wait.__doc__ = subprocess_orig.Popen.wait.__doc__
|
|
|
|
if not subprocess_orig.mswindows:
|
|
# don't want to rewrite the original _communicate() method, we
|
|
# just want a version that uses eventlet.green.select.select()
|
|
# instead of select.select().
|
|
_communicate = FunctionType(
|
|
six.get_function_code(six.get_unbound_function(
|
|
subprocess_orig.Popen._communicate)),
|
|
globals())
|
|
try:
|
|
_communicate_with_select = FunctionType(
|
|
six.get_function_code(six.get_unbound_function(
|
|
subprocess_orig.Popen._communicate_with_select)),
|
|
globals())
|
|
_communicate_with_poll = FunctionType(
|
|
six.get_function_code(six.get_unbound_function(
|
|
subprocess_orig.Popen._communicate_with_poll)),
|
|
globals())
|
|
except AttributeError:
|
|
pass
|
|
|
|
# Borrow subprocess.call() and check_call(), but patch them so they reference
|
|
# OUR Popen class rather than subprocess.Popen.
|
|
call = FunctionType(six.get_function_code(subprocess_orig.call), globals())
|
|
check_call = FunctionType(six.get_function_code(subprocess_orig.check_call), globals())
|