channels: ChannesLiveServerTestcase hanging

Here’s another issue I encountered…

What I expected to happen vs. what actually happened

This test should pass:

from channels.testing.live import ChannelsLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver

class LiveTests(ChannelsLiveServerTestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.selenium = WebDriver()
        cls.selenium.implicitly_wait(5)

    @classmethod
    def tearDownClass(cls):
        cls.selenium.quit()
        super().tearDownClass()

    def test_pass(self):
        pass

But it is hanging. Firefox opens and then nothing happens. In my “real” test it shows no URL and no content. I have to cancel the test.

You can even remove the selenium code showing the same behavior:

from channels.testing.live import ChannelsLiveServerTestCase

class LiveTests(ChannelsLiveServerTestCase):
    def test_pass(self):
        pass

It works with Channels 2.0.2 though. Therefore I think the issue is related to Channels somehow.

OS and runtime environment, and browser

MacOS Sierra 10.12.6, Firefox 58.0.2

The versions of Channels, Daphne, Django, Twisted, and your ASGI backend (channels_redis normally)

my-app==0.0.0
  - channels [required: ~=2.0, installed: 2.0.2] - Using the newest code base from GitHub
    - asgiref [required: ~=2.1, installed: 2.2.0] - Using the newest code base from GitHub
      - async-timeout [required: ~=2.0, installed: 2.0.0]
    - daphne [required: ~=2.0, installed: 2.1.0]
      - autobahn [required: >=0.18, installed: 18.3.1]
        - six [required: >=1.10.0, installed: 1.11.0]
        - txaio [required: >=2.7.0, installed: 2.9.0]
          - six [required: Any, installed: 1.11.0]
      - twisted [required: >=17.5, installed: 17.9.0]
        - Automat [required: >=0.3.0, installed: 0.6.0]
          - attrs [required: Any, installed: 17.4.0]
          - six [required: Any, installed: 1.11.0]
        - constantly [required: >=15.1, installed: 15.1.0]
        - hyperlink [required: >=17.1.1, installed: 18.0.0]
          - idna [required: >=2.5, installed: 2.6]
        - incremental [required: >=16.10.1, installed: 17.5.0]
        - zope.interface [required: >=4.0.2, installed: 4.4.3]
          - setuptools [required: Any, installed: 38.5.2]
    - Django [required: >=1.11, installed: 2.0.3]
      - pytz [required: Any, installed: 2018.3]
  - django [required: ~=2.0, installed: 2.0.3]
    - pytz [required: Any, installed: 2018.3]
psycopg2==2.7.4
pytest-asyncio==0.8.0
  - pytest [required: >=3.0.6, installed: 3.4.2]
    - attrs [required: >=17.2.0, installed: 17.4.0]
    - pluggy [required: >=0.5,<0.7, installed: 0.6.0]
    - py [required: >=1.5.0, installed: 1.5.2]
    - setuptools [required: Any, installed: 38.5.2]
    - six [required: >=1.10.0, installed: 1.11.0]
pytest-django==3.1.2
  - pytest [required: >=2.9, installed: 3.4.2]
    - attrs [required: >=17.2.0, installed: 17.4.0]
    - pluggy [required: >=0.5,<0.7, installed: 0.6.0]
    - py [required: >=1.5.0, installed: 1.5.2]
    - setuptools [required: Any, installed: 38.5.2]
    - six [required: >=1.10.0, installed: 1.11.0]
selenium==3.10.0

How I’m running Channels

pytest

Console logs and full tracebacks of any errors

pipenv run pytest tests/tests_selenium.py  --fulltrace
======================================= test session starts ========================================
platform darwin -- Python 3.6.4, pytest-3.4.2, py-1.5.2, pluggy-0.6.0
Django settings: tests.settings (from ini file)
rootdir: /Users/Daniel/git/my-app, inifile: setup.cfg
plugins: django-3.1.2, asyncio-0.8.0
collected 1 item                                                                                   

tests/tests_selenium.py ^C

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! KeyboardInterrupt !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

config = <_pytest.config.Config object at 0x10d803c18>, doit = <function _main at 0x10d744620>

    def wrap_session(config, doit):
        """Skeleton command line program"""
        session = Session(config)
        session.exitstatus = EXIT_OK
        initstate = 0
        try:
            try:
                config._do_configure()
                initstate = 1
                config.hook.pytest_sessionstart(session=session)
                initstate = 2
>               session.exitstatus = doit(config, session) or 0

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/main.py:100: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = <_pytest.config.Config object at 0x10d803c18>
session = <Session 'my-app'>

    def _main(config, session):
        """ default command line protocol for initialization, session,
        running tests and reporting. """
        config.hook.pytest_collection(session=session)
>       config.hook.pytest_runtestloop(session=session)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/main.py:138: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_HookCaller 'pytest_runtestloop'>, args = ()
kwargs = {'session': <Session 'my-app'>}, notincall = set()

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()
        if self.argnames:
            notincall = set(self.argnames) - set(['__multicall__']) - set(
                kwargs.keys())
            if notincall:
                warnings.warn(
                    "Argument(s) {} which are declared in the hookspec "
                    "can not be found in this hook call"
                    .format(tuple(notincall)),
                    stacklevel=2,
                )
>       return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/pluggy/__init__.py:617: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.config.PytestPluginManager object at 0x10d0c0f60>
hook = <_HookCaller 'pytest_runtestloop'>
methods = [<pluggy.HookImpl object at 0x10d815748>, <pluggy.HookImpl object at 0x10f4e47f0>]
kwargs = {'session': <Session 'my-app'>}

    def _hookexec(self, hook, methods, kwargs):
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook, methods, kwargs)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/pluggy/__init__.py:222: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook = <_HookCaller 'pytest_runtestloop'>
methods = [<pluggy.HookImpl object at 0x10d815748>, <pluggy.HookImpl object at 0x10f4e47f0>]
kwargs = {'session': <Session 'my-app'>}

    self._inner_hookexec = lambda hook, methods, kwargs: \
        hook.multicall(
            methods, kwargs,
>           firstresult=hook.spec_opts.get('firstresult'),
        )

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/pluggy/__init__.py:216: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

