6a8c6233d8992408d800bf1e3c7fdd5a671269b0
[packages/trusty/python-eventlet.git] / python-eventlet / tests / isolated / wsgi_connection_timeout.py
1 """Issue #143 - Socket timeouts in wsgi server not caught.
2 https://bitbucket.org/eventlet/eventlet/issue/143/
3
4 This file intentionally ignored by nose.
5 Caller process (tests.wsgi_test.TestWsgiConnTimeout) handles success / failure
6
7
8 Simulate server connection socket timeout without actually waiting.
9 Logs 'timed out' if server debug=True (similar to 'accepted' logging)
10
11 FAIL: if log (ie, _spawn_n_impl 'except:' catches timeout, logs TB)
12 NOTE: timeouts are NOT on server_sock, but on the conn sockets produced
13 by the socket.accept() call
14
15 server's socket.listen() sock - NaughtySocketAcceptWrap
16     /  |  \
17     |  |  |   (1 - many)
18     V  V  V
19 server / client accept() conn - ExplodingConnectionWrap
20     /  |  \
21     |  |  |   (1 - many)
22     V  V  V
23 connection makefile() file objects - ExplodingSocketFile <-- these raise
24 """
25 import socket
26
27 import eventlet
28 from eventlet.support import six
29 import tests.wsgi_test
30
31
32 # no standard tests in this file, ignore
33 __test__ = False
34
35
36 TAG_BOOM = "=== ~* BOOM *~ ==="
37
38 output_buffer = []
39
40
41 class BufferLog(object):
42     @staticmethod
43     def write(s):
44         output_buffer.append(s.rstrip())
45
46
47 # This test might make you wince
48 class NaughtySocketAcceptWrap(object):
49     # server's socket.accept(); patches resulting connection sockets
50
51     def __init__(self, sock):
52         self.sock = sock
53         self.sock._really_accept = self.sock.accept
54         self.sock.accept = self
55         self.conn_reg = []
56
57     def unwrap(self):
58         self.sock.accept = self.sock._really_accept
59         del self.sock._really_accept
60         for conn_wrap in self.conn_reg:
61             conn_wrap.unwrap()
62
63     def arm(self):
64         output_buffer.append("ca-click")
65         for i in self.conn_reg:
66             i.arm()
67
68     def __call__(self):
69         output_buffer.append(self.__class__.__name__ + ".__call__")
70         conn, addr = self.sock._really_accept()
71         self.conn_reg.append(ExplodingConnectionWrap(conn))
72         return conn, addr
73
74
75 class ExplodingConnectionWrap(object):
76     # new connection's socket.makefile
77     # eventlet *tends* to use socket.makefile, not raw socket methods.
78     # need to patch file operations
79
80     def __init__(self, conn):
81         self.conn = conn
82         self.conn._really_makefile = self.conn.makefile
83         self.conn.makefile = self
84         self.armed = False
85         self.file_reg = []
86
87     def unwrap(self):
88         self.conn.makefile = self.conn._really_makefile
89         del self.conn._really_makefile
90
91     def arm(self):
92         output_buffer.append("tick")
93         for i in self.file_reg:
94             i.arm()
95
96     def __call__(self, mode='r', bufsize=-1):
97         output_buffer.append(self.__class__.__name__ + ".__call__")
98         # file_obj = self.conn._really_makefile(*args, **kwargs)
99         file_obj = ExplodingSocketFile(self.conn._sock, mode, bufsize)
100         self.file_reg.append(file_obj)
101         return file_obj
102
103
104 class ExplodingSocketFile(eventlet.greenio._fileobject):
105
106     def __init__(self, sock, mode='rb', bufsize=-1, close=False):
107         args = [bufsize, close] if six.PY2 else []
108         super(self.__class__, self).__init__(sock, mode, *args)
109         self.armed = False
110
111     def arm(self):
112         output_buffer.append("beep")
113         self.armed = True
114
115     def _fuse(self):
116         if self.armed:
117             output_buffer.append(TAG_BOOM)
118             raise socket.timeout("timed out")
119
120     def readline(self, *args, **kwargs):
121         output_buffer.append(self.__class__.__name__ + ".readline")
122         self._fuse()
123         return super(self.__class__, self).readline(*args, **kwargs)
124
125
126 def step(debug):
127     output_buffer[:] = []
128
129     server_sock = eventlet.listen(('localhost', 0))
130     server_addr = server_sock.getsockname()
131     sock_wrap = NaughtySocketAcceptWrap(server_sock)
132
133     eventlet.spawn_n(
134         eventlet.wsgi.server,
135         debug=debug,
136         log=BufferLog,
137         max_size=128,
138         site=tests.wsgi_test.Site(),
139         sock=server_sock,
140     )
141
142     try:
143         # req #1 - normal
144         sock1 = eventlet.connect(server_addr)
145         sock1.settimeout(0.1)
146         fd1 = sock1.makefile('rwb')
147         fd1.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
148         fd1.flush()
149         tests.wsgi_test.read_http(sock1)
150
151         # let the server socket ops catch up, set bomb
152         eventlet.sleep(0)
153         output_buffer.append("arming...")
154         sock_wrap.arm()
155
156         # req #2 - old conn, post-arm - timeout
157         fd1.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
158         fd1.flush()
159         try:
160             tests.wsgi_test.read_http(sock1)
161             assert False, 'Expected ConnectionClosed exception'
162         except tests.wsgi_test.ConnectionClosed:
163             pass
164
165         fd1.close()
166         sock1.close()
167     finally:
168         # reset streams, then output trapped tracebacks
169         sock_wrap.unwrap()
170     # check output asserts in tests.wsgi_test.TestHttpd
171     # test_143_server_connection_timeout_exception
172
173     return output_buffer[:]
174
175
176 def main():
177     output_normal = step(debug=False)
178     output_debug = step(debug=True)
179
180     assert "timed out" in output_debug[-1], repr(output_debug)
181     # if the BOOM check fails, it's because our timeout didn't happen
182     # (if eventlet stops using file.readline() to read HTTP headers,
183     # for instance)
184     assert TAG_BOOM == output_debug[-2], repr(output_debug)
185     assert TAG_BOOM == output_normal[-1], repr(output_normal)
186     assert "Traceback" not in output_debug, repr(output_debug)
187     assert "Traceback" not in output_normal, repr(output_normal)
188     print("pass")
189
190 if __name__ == '__main__':
191     main()