|
| 1 | +''' |
| 2 | +This is a test for issue #96: |
| 3 | +https://github.com/HyperionGray/trio-websocket/issues/96 |
| 4 | +
|
| 5 | +This is not a part of the automated tests, because its prone to a race condition |
| 6 | +and I have not been able to trigger it deterministically. Instead, this "test" |
| 7 | +connects multiple times, triggering the bug on average once or twice out of |
| 8 | +every 10 connections. The bug causes an uncaught exception which causes the |
| 9 | +script to crash. |
| 10 | +
|
| 11 | +Note that the server handler and the client have `try/except ConnectionClosed` |
| 12 | +blocks. The bug in issue #96 is that the exception is raised in a background |
| 13 | +task, so it is not caught by the caller's try/except block. Instead, it crashes |
| 14 | +the nursery that the background task is running in. |
| 15 | +
|
| 16 | +Here's an example run where the test fails: |
| 17 | +
|
| 18 | +(venv) $ python tests/issue_96.py |
| 19 | +INFO:root:Connection 0 |
| 20 | +INFO:root:Connection 1 |
| 21 | +INFO:root:Connection 2 |
| 22 | +INFO:root:Connection 3 |
| 23 | +INFO:root:Connection 4 |
| 24 | +INFO:root:Connection 5 |
| 25 | +INFO:root:Connection 6 |
| 26 | +INFO:root:Connection 7 |
| 27 | +INFO:root:Connection 8 |
| 28 | +INFO:root:Connection 9 |
| 29 | +INFO:root:Connection 10 |
| 30 | +INFO:root:Connection 11 |
| 31 | +Traceback (most recent call last): |
| 32 | + File "tests/issue_96.py", line 58, in <module> |
| 33 | + trio.run(main) |
| 34 | + File "/home/mhaase/code/trio-websocket/venv/lib/python3.6/site-packages/trio/_core/_run.py", line 1337, in run |
| 35 | + raise runner.main_task_outcome.error |
| 36 | + File "tests/issue_96.py", line 54, in main |
| 37 | + nursery.cancel_scope.cancel() |
| 38 | + File "/home/mhaase/code/trio-websocket/venv/lib/python3.6/site-packages/trio/_core/_run.py", line 397, in __aexit__ |
| 39 | + raise combined_error_from_nursery |
| 40 | + File "/home/mhaase/code/trio-websocket/trio_websocket/_impl.py", line 327, in serve_websocket |
| 41 | + await server.run(task_status=task_status) |
| 42 | + File "/home/mhaase/code/trio-websocket/trio_websocket/_impl.py", line 1097, in run |
| 43 | + await trio.sleep_forever() |
| 44 | + File "/home/mhaase/code/trio-websocket/venv/lib/python3.6/site-packages/trio/_core/_run.py", line 397, in __aexit__ |
| 45 | + raise combined_error_from_nursery |
| 46 | + File "/home/mhaase/code/trio-websocket/venv/lib/python3.6/site-packages/trio/_highlevel_serve_listeners.py", line 129, in serve_listeners |
| 47 | + task_status.started(listeners) |
| 48 | + File "/home/mhaase/code/trio-websocket/venv/lib/python3.6/site-packages/trio/_core/_run.py", line 397, in __aexit__ |
| 49 | + raise combined_error_from_nursery |
| 50 | + File "/home/mhaase/code/trio-websocket/venv/lib/python3.6/site-packages/trio/_highlevel_serve_listeners.py", line 27, in _run_handler |
| 51 | + await handler(stream) |
| 52 | + File "/home/mhaase/code/trio-websocket/trio_websocket/_impl.py", line 1125, in _handle_connection |
| 53 | + await connection.aclose() |
| 54 | + File "/home/mhaase/code/trio-websocket/venv/lib/python3.6/site-packages/trio/_core/_run.py", line 397, in __aexit__ |
| 55 | + raise combined_error_from_nursery |
| 56 | + File "/home/mhaase/code/trio-websocket/trio_websocket/_impl.py", line 927, in _reader_task |
| 57 | + await handler(event) |
| 58 | + File "/home/mhaase/code/trio-websocket/trio_websocket/_impl.py", line 811, in _handle_connection_closed_event |
| 59 | + await self._write_pending() |
| 60 | + File "/home/mhaase/code/trio-websocket/trio_websocket/_impl.py", line 968, in _write_pending |
| 61 | + raise ConnectionClosed(self._close_reason) from None |
| 62 | +trio_websocket._impl.ConnectionClosed: <CloseReason code=1006 name=ABNORMAL_CLOSURE reason=None> |
| 63 | +
|
| 64 | +You may need to modify the sleep duration to reliably trigger the bug on your |
| 65 | +system. On a successful test, it will make 1,000 connections without any |
| 66 | +uncaught exceptions. |
| 67 | +''' |
| 68 | +import functools |
| 69 | +import logging |
| 70 | +from random import random |
| 71 | + |
| 72 | +import trio |
| 73 | +from trio_websocket import connect_websocket, ConnectionClosed, serve_websocket |
| 74 | + |
| 75 | + |
| 76 | +logging.basicConfig(level=logging.INFO) |
| 77 | + |
| 78 | + |
| 79 | +async def handler(request): |
| 80 | + ''' Reverse incoming websocket messages and send them back. ''' |
| 81 | + try: |
| 82 | + ws = await request.accept() |
| 83 | + await ws.send_message('issue96') |
| 84 | + await trio.sleep(random()/1000) |
| 85 | + await trio.aclose_forcefully(ws._stream) |
| 86 | + except ConnectionClosed: |
| 87 | + pass |
| 88 | + |
| 89 | + |
| 90 | +async def main(): |
| 91 | + async with trio.open_nursery() as nursery: |
| 92 | + serve = functools.partial(serve_websocket, handler, 'localhost', 8000, |
| 93 | + ssl_context=None) |
| 94 | + await nursery.start(serve) |
| 95 | + |
| 96 | + for n in range(1000): |
| 97 | + logging.info('Connection %d', n) |
| 98 | + try: |
| 99 | + connection = await connect_websocket(nursery, 'localhost', 8000, |
| 100 | + '/', use_ssl=False) |
| 101 | + await connection.get_message() |
| 102 | + await connection.aclose() |
| 103 | + except ConnectionClosed: |
| 104 | + pass |
| 105 | + |
| 106 | + nursery.cancel_scope.cancel() |
| 107 | + |
| 108 | + |
| 109 | +if __name__ == '__main__': |
| 110 | + trio.run(main) |
0 commit comments