session = <Session 'my-app'>

    def pytest_runtestloop(session):
        if (session.testsfailed and
                not session.config.option.continue_on_collection_errors):
            raise session.Interrupted(
                "%d errors during collection" % session.testsfailed)
    
        if session.config.option.collectonly:
            return True
    
        for i, item in enumerate(session.items):
            nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
>           item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/main.py:161: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_HookCaller 'pytest_runtest_protocol'>, args = ()
kwargs = {'item': <TestCaseFunction 'test_pass'>, 'nextitem': None}, notincall = set()

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()
        if self.argnames:
            notincall = set(self.argnames) - set(['__multicall__']) - set(
                kwargs.keys())
            if notincall:
                warnings.warn(
                    "Argument(s) {} which are declared in the hookspec "
                    "can not be found in this hook call"
                    .format(tuple(notincall)),
                    stacklevel=2,
                )
>       return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/pluggy/__init__.py:617: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.config.PytestPluginManager object at 0x10d0c0f60>
hook = <_HookCaller 'pytest_runtest_protocol'>
methods = [<pluggy.HookImpl object at 0x10d826518>, <pluggy.HookImpl object at 0x10d826e80>, <pluggy.HookImpl object at 0x10d90e7f0>]
kwargs = {'item': <TestCaseFunction 'test_pass'>, 'nextitem': None}

    def _hookexec(self, hook, methods, kwargs):
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook, methods, kwargs)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/pluggy/__init__.py:222: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook = <_HookCaller 'pytest_runtest_protocol'>
methods = [<pluggy.HookImpl object at 0x10d826518>, <pluggy.HookImpl object at 0x10d826e80>, <pluggy.HookImpl object at 0x10d90e7f0>]
kwargs = {'item': <TestCaseFunction 'test_pass'>, 'nextitem': None}

    self._inner_hookexec = lambda hook, methods, kwargs: \
        hook.multicall(
            methods, kwargs,
>           firstresult=hook.spec_opts.get('firstresult'),
        )

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/pluggy/__init__.py:216: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <TestCaseFunction 'test_pass'>, nextitem = None

    def pytest_runtest_protocol(item, nextitem):
        item.ihook.pytest_runtest_logstart(
            nodeid=item.nodeid, location=item.location,
        )
