channels: Cannot test synchronous consumer that includes async_to_sync
I am having a problem calling async_to_sync from python manage.py tests
. The relevant code is
class MyHandler(SyncConsumer):
def websocket_connect(self, event):
self.send({ "type": "websocket.accept"})
async_to_sync(self.channel_layer.group_add('grpup', self.channel_name))
Test looks like this – I use asynctest
because I don’t want this to be the only test in my project that uses pytest
syntax.
class ChannelTests(asynctest.TestCase):
async def test_websockets(self):
communicator = WebsocketCommunicator(MyHandler, path)
connected, subprotocol = await communicator.connect()
self.assertTrue(connected)
When I run this, I get RuntimeError: You cannot use AsyncToSync in the same thread as an async event loop - just await the async function directly.
But I’m actually not sure it ever gets to my async_to_sync call. Looking at the call stack, it seems to bomb on the previous line:
Traceback (most recent call last):
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/asynctest/case.py", line 297, in run
self._run_test_method(testMethod)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/asynctest/case.py", line 354, in _run_test_method
self.loop.run_until_complete(result)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/asynctest/case.py", line 224, in wrapper
return method(*args, **kwargs)
File "/Users/jonathanstray/anaconda/lib/python3.5/asyncio/base_events.py", line 387, in run_until_complete
return future.result()
File "/Users/jonathanstray/anaconda/lib/python3.5/asyncio/futures.py", line 274, in result
raise self._exception
File "/Users/jonathanstray/anaconda/lib/python3.5/asyncio/tasks.py", line 241, in _step
result = coro.throw(exc)
File "/Users/jonathanstray/PycharmProjects/cjworkbench/server/tests/test_websockets.py", line 26, in test_websockets
connected, subprotocol = await communicator.connect()
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/channels/testing/websocket.py", line 33, in connect
response = await self.receive_output(timeout)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/asgiref/testing.py", line 66, in receive_output
self.future.result()
File "/Users/jonathanstray/anaconda/lib/python3.5/asyncio/futures.py", line 274, in result
raise self._exception
File "/Users/jonathanstray/anaconda/lib/python3.5/asyncio/tasks.py", line 241, in _step
result = coro.throw(exc)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/channels/consumer.py", line 54, in __call__
await await_many_dispatch([receive, self.channel_receive], self.dispatch)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/channels/utils.py", line 48, in await_many_dispatch
await dispatch(result)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/asgiref/sync.py", line 110, in __call__
return await asyncio.wait_for(future, timeout=None)
File "/Users/jonathanstray/anaconda/lib/python3.5/asyncio/tasks.py", line 373, in wait_for
return (yield from fut)
File "/Users/jonathanstray/anaconda/lib/python3.5/asyncio/futures.py", line 361, in __iter__
yield self # This tells Task to wait for completion.
File "/Users/jonathanstray/anaconda/lib/python3.5/asyncio/tasks.py", line 296, in _wakeup
future.result()
File "/Users/jonathanstray/anaconda/lib/python3.5/asyncio/futures.py", line 274, in result
raise self._exception
File "/Users/jonathanstray/anaconda/lib/python3.5/concurrent/futures/thread.py", line 55, in run
result = self.fn(*self.args, **self.kwargs)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/channels/db.py", line 13, in thread_handler
return super().thread_handler(loop, *args, **kwargs)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/asgiref/sync.py", line 125, in thread_handler
return self.func(*args, **kwargs)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/channels/consumer.py", line 99, in dispatch
handler(message)
File "/Users/jonathanstray/PycharmProjects/cjworkbench/server/websockets.py", line 36, in websocket_connect
self.send({ "type": "websocket.accept"})
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/channels/consumer.py", line 107, in send
self.base_send(message)
File "/Users/jonathanstray/anaconda/lib/python3.5/site-packages/asgiref/sync.py", line 34, in __call__
"You cannot use AsyncToSync in the same thread as an async event loop - "
RuntimeError: You cannot use AsyncToSync in the same thread as an async event loop - just await the async function directly.
This is on OS X / django 1.11 / channels 2.0.2
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 1
- Comments: 15 (5 by maintainers)
Maybe not directly related, but there’s probably a problem with your code. The line
async_to_sync(self.channel_layer.group_add('grpup', self.channel_name))
should beasync_to_sync(self.channel_layer.group_add)('grpup', self.channel_name)
instead. You want to convert thegroup_add
method into a synchronous version and call it. See https://channels.readthedocs.io/en/latest/topics/channel_layers.html#synchronous-functionsIf an async test runs some function which in turn runs something within
async_to_sync
you can wrap that function insync_to_async
to get rid of the “You cannot use AsyncToSync in the same thread as an async event loop - just await the async function directly” error.