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
- Tutorial: Document that ChannelsLiveServerTestCase hangs on macOS (#962) — committed to davidfstr/channels by davidfstr 6 years ago
- Tutorial: Document that ChannelsLiveServerTestCase hangs on macOS (#962) (#1167) — committed to django/channels by davidfstr 6 years ago
- Change multiprocessing for DaphneProcess to use 'spawn' instead of the default 'fork'. Fixes channels bug https://github.com/django/channels/issues/962 Also allow the Server to be passed a logger, w... — committed to shjohnson-pi/daphne by shjohnson-pi 5 years ago
- Change multiprocessing for DaphneProcess to use 'spawn' instead of the default 'fork'. Fixes channels bug https://github.com/django/channels/issues/962 Also allow the Server to be passed a logger, w... — committed to shjohnson-pi/daphne by shjohnson-pi 5 years ago
- Change multiprocessing for DaphneProcess to use 'spawn' instead of the default 'fork'. Fixes channels bug https://github.com/django/channels/issues/962 Also allow the Server to be passed a logger, w... — committed to shjohnson-pi/daphne by shjohnson-pi 5 years ago
- Refs #962 -- Removed tutorial note that tests didn't run on macOS. Fixed by https://github.com/django/daphne/pull/247. — committed to carltongibson/channels by carltongibson 5 years ago
- Refs #962 -- Removed tutorial note that tests didn't run on macOS. (#1377) Fixed by https://github.com/django/daphne/pull/247. — committed to django/channels by carltongibson 5 years ago
- Change multiprocessing for DaphneProcess to use 'spawn' instead of the default 'fork'. Fixes channels bug https://github.com/django/channels/issues/962 Also allow the Server to be passed a logger, w... — committed to shjohnson-pi/daphne by shjohnson-pi 5 years ago
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:
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.