>       runtestprotocol(item, nextitem=nextitem)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/runner.py:62: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <TestCaseFunction 'test_pass'>, log = True, nextitem = None

    def runtestprotocol(item, log=True, nextitem=None):
        hasrequest = hasattr(item, "_request")
        if hasrequest and not item._request:
            item._initrequest()
        rep = call_and_report(item, "setup", log)
        reports = [rep]
        if rep.passed:
            if item.config.option.setupshow:
                show_test_item(item)
            if not item.config.option.setuponly:
>               reports.append(call_and_report(item, "call", log))

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/runner.py:79: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <TestCaseFunction 'test_pass'>, when = 'call', log = True, kwds = {}

    def call_and_report(item, when, log=True, **kwds):
>       call = call_runtest_hook(item, when, **kwds)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/runner.py:158: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <TestCaseFunction 'test_pass'>, when = 'call', kwds = {}, hookname = 'pytest_runtest_call'

    def call_runtest_hook(item, when, **kwds):
        hookname = "pytest_runtest_" + when
        ihook = getattr(item.ihook, hookname)
>       return CallInfo(lambda: ihook(item=item, **kwds), when=when)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/runner.py:178: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'CallInfo' object has no attribute 'result'") raised in repr()] CallInfo object at 0x10f67b208>
func = <function call_runtest_hook.<locals>.<lambda> at 0x10f793ea0>, when = 'call'

    def __init__(self, func, when):
        #: context of invocation: one of "setup", "call",
        #: "teardown", "memocollect"
        self.when = when
        self.start = time()
        try:
>           self.result = func()

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/runner.py:192: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   return CallInfo(lambda: ihook(item=item, **kwds), when=when)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/runner.py:178: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_HookCaller 'pytest_runtest_call'>, args = ()
kwargs = {'item': <TestCaseFunction 'test_pass'>}, notincall = set()

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()
        if self.argnames:
            notincall = set(self.argnames) - set(['__multicall__']) - set(
                kwargs.keys())
            if notincall:
                warnings.warn(
                    "Argument(s) {} which are declared in the hookspec "
                    "can not be found in this hook call"
                    .format(tuple(notincall)),
                    stacklevel=2,
                )
>       return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/pluggy/__init__.py:617: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.config.PytestPluginManager object at 0x10d0c0f60>
hook = <_HookCaller 'pytest_runtest_call'>
methods = [<pluggy.HookImpl object at 0x10d8264a8>, <pluggy.HookImpl object at 0x10dfb0588>, <pluggy.HookImpl object at 0x10f4e4710>]
kwargs = {'item': <TestCaseFunction 'test_pass'>}

    def _hookexec(self, hook, methods, kwargs):
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook, methods, kwargs)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/pluggy/__init__.py:222: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook = <_HookCaller 'pytest_runtest_call'>
methods = [<pluggy.HookImpl object at 0x10d8264a8>, <pluggy.HookImpl object at 0x10dfb0588>, <pluggy.HookImpl object at 0x10f4e4710>]
kwargs = {'item': <TestCaseFunction 'test_pass'>}

    self._inner_hookexec = lambda hook, methods, kwargs: \
        hook.multicall(
            methods, kwargs,
>           firstresult=hook.spec_opts.get('firstresult'),
        )

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/pluggy/__init__.py:216: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <TestCaseFunction 'test_pass'>

    def pytest_runtest_call(item):
        _update_current_test_var(item, 'call')
        try:
>           item.runtest()

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/runner.py:109: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <TestCaseFunction 'test_pass'>

    def runtest(self):
        if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
