
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
167 lines
4.9 KiB
Python
167 lines
4.9 KiB
Python
"""Issue #143 - Socket timeouts in wsgi server not caught.
|
|
https://bitbucket.org/eventlet/eventlet/issue/143/
|
|
|
|
This file intentionally ignored by nose.
|
|
Caller process (tests.wsgi_test.TestWsgiConnTimeout) handles success / failure
|
|
|
|
|
|
Simulate server connection socket timeout without actually waiting.
|
|
Logs 'timed out' if server debug=True (similar to 'accepted' logging)
|
|
|
|
FAIL: if log (ie, _spawn_n_impl 'except:' catches timeout, logs TB)
|
|
NOTE: timeouts are NOT on server_sock, but on the conn sockets produced
|
|
by the socket.accept() call
|
|
|
|
server's socket.listen() sock - NaughtySocketAcceptWrap
|
|
/ | \
|
|
| | | (1 - many)
|
|
V V V
|
|
server / client accept() conn - ExplodingConnectionWrap
|
|
/ | \
|
|
| | | (1 - many)
|
|
V V V
|
|
connection makefile() file objects - ExplodingSocketFile <-- these raise
|
|
"""
|
|
from __future__ import print_function
|
|
|
|
import eventlet
|
|
from eventlet.support import six
|
|
|
|
import socket
|
|
import sys
|
|
|
|
import tests.wsgi_test
|
|
|
|
|
|
# no standard tests in this file, ignore
|
|
__test__ = False
|
|
|
|
|
|
# This test might make you wince
|
|
class NaughtySocketAcceptWrap(object):
|
|
# server's socket.accept(); patches resulting connection sockets
|
|
|
|
def __init__(self, sock):
|
|
self.sock = sock
|
|
self.sock._really_accept = self.sock.accept
|
|
self.sock.accept = self
|
|
self.conn_reg = []
|
|
|
|
def unwrap(self):
|
|
self.sock.accept = self.sock._really_accept
|
|
del self.sock._really_accept
|
|
for conn_wrap in self.conn_reg:
|
|
conn_wrap.unwrap()
|
|
|
|
def arm(self):
|
|
print("ca-click")
|
|
for i in self.conn_reg:
|
|
i.arm()
|
|
|
|
def __call__(self):
|
|
print(self.__class__.__name__ + ".__call__")
|
|
conn, addr = self.sock._really_accept()
|
|
self.conn_reg.append(ExplodingConnectionWrap(conn))
|
|
return conn, addr
|
|
|
|
|
|
class ExplodingConnectionWrap(object):
|
|
# new connection's socket.makefile
|
|
# eventlet *tends* to use socket.makefile, not raw socket methods.
|
|
# need to patch file operations
|
|
|
|
def __init__(self, conn):
|
|
self.conn = conn
|
|
self.conn._really_makefile = self.conn.makefile
|
|
self.conn.makefile = self
|
|
self.armed = False
|
|
self.file_reg = []
|
|
|
|
def unwrap(self):
|
|
self.conn.makefile = self.conn._really_makefile
|
|
del self.conn._really_makefile
|
|
|
|
def arm(self):
|
|
print("tick")
|
|
for i in self.file_reg:
|
|
i.arm()
|
|
|
|
def __call__(self, mode='r', bufsize=-1):
|
|
print(self.__class__.__name__ + ".__call__")
|
|
# file_obj = self.conn._really_makefile(*args, **kwargs)
|
|
file_obj = ExplodingSocketFile(self.conn._sock, mode, bufsize)
|
|
self.file_reg.append(file_obj)
|
|
return file_obj
|
|
|
|
|
|
class ExplodingSocketFile(eventlet.greenio._fileobject):
|
|
|
|
def __init__(self, sock, mode='rb', bufsize=-1, close=False):
|
|
args = [bufsize, close] if six.PY2 else []
|
|
super(self.__class__, self).__init__(sock, mode, *args)
|
|
self.armed = False
|
|
|
|
def arm(self):
|
|
print("beep")
|
|
self.armed = True
|
|
|
|
def _fuse(self):
|
|
if self.armed:
|
|
print("=== ~* BOOM *~ ===")
|
|
raise socket.timeout("timed out")
|
|
|
|
def readline(self, *args, **kwargs):
|
|
print(self.__class__.__name__ + ".readline")
|
|
self._fuse()
|
|
return super(self.__class__, self).readline(*args, **kwargs)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
for debug in (False, True):
|
|
print("SEPERATOR_SENTINEL")
|
|
print("debug set to: %s" % debug)
|
|
|
|
server_sock = eventlet.listen(('localhost', 0))
|
|
server_addr = server_sock.getsockname()
|
|
sock_wrap = NaughtySocketAcceptWrap(server_sock)
|
|
|
|
eventlet.spawn_n(
|
|
eventlet.wsgi.server,
|
|
debug=debug,
|
|
log=sys.stdout,
|
|
max_size=128,
|
|
site=tests.wsgi_test.Site(),
|
|
sock=server_sock,
|
|
)
|
|
|
|
try:
|
|
# req #1 - normal
|
|
sock1 = eventlet.connect(server_addr)
|
|
sock1.settimeout(0.1)
|
|
fd1 = sock1.makefile('rwb')
|
|
fd1.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd1.flush()
|
|
tests.wsgi_test.read_http(sock1)
|
|
|
|
# let the server socket ops catch up, set bomb
|
|
eventlet.sleep(0)
|
|
print("arming...")
|
|
sock_wrap.arm()
|
|
|
|
# req #2 - old conn, post-arm - timeout
|
|
fd1.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd1.flush()
|
|
try:
|
|
tests.wsgi_test.read_http(sock1)
|
|
assert False, 'Expected ConnectionClosed exception'
|
|
except tests.wsgi_test.ConnectionClosed:
|
|
pass
|
|
|
|
fd1.close()
|
|
sock1.close()
|
|
finally:
|
|
# reset streams, then output trapped tracebacks
|
|
sock_wrap.unwrap()
|
|
# check output asserts in tests.wsgi_test.TestHttpd
|
|
# test_143_server_connection_timeout_exception
|