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)

Most upvoted comments

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 be async_to_sync(self.channel_layer.group_add)('grpup', self.channel_name) instead. You want to convert the group_add method into a synchronous version and call it. See https://channels.readthedocs.io/en/latest/topics/channel_layers.html#synchronous-functions

If an async test runs some function which in turn runs something within async_to_sync you can wrap that function in sync_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.