crossbario / crossbario/autobahn-python

WebSocketClientFactory: unable to create new event loop

Open
#925 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

asyncio needs-investigation websocket
Dominant language
Python
Stars
2.5k
Forks
768
PR merge metrics
No merged PRs in 30d

Description

Hi all.

I am using Fedora 26 Workstation, with Python 3.6.2 and the asyncio backend of autobahn | python 17.9.3.

I have an issue where I am unable to create and use a new event loop after having closed another one using:

factory = WebSocketClientFactory("<URL>:443")
factory.protocol = MyClientProtocol
loop.run_until_complete(loop2.create_connection(factory, "<URL>", 443, ssl=True))

Here is a full code example, which demonstrates the issue:

import asyncio, json
from autobahn.asyncio.websocket import WebSocketClientProtocol, WebSocketClientFactory

class MyClientProtocol(WebSocketClientProtocol):

    def onConnect(self, response):
        print(response.peer)

    def onOpen(self):
        print("open")
        self.sendMessage(json.dumps({'command': 'subscribe', 'channel': "1010"}).encode("utf8"))

    def onMessage(self, payload, isBinary):
        print("message")
        print(json.loads(payload))

factory1 = WebSocketClientFactory("wss://api2.poloniex.com:443")
factory1.protocol = MyClientProtocol
loop1 = asyncio.get_event_loop()
loop1.run_until_complete(loop1.create_connection(factory1, "api2.poloniex.com", 443, ssl=True))
try:
    loop1.run_forever()
except KeyboardInterrupt:
    pass

loop1.close()

asyncio.set_event_loop(asyncio.new_event_loop())

factory2 = WebSocketClientFactory("wss://api2.poloniex.com:443")
factory2.protocol = MyClientProtocol
loop2 = asyncio.get_event_loop()
loop2.run_until_complete(loop2.create_connection(factory2, "api2.poloniex.com", 443, ssl=True))
try:
    loop2.run_forever()
except KeyboardInterrupt:
    pass

loop2.close()

Running the example, the first event loop executes correctly, and I get a connection. After Ctrl-C and running loop2.run_until_complete(loop2.create_connection( ... )) I get the following errors:

Fatal write error on socket transport
protocol: <asyncio.sslproto.SSLProtocol object at 0x7fe57419c7f0>
transport: <_SelectorSocketTransport fd=6>
Traceback (most recent call last):
  File "/usr/lib64/python3.6/asyncio/selector_events.py", line 762, in write
    n = self._sock.send(data)
OSError: [Errno 9] Bad file descriptor
Fatal error on SSL transport
protocol: <asyncio.sslproto.SSLProtocol object at 0x7fe57419c7f0>
transport: <_SelectorSocketTransport closing fd=6>
Traceback (most recent call last):
  File "/usr/lib64/python3.6/asyncio/selector_events.py", line 762, in write
    n = self._sock.send(data)
OSError: [Errno 9] Bad file descriptor

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib64/python3.6/asyncio/sslproto.py", line 648, in _process_write_backlog
    self._transport.write(chunk)
  File "/usr/lib64/python3.6/asyncio/selector_events.py", line 766, in write
    self._fatal_error(exc, 'Fatal write error on socket transport')
  File "/usr/lib64/python3.6/asyncio/selector_events.py", line 646, in _fatal_error
    self._force_close(exc)
  File "/usr/lib64/python3.6/asyncio/selector_events.py", line 658, in _force_close
    self._loop.call_soon(self._call_connection_lost, exc)
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 574, in call_soon
    self._check_closed()
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 357, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Exception in callback _SelectorSocketTransport._read_ready()
handle: <Handle _SelectorSocketTransport._read_ready()>
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/txaio/_common.py", line 63, in call_later
    self._buckets[real_time][1].append(call)
KeyError: 28880000

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib64/python3.6/asyncio/events.py", line 127, in _run
    self._callback(*self._args)
  File "/usr/lib64/python3.6/asyncio/selector_events.py", line 731, in _read_ready
    self._protocol.data_received(data)
  File "/usr/lib64/python3.6/asyncio/sslproto.py", line 503, in data_received
    ssldata, appdata = self._sslpipe.feed_ssldata(data)
  File "/usr/lib64/python3.6/asyncio/sslproto.py", line 204, in feed_ssldata
    self._handshake_cb(None)
  File "/usr/lib64/python3.6/asyncio/sslproto.py", line 619, in _on_handshake_complete
    self._app_protocol.connection_made(self._app_transport)
  File "/usr/lib/python3.6/site-packages/autobahn/asyncio/websocket.py", line 97, in connection_made
    self._connectionMade()
  File "/usr/lib/python3.6/site-packages/autobahn/websocket/protocol.py", line 3340, in _connectionMade
    WebSocketProtocol._connectionMade(self)
  File "/usr/lib/python3.6/site-packages/autobahn/websocket/protocol.py", line 1055, in _connectionMade
    self.onOpenHandshakeTimeout,
  File "/usr/lib/python3.6/site-packages/txaio/_common.py", line 72, in call_later
    self._notify_bucket, real_time,
  File "/usr/lib/python3.6/site-packages/txaio/aio.py", line 382, in call_later
    return self._config.loop.call_later(delay, real_call)
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 543, in call_later
    timer = self.call_at(self.time() + delay, callback, *args)
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 553, in call_at
    self._check_closed()
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 357, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed

I am able to renew the event loop in the following simpler asyncio example:

import asyncio

async def hello_world():
    print("Hello World!")

coro1 = hello_world()
loop1 = asyncio.get_event_loop()
loop1.run_until_complete(coro1)    # print "Hello World!"
loop1.close()

asyncio.set_event_loop(asyncio.new_event_loop())

coro2 = hello_world()
loop2 = asyncio.get_event_loop()
loop2.run_until_complete(coro2)    # print "Hello World!"
loop2.close()

assert(loop1 != loop2)

But not when using autobahn.asyncio WebSocketClientFactory.

A StackOverflow user has suggested that the problem might be dangling pointers in the Websocket implementaion (thread: https://stackoverflow.com/questions/47101290/asyncio-unable-to-create-new-event-loop), which leads me here. I am happy to provide further information and do more testing, but some guidance would be helpful.

All the best,
James.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the full example that closes loop1 and creates loop2, then inspect autobahn/asyncio/websocket.py and txaio/aio.py, both named in the traceback. Compare their loop and transport handling with the simpler asyncio example; done means the second WebSocket connection runs without closed-loop, bad-file-descriptor, or txaio errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.