<div dir="auto">Depending on the coroutine*not* running sounds like asking for trouble.</div><br><div class="gmail_quote"><div dir="ltr">On Thu, May 3, 2018, 09:38 Andrew Svetlov <<a href="mailto:andrew.svetlov@gmail.com">andrew.svetlov@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">What real problem do you want to solve?<br>Correct code should always use `await loop.sock_connect(sock, addr)`, it this case the behavior difference never hurts you.</div><br><div class="gmail_quote"><div dir="ltr">On Thu, May 3, 2018 at 7:04 PM twisteroid ambassador <<a href="mailto:twisteroid.ambassador@gmail.com" target="_blank" rel="noreferrer">twisteroid.ambassador@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi,<br>
<br>
tl;dr: coroutine functions and regular functions returning Futures<br>
behave differently: the latter may start running immediately without<br>
being scheduled on a loop, or even with no loop running. This might be<br>
bad since the two are sometimes advertised to be interchangeable.<br>
<br>
<br>
I find that sometimes I want to construct a coroutine object, store it<br>
for some time, and run it later. Most times it works like one would<br>
expect: I call a coroutine function which gives me a coroutine object,<br>
I hold on to the coroutine object, I later await it or use<br>
loop.create_task(), asyncio.gather(), etc. on it, and only then it<br>
starts to run.<br>
<br>
However, I have found some cases where the "coroutine" starts running<br>
immediately. The first example is loop.run_in_executor(). I guess this<br>
is somewhat unsurprising since the passed function don't actually run<br>
in the event loop. Demonstrated below with strace and the interactive<br>
console:<br>
<br>
$ strace -e connect -f python3<br>
Python 3.6.5 (default, Apr  4 2018, 15:01:18)<br>
[GCC 7.3.1 20180303 (Red Hat 7.3.1-5)] on linux<br>
Type "help", "copyright", "credits" or "license" for more information.<br>
>>> import asyncio<br>
>>> import socket<br>
>>> s = socket.socket()<br>
>>> loop = asyncio.get_event_loop()<br>
>>> coro = loop.sock_connect(s, ('127.0.0.1', 80))<br>
>>> loop.run_until_complete(asyncio.sleep(1))<br>
>>> task = loop.create_task(coro)<br>
>>> loop.run_until_complete(asyncio.sleep(1))<br>
connect(3, {sa_family=AF_INET, sin_port=htons(80),<br>
sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection<br>
refused)<br>
>>> s.close()<br>
>>> s = socket.socket()<br>
>>> coro2 = loop.run_in_executor(None, s.connect, ('127.0.0.1', 80))<br>
strace: Process 13739 attached<br>
>>> [pid 13739] connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection refused)<br>
<br>
>>> coro2<br>
<Future pending cb=[_chain_future.<locals>._call_check_cancel() at<br>
/usr/lib64/python3.6/asyncio/futures.py:403]><br>
>>> loop.run_until_complete(asyncio.sleep(1))<br>
>>> coro2<br>
<Future finished exception=ConnectionRefusedError(111, 'Connection refused')><br>
>>><br>
<br>
Note that with loop.sock_connect(), the connect syscall is only run<br>
after loop.create_task() is called on the coroutine AND the loop is<br>
running. On the other hand, as soon as loop.run_in_executor() is<br>
called on socket.connect, the connect syscall gets called, without the<br>
event loop running at all.<br>
<br>
Another such case is with Python 3.4.2, where even loop.sock_connect()<br>
will run immediately:<br>
<br>
$ strace -e connect -f python3<br>
Python 3.4.2 (default, Oct  8 2014, 10:45:20)<br>
[GCC 4.9.1] on linux<br>
Type "help", "copyright", "credits" or "license" for more information.<br>
>>> import socket<br>
>>> import asyncio<br>
>>> loop = asyncio.get_event_loop()<br>
>>> s = socket.socket()<br>
>>> c = loop.sock_connect(s, ('127.0.0.1', 82))<br>
connect(7, {sa_family=AF_INET, sin_port=htons(82),<br>
sin_addr=inet_addr("127.0.0.1")}, 16) = -1ECONNREFUSED (Connection<br>
refused)<br>
>>> c<br>
<Future finished exception=ConnectionRefusedError(111, 'Connection refused')><br>
>>><br>
<br>
In both these cases, the misbehaving "coroutine" aren't actually<br>
defined as coroutine functions, but regular functions returning a<br>
Future, which is probably why they don't act like coroutines. However,<br>
coroutine functions and regular functions returning Futures are often<br>
used interchangeably: Python docs Section 18.5.3.1 even says:<br>
<br>
> Note: In this documentation, some methods are documented as coroutines, even if they are plain Python functions returning a Future. This is intentional to have a freedom of tweaking the implementation of these functions in the future.<br>
<br>
In particular, both run_in_executor() and sock_connect() are<br>
documented as coroutines.<br>
<br>
If an asyncio API may change from a function returning Future to a<br>
coroutine function and vice versa any time, then one cannot rely on<br>
the behavior of creating the "coroutine object" not running the<br>
coroutine immediately. This seems like an important Gotcha waiting to<br>
bite someone.<br>
<br>
Back to the scenario in the beginning. If I want to write a function<br>
that takes coroutine objects and schedule them to run later, and some<br>
coroutine objects turn out to be misbehaving like above, then they<br>
will run too early. To avoid this, I could either 1. pass the<br>
coroutine functions and their arguments separately "callback style",<br>
2. use functools.partial or lambdas, or 3. always pass in real<br>
coroutine objects returned from coroutine functions defined with<br>
"async def". Does this sound right?<br>
<br>
Thanks,<br>
<br>
twistero<br>
_______________________________________________<br>
Async-sig mailing list<br>
<a href="mailto:Async-sig@python.org" target="_blank" rel="noreferrer">Async-sig@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/async-sig" rel="noreferrer noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/async-sig</a><br>
Code of Conduct: <a href="https://www.python.org/psf/codeofconduct/" rel="noreferrer noreferrer" target="_blank">https://www.python.org/psf/codeofconduct/</a><br>
</blockquote></div>-- <br><div dir="ltr" class="m_-1966713641261218558gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><div>Thanks,</div>Andrew Svetlov</div></div>
_______________________________________________<br>
Async-sig mailing list<br>
<a href="mailto:Async-sig@python.org" target="_blank" rel="noreferrer">Async-sig@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/async-sig" rel="noreferrer noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/async-sig</a><br>
Code of Conduct: <a href="https://www.python.org/psf/codeofconduct/" rel="noreferrer noreferrer" target="_blank">https://www.python.org/psf/codeofconduct/</a><br>
</blockquote></div>