On Thu, May 3, 2018 at 8:52 PM, Dima Tisnek <dim...@gmail.com> wrote:

> My 2c: don't use py3.4; in fact don't use 3.5 either :)
> If you decide to support older Python versions, it's only fair that
> separate implementation may be needed.
>

I'd agree - focus Python 3.6+

>
> Re: overall problem, why not try the following:
> wrap your individual tasks in async def, where each staggers, connects and
> resolves and handles cancellation (if it didn't win the race).
> IMO that's easier to reason about, debug and works around your problem ;)
>
> On Fri, 4 May 2018 at 9:34 AM, twisteroid ambassador <
> twisteroid.ambassa...@gmail.com> wrote:
>
>> The real problem I'm playing with is implementing "happy eyeballs",
>> where I may have several sockets attempting to connect simultaneously,
>> and the first one to successfully connect gets used. I had the idea of
>>
>
Simpler is better ...  this isn't an asyncio example, but maybe the
readability (ymmv?  For me - very clearly readable) is worth a ponder:

https://github.com/dabeaz/curio/blob/master/README.rst#a-complex-example


> preparing all of the loop.sock_connect() coroutine objects in advance,
>> and scheduling them one by one on the loop, but wanted to make double
>> sure that the sockets won't start connecting before the coroutines are
>> scheduled. I wanted to write something like this:
>>
>> successful_socket = await
>> staggered_start([loop.sock_connect(socket.socket(), addr) for addr in
>> addresses])
>>
>> where async def staggered_start(coros) is some kind of reusable
>> scheduling logic. As it turns out, I can't actually depend on
>> loop.sock_connect() doing the Right Thing (TM) if I want to support
>> Python 3.4.
>>
>> On Fri, May 4, 2018 at 12:37 AM, Andrew Svetlov
>> <andrew.svet...@gmail.com> wrote:
>> > What real problem do you want to solve?
>> > Correct code should always use `await loop.sock_connect(sock, addr)`, it
>> > this case the behavior difference never hurts you.
>> >
>> > On Thu, May 3, 2018 at 7:04 PM twisteroid ambassador
>> > <twisteroid.ambassa...@gmail.com> wrote:
>> >>
>> >> Hi,
>> >>
>> >> tl;dr: coroutine functions and regular functions returning Futures
>> >> behave differently: the latter may start running immediately without
>> >> being scheduled on a loop, or even with no loop running. This might be
>> >> bad since the two are sometimes advertised to be interchangeable.
>> >>
>> >>
>> >> I find that sometimes I want to construct a coroutine object, store it
>> >> for some time, and run it later. Most times it works like one would
>> >> expect: I call a coroutine function which gives me a coroutine object,
>> >> I hold on to the coroutine object, I later await it or use
>> >> loop.create_task(), asyncio.gather(), etc. on it, and only then it
>> >> starts to run.
>> >>
>> >> However, I have found some cases where the "coroutine" starts running
>> >> immediately. The first example is loop.run_in_executor(). I guess this
>> >> is somewhat unsurprising since the passed function don't actually run
>> >> in the event loop. Demonstrated below with strace and the interactive
>> >> console:
>> >>
>> >> $ strace -e connect -f python3
>> >> Python 3.6.5 (default, Apr  4 2018, 15:01:18)
>> >> [GCC 7.3.1 20180303 (Red Hat 7.3.1-5)] on linux
>> >> Type "help", "copyright", "credits" or "license" for more information.
>> >> >>> import asyncio
>> >> >>> import socket
>> >> >>> s = socket.socket()
>> >> >>> loop = asyncio.get_event_loop()
>> >> >>> coro = loop.sock_connect(s, ('127.0.0.1', 80))
>> >> >>> loop.run_until_complete(asyncio.sleep(1))
>> >> >>> task = loop.create_task(coro)
>> >> >>> loop.run_until_complete(asyncio.sleep(1))
>> >> connect(3, {sa_family=AF_INET, sin_port=htons(80),
>> >> sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection
>> >> refused)
>> >> >>> s.close()
>> >> >>> s = socket.socket()
>> >> >>> coro2 = loop.run_in_executor(None, s.connect, ('127.0.0.1', 80))
>> >> strace: Process 13739 attached
>> >> >>> [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)
>> >>
>> >> >>> coro2
>> >> <Future pending cb=[_chain_future.<locals>._call_check_cancel() at
>> >> /usr/lib64/python3.6/asyncio/futures.py:403]>
>> >> >>> loop.run_until_complete(asyncio.sleep(1))
>> >> >>> coro2
>> >> <Future finished exception=ConnectionRefusedError(111, 'Connection
>> >> refused')>
>> >> >>>
>> >>
>> >> Note that with loop.sock_connect(), the connect syscall is only run
>> >> after loop.create_task() is called on the coroutine AND the loop is
>> >> running. On the other hand, as soon as loop.run_in_executor() is
>> >> called on socket.connect, the connect syscall gets called, without the
>> >> event loop running at all.
>> >>
>> >> Another such case is with Python 3.4.2, where even loop.sock_connect()
>> >> will run immediately:
>> >>
>> >> $ strace -e connect -f python3
>> >> Python 3.4.2 (default, Oct  8 2014, 10:45:20)
>> >> [GCC 4.9.1] on linux
>> >> Type "help", "copyright", "credits" or "license" for more information.
>> >> >>> import socket
>> >> >>> import asyncio
>> >> >>> loop = asyncio.get_event_loop()
>> >> >>> s = socket.socket()
>> >> >>> c = loop.sock_connect(s, ('127.0.0.1', 82))
>> >> connect(7, {sa_family=AF_INET, sin_port=htons(82),
>> >> sin_addr=inet_addr("127.0.0.1")}, 16) = -1ECONNREFUSED (Connection
>> >> refused)
>> >> >>> c
>> >> <Future finished exception=ConnectionRefusedError(111, 'Connection
>> >> refused')>
>> >> >>>
>> >>
>> >> In both these cases, the misbehaving "coroutine" aren't actually
>> >> defined as coroutine functions, but regular functions returning a
>> >> Future, which is probably why they don't act like coroutines. However,
>> >> coroutine functions and regular functions returning Futures are often
>> >> used interchangeably: Python docs Section 18.5.3.1 even says:
>> >>
>> >> > 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.
>> >>
>> >> In particular, both run_in_executor() and sock_connect() are
>> >> documented as coroutines.
>> >>
>> >> If an asyncio API may change from a function returning Future to a
>> >> coroutine function and vice versa any time, then one cannot rely on
>> >> the behavior of creating the "coroutine object" not running the
>> >> coroutine immediately. This seems like an important Gotcha waiting to
>> >> bite someone.
>> >>
>> >> Back to the scenario in the beginning. If I want to write a function
>> >> that takes coroutine objects and schedule them to run later, and some
>> >> coroutine objects turn out to be misbehaving like above, then they
>> >> will run too early. To avoid this, I could either 1. pass the
>> >> coroutine functions and their arguments separately "callback style",
>> >> 2. use functools.partial or lambdas, or 3. always pass in real
>> >> coroutine objects returned from coroutine functions defined with
>> >> "async def". Does this sound right?
>> >>
>> >> Thanks,
>> >>
>> >> twistero
>> >> _______________________________________________
>> >> Async-sig mailing list
>> >> Async-sig@python.org
>> >> https://mail.python.org/mailman/listinfo/async-sig
>> >> Code of Conduct: https://www.python.org/psf/codeofconduct/
>> >
>> > --
>> > Thanks,
>> > Andrew Svetlov
>> _______________________________________________
>> Async-sig mailing list
>> Async-sig@python.org
>> https://mail.python.org/mailman/listinfo/async-sig
>> Code of Conduct: https://www.python.org/psf/codeofconduct/
>>
>
> _______________________________________________
> Async-sig mailing list
> Async-sig@python.org
> https://mail.python.org/mailman/listinfo/async-sig
> Code of Conduct: https://www.python.org/psf/codeofconduct/
>
>
_______________________________________________
Async-sig mailing list
Async-sig@python.org
https://mail.python.org/mailman/listinfo/async-sig
Code of Conduct: https://www.python.org/psf/codeofconduct/

Reply via email to