Document onUserError
I'm writing a game application using autobahn, and first I encountered this issue at the application level and then realised it is relevant to the library.
I omit the imports assuming they are obvious.
Usecase
When opening a connection (or generally firing a callback of a protocol) I would like to shedule some async task. So I write
class PlayerProtocol(WebSocketServerProtocol):
def onOpen(self):
asyncio.ensure_future(begin(), loop=loop)
Everything's ok, but when I occationnally break functionality of a coroutine begin()... nothing happens! No exception is raised. B-but why? I started an investigation.
The Rule
This investigation lead me to a simple rule: one should either await the task or handle the exceptions in a callback. What happens is pretty strightforward: the task is done with an exception, and as no one awaits for its result, the result (which is an exception in the case) is thrown away.
The same in the library
So I ended up with the callback that logs the exception, but glanced in a cource code and saw in asyncio/websocket.py:WebSocketAdapterProtocol these lines:
def _onOpen(self):
res = self.onOpen()
if yields(res):
ensure_future(res)
This is totally the same pattern that I implemented in my app: if self.onOpen() is a function, we're done, and if it is a coroutine (roughly), we shedule it. The other callbacks are the same.
Here we have an owerwhelming knowledge that onOpen and others may be a coroutine, but the docs says nothing about it. And I could throw away dancing with callbacks and just await begin()!. This is another issue, I will open it next. But...
But what if things in onOpen go wrong? Say,
class TrappingProtocol(WebSocketServerProtocol):
async def onOpen(self):
raise ValueError
Then we run it and try to connect with a simple client. And..? You are right -- nothing happen. The user (a programmer) did not even notice the error.
A simplified example that should be enough to reproduce:
class TrappingProtocol(WebSocketServerProtocol):
async def onOpen(self):
raise ValueError
if __name__ == '__main__':
loop = asyncio.get_event_loop()
factory = WebSocketServerFactory()
factory.protocol = TrappingProtocol
coro = loop.create_server(factory, HOST, PORT)
server = loop.run_until_complete(coro)
try:
print('Server created.')
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
server.close()
loop.close()
A solution
Actually in this case I exepect that the server crash. Now in my app I implemented a callback which just logs the exception and stops the loop. Opening to discuss whether this solution is enough and ready to PR then if you giude me a little. (never did a PR on github thuogh)
Cheers!
#485 may be related.
For now, pls see https://autobahn.readthedocs.io/en/latest/reference/autobahn.wamp.html?highlight=onUserError#autobahn.wamp.interfaces.ISession.onUserError
A proper documentation/example would be even better;)