summaryrefslogtreecommitdiff
path: root/tests/wsgi_test_conntimeout.py
blob: d925a042b82544ba1624e72f1c97860bd71ce15c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
"""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

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):
        super(self.__class__, self).__init__(sock, mode, bufsize, close)
        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