>           self._testcase(result=self)

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/_pytest/unittest.py:174: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.tests_selenium.LiveTests testMethod=test_pass>
result = <TestCaseFunction 'test_pass'>

    def __call__(self, result=None):
        """
            Wrapper around default __call__ method to perform common Django test
            set up. This means that user-defined Test Cases aren't required to
            include a call to super().setUp().
            """
        testMethod = getattr(self, self._testMethodName)
        skipped = (
            getattr(self.__class__, "__unittest_skip__", False) or
            getattr(testMethod, "__unittest_skip__", False)
        )
    
        if not skipped:
            try:
>               self._pre_setup()

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/django/test/testcases.py:202: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.tests_selenium.LiveTests testMethod=test_pass>

    def _pre_setup(self):
        for connection in connections.all():
            if self._is_in_memory_db(connection):
                raise ImproperlyConfigured(
                    "ChannelLiveServerTestCase can not be used with in memory databases"
                )
    
        super(ChannelsLiveServerTestCase, self)._pre_setup()
    
        self._server_process = self.ProtocolServerProcess(
            self.host,
            get_default_application(),
        )
        self._server_process.start()
>       self._server_process.ready.wait()

/Users/daniel/.local/share/virtualenvs/env/lib/python3.6/site-packages/channels/testing/live.py:43: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <multiprocessing.synchronize.Event object at 0x10faa2f28>, timeout = None

    def wait(self, timeout=None):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
            else:
>               self._cond.wait(timeout)

/usr/local/Cellar/python/3.6.4_3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/multiprocessing/synchronize.py:361: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Condition(<Lock(owner=unknown)>, unknown)>, timeout = None

    def wait(self, timeout=None):
        assert self._lock._semlock._is_mine(), \
               'must acquire() condition before using wait()'
    
        # indicate that this thread is going to sleep
        self._sleeping_count.release()
    
        # release lock
        count = self._lock._semlock._count()
        for i in range(count):
            self._lock.release()
    
        try:
            # wait for notification or timeout
>           return self._wait_semaphore.acquire(True, timeout)
E           KeyboardInterrupt

/usr/local/Cellar/python/3.6.4_3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/multiprocessing/synchronize.py:262: KeyboardInterrupt
================================== no tests ran in 12.25 seconds ===================================

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 7
  • Comments: 30 (22 by maintainers)

Commits related to this issue

Most upvoted comments

Assuming you are using macOS, downgrade Channels to 2.0.2. pip3 install channels==2.0.2 If you are using Linux or Windows you shouldn’t need to take any special action.

I don’t have enough bandwidth to fix this for probably a month or two, so I am planning on putting in a documentation change in the meantime giving similar advice.

I’ve spent a couple more hours troubleshooting today.

Between Channels 2.0.2 and 2.1.x the implementation of ChannelsLiveServerTestCase was rewritten to use multiprocessing, so the implementation is fairly different.

The multiprocessing implementation crashes fairly deeply inside Twisted. It gets a “[Errno 9] Bad file descriptor” when trying to open a socket.

Failing code path:

ChannelsLiveServerTestCase._pre_setup()
  DaphneProcess.start()
    << forked process >>
      DaphneProcess.run()
        daphne.server.Server.run()
          ep = serverFromString(reactor, str(socket_description))
          listener = ep.listen(self.http_factory) --> twisted._TCPServerEndpoint.listen
            defer.execute(self._reactor.listenTCP, ...)
              twisted.AsyncioSelectorReactor.listenTCP(...)
                twisted.tcp.Port.startListening
                  twisted.FileDescriptor.startReading
                    twisted.AsyncioSelectorReactor.addReader(...)
                      asyncio._UnixSelectorEventLoop.add_reader(fd, ...)
                        ⚠️ Throws [Errno 9] Bad file descriptor
                      raise
                    implicit raise
                  implicit raise
                implicit raise
              return fail()
          listener.addErrback(self.listen_error)
            daphne.server.Server.listen_error(...)
              logger.critical("Listen failure: %s", failure.getErrorMessage())
                💬 Listen failure: [Errno 9] Bad file descriptor
              self.stop()

I’m not very familiar with Twisted, so I don’t have a lot of ideas for efficiently proceeding in investigating.

I wonder if Twisted is having trouble running inside of a forked context, which sometimes does strange things to file descriptors.