You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
_sock_connect_cb can be called twice resulting in InvalidStateError #69779
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee='https://github.com/gvanrossum'closed_at=<Date2015-11-19.21:35:54.971>created_at=<Date2015-11-10.07:16:34.030>labels= ['type-bug', 'expert-asyncio']
title='_sock_connect_cb can be called twice resulting in InvalidStateError'updated_at=<Date2016-02-10.22:39:48.267>user='https://github.com/thehesiod'
asyncio.selector_events.BaseSelectorEventLoop._sock_connect_cb is a callback based on the selector for a socket. There are certain situations when the selector triggers twice calling this callback twice, resulting in an InvalidStateError when it sets the Future to None. The way I triggered this was by having several parallel connections to the same host in a multiprocessing script. I suggest analyzing why this callback can be called twice and figuring out what the correct fix is. I monkey patched it by adding a fut.done() check at the top. If this information is not enough I can try to provide a sample script. Its currently reproducing in a fairly involved multiprocessing script.
Please show us how to repro -- there's no way we can figure out how this "impossible" event could happen in your code without understanding your code. Is it possible that multiprocessing forked your event loop or something similarly esoteric?
thehesiod commented on Nov 11, 2015
thehesiodmannequin
MannequinAuthor
More actions
Sorry for being obscure before, it was hard to pinpoint. I think I just figured it out! I had code like this in a subprocess:
defworker():
whileTrue:
obj=self.queue.get()
# do work with obj using asyncio http moduledefproducer():
nonlocalselfobj2=self.queue.get()
returnobj2workers= []
foriinrange(FILE_OP_WORKERS):
t=asyncio.ensure_future(worker())
t.add_done_callback(op_finished)
workers.append(t)
whileTrue:
f=loop.run_in_executor(None, producer)
obj=loop.run_until_complete(f)
t=async_queue.put(obj)
loop.run_until_complete(t)
loop.run_until_complete(asyncio.wait(workers))
where self.queue is a multiprocessing.Queue, and async_queue is an asyncio queue. The idea is that I have a process populating a multiprocessing queue, and I want to transfer it to an syncio queue while letting the workers do their thing.
Without knowing the underlying behavior, my theory is that when python blocks on the multiprocessing queue lock, it releases socket events to the async http module's selectors, and then when the async loop gets to the selectors they're released again.
If I switch the producer to instead use a queue.get_nowait and busy wait with asyncio.sleep I don't get the error...however this is not ideal is we're busy waiting.
Thanks!
thehesiod commented on Nov 11, 2015
thehesiodmannequin
MannequinAuthor
More actions
I'm going to close this as I've found a work-around, if I find a better test-case I'll open a new bug.
Actually, I just realized I had fixed it locally by changing the callback to the following:
429 def _sock_connect_cb(self, fut, sock, address):
430 if fut.cancelled() or fut.done():
431 return
so a fix is still needed, and I also verified this happens with python3.4 as well.
Sorry, the code you posted is still incomprehensible. E.g. I suppose your
worker doesn't really have
obj = self.queue.get()
but rather something like
obj = yield from async_queue.get()
But in the end, even with that hypothesis, I can't explain what you're
seeing, and I believe there is a bug related to bad mixing multiprocessing
and asyncio in some code you're not showing, and your "fix" just masks the
problem. Note that the code you posted doesn't touch sockets in any way,
while the issue you're seeing is related to sockets. So there *must* be
more to it.
thehesiod commented on Nov 11, 2015
thehesiodmannequin
MannequinAuthor
More actions
self.queue is not an async queue, as I stated above its a multiprocessing queue. This code is to multiplex a multiprocessing queue to a async queue.
JustinMayfield commented on Nov 11, 2015
JustinMayfieldmannequin
Mannequin
More actions
I believe I'm seeing this bug in a non-threaded and non-forked env.
I'm using aiohttp to create several dozens of HTTP connections to the same server (an async tornado web server). Nothing special is being done around the event loop creation (standard get_event_loop()). However in my system the event loop is frequently stopped, via ioloop.stop(), and restarted via ioloop.run_forever(). I'm not sure this is related to the issue yet, but it's worth mentioning.
I can't provide simplified test code just yet, but I can reproduce in my env with nearly 100% odds when doing a full system test. Attached is a sample backtrace.
thehesiod commented on Nov 11, 2015
thehesiodmannequin
MannequinAuthor
More actions
Perhaps I'm doing something really stupid, but I was able to reproduce the two issues I'm having with the following sample script. If you leave the monkey patch disabled, you get the InvalidStateError, if you enable it, you get the ServerDisconnect errors that I'm currently seeing which I work-around with retries. Ideas?
I'm not sure if you guys are still listening on this closed bug but I think I've found another issue ;) I'm using python 3.5.1 + asyncio 3.4.3 with the latest aiobotocore (which uses aiohttp 0.21.0) and had two sessions (two TCPConnectors), one doing a multitude of GetObjects via HTTP1.1, and the other doing PutObject, and the PutObject session returns error 61 (connection refused) from the same _sock_connect_cb. It feels like a similar issue to the original. I'll see if I can get small testcase.
thehesiod commented on Feb 10, 2016
thehesiodmannequin
MannequinAuthor
More actions
update: its unrelated to the number of sessions or SSL, but instead to the number of concurrent aiohttp requests. When set to 500, I get the error, when set to 100 I do not.
JustinMayfield commented on Feb 10, 2016
JustinMayfieldmannequin
Mannequin
More actions
Alexander,
That sounds unrelated. I'd treat it as a new issue until you have concrete evidence to the contrary.
Also on face value it sounds like it might just be your operating systems open file limit. On OSX I think the default open file limit is in the hundreds (256 on my box). Generally on unix-oid platforms it can be checked and changed with the ulimit -n command.
Cheers
thehesiod commented on Feb 10, 2016
thehesiodmannequin
MannequinAuthor
More actions
sorry for disruption! ends up our router seems to be doing some kind of QoS limits on # of connections :(
Activity
thehesiod commentedon Nov 10, 2015
asyncio.selector_events.BaseSelectorEventLoop._sock_connect_cb is a callback based on the selector for a socket. There are certain situations when the selector triggers twice calling this callback twice, resulting in an InvalidStateError when it sets the Future to None. The way I triggered this was by having several parallel connections to the same host in a multiprocessing script. I suggest analyzing why this callback can be called twice and figuring out what the correct fix is. I monkey patched it by adding a fut.done() check at the top. If this information is not enough I can try to provide a sample script. Its currently reproducing in a fairly involved multiprocessing script.
gvanrossum commentedon Nov 10, 2015
Please show us how to repro -- there's no way we can figure out how this "impossible" event could happen in your code without understanding your code. Is it possible that multiprocessing forked your event loop or something similarly esoteric?
thehesiod commentedon Nov 11, 2015
Sorry for being obscure before, it was hard to pinpoint. I think I just figured it out! I had code like this in a subprocess:
where self.queue is a multiprocessing.Queue, and async_queue is an asyncio queue. The idea is that I have a process populating a multiprocessing queue, and I want to transfer it to an syncio queue while letting the workers do their thing.
Without knowing the underlying behavior, my theory is that when python blocks on the multiprocessing queue lock, it releases socket events to the async http module's selectors, and then when the async loop gets to the selectors they're released again.
If I switch the producer to instead use a queue.get_nowait and busy wait with asyncio.sleep I don't get the error...however this is not ideal is we're busy waiting.
Thanks!
thehesiod commentedon Nov 11, 2015
I'm going to close this as I've found a work-around, if I find a better test-case I'll open a new bug.
thehesiod commentedon Nov 11, 2015
Actually, I just realized I had fixed it locally by changing the callback to the following:
429 def _sock_connect_cb(self, fut, sock, address):
430 if fut.cancelled() or fut.done():
431 return
so a fix is still needed, and I also verified this happens with python3.4 as well.
thehesiod commentedon Nov 11, 2015
clarification, adding the fut.done() check, or monkey patching:
orig_sock_connect_cb = asyncio.selector_events.BaseSelectorEventLoop._sock_connect_cb
def _sock_connect_cb(self, fut, sock, address):
if fut.done(): return
return orig_sock_connect_cb(self, fut, sock, address)
gvanrossum commentedon Nov 11, 2015
Sorry, the code you posted is still incomprehensible. E.g. I suppose your
worker doesn't really have
but rather something like
But in the end, even with that hypothesis, I can't explain what you're
seeing, and I believe there is a bug related to bad mixing multiprocessing
and asyncio in some code you're not showing, and your "fix" just masks the
problem. Note that the code you posted doesn't touch sockets in any way,
while the issue you're seeing is related to sockets. So there *must* be
more to it.
thehesiod commentedon Nov 11, 2015
self.queue is not an async queue, as I stated above its a multiprocessing queue. This code is to multiplex a multiprocessing queue to a async queue.
JustinMayfield commentedon Nov 11, 2015
I believe I'm seeing this bug in a non-threaded and non-forked env.
System:
OSX 10.11.1 (15B42)
Python 3.5.0 (from brew install)
I'm using aiohttp to create several dozens of HTTP connections to the same server (an async tornado web server). Nothing special is being done around the event loop creation (standard get_event_loop()). However in my system the event loop is frequently stopped, via ioloop.stop(), and restarted via ioloop.run_forever(). I'm not sure this is related to the issue yet, but it's worth mentioning.
I can't provide simplified test code just yet, but I can reproduce in my env with nearly 100% odds when doing a full system test. Attached is a sample backtrace.
thehesiod commentedon Nov 11, 2015
Perhaps I'm doing something really stupid, but I was able to reproduce the two issues I'm having with the following sample script. If you leave the monkey patch disabled, you get the InvalidStateError, if you enable it, you get the ServerDisconnect errors that I'm currently seeing which I work-around with retries. Ideas?
gvanrossum commentedon Nov 11, 2015
I wonder if the bug is in aiohttp? The code you show is still too complex
to debug for me.
27 remaining items
JustinMayfield commentedon Nov 19, 2015
Ha, email race.
Regarding rev 2, the updated docstring and scheduled stop looks good along with alleviating the confusion I mentioned.
I'm not sure about your warning comment; Perhaps that's a patch I didn't lay eyes on.
Cheers.
gvanrossum commentedon Nov 19, 2015
No, I mentioned the idea of a warning in the thread on the
python-tulip mailing list, but decided not to do it after all.
JustinMayfield commentedon Nov 19, 2015
I see. Seems like good discussion over there. I joined up.
gvanrossum commentedon Nov 19, 2015
OK, here's another revision of the patch, setting the timeout passed to the selector to 0 when the loop is pre-stopped.
gvanrossum commentedon Nov 19, 2015
OK, another revision, keep the mock selector.
gvanrossum commentedon Nov 19, 2015
Whoops. Hopefully this one's right.
python-dev commentedon Nov 19, 2015
New changeset 9b3144716d17 by Guido van Rossum in branch '3.4':
Issue bpo-25593: Change semantics of EventLoop.stop().
https://hg.python.org/cpython/rev/9b3144716d17
New changeset 158cc5701488 by Guido van Rossum in branch '3.5':
Issue bpo-25593: Change semantics of EventLoop.stop(). (Merge 3.4->3.5)
https://hg.python.org/cpython/rev/158cc5701488
New changeset 2ebe03a94f8f by Guido van Rossum in branch 'default':
Issue bpo-25593: Change semantics of EventLoop.stop(). (Merge 3.5->3.6)
https://hg.python.org/cpython/rev/2ebe03a94f8f
gvanrossum commentedon Nov 19, 2015
Hopefully this is it!
thehesiod commentedon Feb 10, 2016
I'm not sure if you guys are still listening on this closed bug but I think I've found another issue ;) I'm using python 3.5.1 + asyncio 3.4.3 with the latest aiobotocore (which uses aiohttp 0.21.0) and had two sessions (two TCPConnectors), one doing a multitude of GetObjects via HTTP1.1, and the other doing PutObject, and the PutObject session returns error 61 (connection refused) from the same _sock_connect_cb. It feels like a similar issue to the original. I'll see if I can get small testcase.
thehesiod commentedon Feb 10, 2016
update: its unrelated to the number of sessions or SSL, but instead to the number of concurrent aiohttp requests. When set to 500, I get the error, when set to 100 I do not.
JustinMayfield commentedon Feb 10, 2016
Alexander,
That sounds unrelated. I'd treat it as a new issue until you have concrete evidence to the contrary.
Also on face value it sounds like it might just be your operating systems open file limit. On OSX I think the default open file limit is in the hundreds (256 on my box). Generally on unix-oid platforms it can be checked and changed with the
ulimit -ncommand.Cheers
thehesiod commentedon Feb 10, 2016
sorry for disruption! ends up our router seems to be doing some kind of QoS limits on # of connections :(