-
-
Notifications
You must be signed in to change notification settings - Fork 178
AttributeError: 'NoneType' object has no attribute 'get_debug' #390
Comments
Hi. For good or for bad aiohttp test suite requires explicit loop everywhere. |
I've closed the issue to don't make a mess in asyncio bugtracker, please open new one in aiohttp project if needed. |
Isn't there a problem with asyncio since loop was provided to create_subprocess_exec() and it does not reach the lower levels of unix_events in time? It's 5 steps deep in the stack trace. |
Ok. |
I've reduced the script to the bare minimum. I did not use loop_context() here or place it in a unit test as it affects the global scope. The only difference with a normal execution is that the default event loop is not set. import asyncio
asyncio.set_event_loop(None)
loop = asyncio.new_event_loop()
async def read_lines():
process = await asyncio.create_subprocess_exec(
*["cat", __file__],
loop=loop,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.DEVNULL,
stdin=asyncio.subprocess.DEVNULL
)
while not process.stdout.at_eof():
line = await process.stdout.readline()
return await process.wait()
async def wait_for_response():
try:
await asyncio.wait_for(read_lines(), 0.1, loop=loop)
except asyncio.TimeoutError:
pass
loop.run_until_complete(wait_for_response()) Keep in mind, it does not fail every time, but about 1/5 on my system, which is a rather clean ubuntu 16.04. Full stack trace:
|
I couldn’t reproduce the issue but there is definitely something off here. In the example, assert asyncio.get_child_watcher()._loop is loop # Fail! It is actually bound to assert asyncio.get_child_watcher()._loop is not None # Fail! This explains why the stack trace reports an The culprit is in with events.get_child_watcher() as watcher: This creates the child watcher through self._watcher = SafeChildWatcher()
if isinstance(threading.current_thread(),
threading._MainThread):
self._watcher.attach_loop(self._local._loop) The root of the problem is the fact that the child watcher is created by the policy and not the loop. There's more information about child watchers in the PEP 3156. |
I did have some occurrences of .wait() hanging forever. Is there anything I can do to help resolve this issue? |
Now I understand the design better:
I can propose the following fix for _UnixSelectorEventLoop._make_subprocess_transport (untested): def _make_subprocess_transport(self, protocol, args, shell,
stdin, stdout, stderr, bufsize,
extra=None, **kwargs):
with events.get_child_watcher() as watcher:
if watcher._loop is not self and \
isinstance(threading.current_thread(), threading._MainThread):
watcher.attach_loop(self)
[...] It is also possible to move this test to Maybe some warnings could also be useful in case the main loop is not defined or not running. |
I've tested the proposed fix locally and it does seem to resolve the issue. Methodology:
Without the fix, dumps the stacktrace within a second. With the fix, runs forever as expected. (I also verified that it still behaves as expected) |
This is a nice workaround, albeit it feels a bit "hacky". Maybe we should modify Also, should we raise an exception if there is no main event loop and you're trying to still use subprocess? |
Testing async code is hard enough. Isolating the loop is the most reliable way I found to make sure the tests don't interfere with each other or randomly dump exceptions to the output. Throwing exceptions if the main loop is not set is probably not ideal from this perspective. |
Is it acceptable to have the
I guess in some cases, the sub-loops might be started first, and the main loop after. So raising an exception might be a bit too strict, but some warnings would be nice. |
Without exceptions you wouldn't know that your program doesn't work at all. If there is no main event loop, child watchers won't receive UNIX signals at all. I've only quickly glanced over current implementation, and it seems to be the case. Silently ignoring the problem will only make testing async code harder. |
I wanted to raise an exception in
I think the problem is that only main thread can receive signals, so you have to have something in the main thread to actually handle them. Maybe, when we don't have a main loop, we can install a regular handler (with |
That makes sense. Actually it would be even better to raise this exception in
Indeed... I'm not sure it's worth it 😄 |
Agree, let's not do it. If you have time to tackle the solution for this issue, it would be nice if you can submit a PR that:
|
There are 3 different approaches I can think of:
I think they're all valid solutions, with pros and cons. The first one requires to register a watcher and a signal even though they will probably not be used. The second one feels indeed a bit hackish, and the third one requires to add an optional But once this question is settled, I don't mind taking care of the PR. |
Maybe the second approach is better since it doesn't harm performance of @asvetlov what do you think? |
I've been thinking about it and there's a 4th option that I like better: it is not really a bug so there is nothing to fix. From the design point of view, it is up to the policy to decide how the watcher is supposed to run. So if one chooses to go explicit (i.e.
This could be added in |
That's actually rather elegant. I verified and it works. In this case, raising an exception would be a good solution. NoneType errors are misleading. |
Maybe 4th option isn't a bad idea. Let's keep things as is.
@vxgmichel Feel free to create a PR to make loop.subprocess error out when there is no main event loop to handle SIGCHLD. |
Closing this one (the relevant PR has been merged). |
Writing unit tests for asyncio code, I have a few tests that fail occasionally with this error. It seems to be timing-related.
I use loop_context() from aiohttp to make sure all tests are isolated.
The problem is essentially that _loop is not set by the time logging is attempted, which may be related to the loop closing and unregistering after wait_for() canceled the job.
In this case, the culprit is this one, but the pattern repeats often in the code.
The text was updated successfully, but these errors were encountered: