https://github.com/eventlet/eventlet/issues/94 Because of the way paramiko utilises a client thread to manage its communication, it's not been compatible with eventlet when run in parallel. It's not the only place these problems would arise. This stemmed from the reuse of a fileno by the underlying OS. Because listeners are registered against this descriptor, it would be possible for old listeners to receive events destined for newer descriptors; occasionally code would attempt to utilise the new descriptor from a different greenlet, giving rise to the 'second simultaneous read' problem. Whenever a Python object is created to wrap one of these filenos, we now signal the hub in order that it can correctly obsolete extant listeners against that fileno. This is a fairly tricky operation, due to the way that listeners' threads are interleaved with the hub's operation - there are a number of small fixes here to defend against one listener from effectively obsoleting another when an event is pending against it.
		
			
				
	
	
		
			95 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			95 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
os_orig = __import__("os")
 | 
						|
import errno
 | 
						|
socket = __import__("socket")
 | 
						|
 | 
						|
from eventlet import greenio
 | 
						|
from eventlet.support import get_errno
 | 
						|
from eventlet import greenthread
 | 
						|
from eventlet import hubs
 | 
						|
from eventlet.patcher import slurp_properties
 | 
						|
 | 
						|
__all__ = os_orig.__all__
 | 
						|
__patched__ = ['fdopen', 'read', 'write', 'wait', 'waitpid', 'open']
 | 
						|
 | 
						|
slurp_properties(os_orig, globals(), 
 | 
						|
    ignore=__patched__, srckeys=dir(os_orig))
 | 
						|
 | 
						|
def fdopen(fd, *args, **kw):
 | 
						|
    """fdopen(fd [, mode='r' [, bufsize]]) -> file_object
 | 
						|
    
 | 
						|
    Return an open file object connected to a file descriptor."""
 | 
						|
    if not isinstance(fd, int):
 | 
						|
        raise TypeError('fd should be int, not %r' % fd)
 | 
						|
    try:
 | 
						|
        return greenio.GreenPipe(fd, *args, **kw)
 | 
						|
    except IOError as e:
 | 
						|
        raise OSError(*e.args)
 | 
						|
 | 
						|
__original_read__ = os_orig.read
 | 
						|
def read(fd, n):
 | 
						|
    """read(fd, buffersize) -> string
 | 
						|
    
 | 
						|
    Read a file descriptor."""
 | 
						|
    while True:
 | 
						|
        try:
 | 
						|
            return __original_read__(fd, n)
 | 
						|
        except (OSError, IOError) as e:
 | 
						|
            if get_errno(e) != errno.EAGAIN:
 | 
						|
                raise
 | 
						|
        except socket.error as e:
 | 
						|
            if get_errno(e) == errno.EPIPE:
 | 
						|
                return ''
 | 
						|
            raise
 | 
						|
        try:
 | 
						|
            hubs.trampoline(fd, read=True)
 | 
						|
        except hubs.IOClosed:
 | 
						|
            return ''
 | 
						|
 | 
						|
__original_write__ = os_orig.write
 | 
						|
def write(fd, st):
 | 
						|
    """write(fd, string) -> byteswritten
 | 
						|
    
 | 
						|
    Write a string to a file descriptor.
 | 
						|
    """
 | 
						|
    while True:
 | 
						|
        try:
 | 
						|
            return __original_write__(fd, st)
 | 
						|
        except (OSError, IOError) as e:
 | 
						|
            if get_errno(e) != errno.EAGAIN:
 | 
						|
                raise
 | 
						|
        except socket.error as e:
 | 
						|
            if get_errno(e) != errno.EPIPE:
 | 
						|
                raise
 | 
						|
        hubs.trampoline(fd, write=True)
 | 
						|
    
 | 
						|
def wait():
 | 
						|
    """wait() -> (pid, status)
 | 
						|
    
 | 
						|
    Wait for completion of a child process."""
 | 
						|
    return waitpid(0,0)
 | 
						|
 | 
						|
__original_waitpid__ = os_orig.waitpid
 | 
						|
def waitpid(pid, options):
 | 
						|
    """waitpid(...)
 | 
						|
    waitpid(pid, options) -> (pid, status)
 | 
						|
    
 | 
						|
    Wait for completion of a given child process."""
 | 
						|
    if options & os_orig.WNOHANG != 0:
 | 
						|
        return __original_waitpid__(pid, options)
 | 
						|
    else:
 | 
						|
        new_options = options | os_orig.WNOHANG
 | 
						|
        while True:
 | 
						|
            rpid, status = __original_waitpid__(pid, new_options)
 | 
						|
            if rpid and status >= 0:
 | 
						|
                return rpid, status
 | 
						|
            greenthread.sleep(0.01)
 | 
						|
 | 
						|
__original_open__ = os_orig.open
 | 
						|
def open(file, flags, mode=0777):
 | 
						|
    """ Wrap os.open
 | 
						|
        This behaves identically, but collaborates with
 | 
						|
        the hub's notify_opened protocol.
 | 
						|
    """
 | 
						|
    fd = __original_open__(file, flags, mode)
 | 
						|
    hubs.notify_opened(fd)
 | 
						|
    return fd |