How does asyncio.sleep work with negative values?
<p>I decided to implement sleep sort (<a href="https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort" rel="nofollow">https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort</a>) using Python's <code>asyncio</code> when I made a strange discovery: it works with negative values (and returns immediately with 0)!</p>

<p>Here is the code (you can run it here <a href="https://repl.it/DYTZ" rel="nofollow">https://repl.it/DYTZ</a>):</p>

<pre><code>import asyncio
import random

async def sleepy(value):
    return await asyncio.sleep(value, result=value)


async def main(input_values):
    result = []
    for sleeper in asyncio.as_completed(map(sleepy, input_values)):
        result.append(await sleeper)
    print(result)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    input_values = list(range(-5, 6))
    random.shuffle(input_values)
    loop.run_until_complete(main(input_values))
</code></pre>

<p>The code takes 5 seconds to execute, as expected, but the result is always <code>[0, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5]</code>. I can understand 0 returning immediately, but how are the negative values coming back in the right order?</p>

<p>Well, looking at the <a href="https://hg.python.org/cpython/file/3.5/Lib/asyncio/tasks.py#l503" rel="nofollow">source</a>:</p>

<ul>
<li><code>delay == 0</code> is special-cased to return immediately, it doesn't even try to sleep.</li>
<li>Non-zero delay calls <code>events.get_event_loop()</code>. Since there are no calls to <code>events.set_event_loop_policy(policy)</code> in <code>asyncio.tasks</code>, it would seem to fall back on the default unless it's already been set somewhere else, and <a href="https://hg.python.org/cpython/file/3.5/Lib/asyncio/events.py#l610" rel="nofollow">the default is <code>asyncio.DefaultEventLoopPolicy</code></a>.</li>
<li>This is not defined in <code>events.py</code>, because it's different on <a href="https://hg.python.org/cpython/file/3.5/Lib/asyncio/windows_events.py" rel="nofollow">Windows</a> from on <a href="https://hg.python.org/cpython/file/3.5/Lib/asyncio/unix_events.py" rel="nofollow">UNIX</a>.</li>
<li>Either way, <code>sleep</code> calls <code>loop.create_future()</code>. That's defined a few inheritances back, over in <a href="https://hg.python.org/cpython/file/3.5/Lib/asyncio/base_events.py#l221" rel="nofollow"><code>base_events.BaseEventLoop</code></a>. It's just a simple call to the <code>Future()</code> constructor, no significant logic.</li>
<li><p>From the instance of <code>Future</code> it delegates back to the loop, as follows:</p>

<pre><code>future._loop.call_later(delay,
                        futures._set_result_unless_cancelled,
                        future, result)
</code></pre></li>
<li>That one is also in <code>BaseEventLoop</code>, and still doesn't directly handle the <code>delay</code> number: it calls <code>self.call_at</code>, adding the current time to the delay.</li>
<li><code>call_at</code> schedules and returns an <code>events.TimerHandle</code>, and the callback is to tell the <code>Future</code> it's done. The return value is only relevant if the task is to be cancelled, which it is automatically at the end for cleanup. The scheduling is the important bit.</li>
<li><code>_scheduled</code> is sorted via <code>heapq</code> - everything goes on there in sorted order, and timers sort by their <code>_when</code>. This is key.</li>
<li>Every time it checks, it strips out all cancelled scheduled things, then runs all remaining scheduled callbacks, in order, until it hits one that's not ready.</li>
</ul>

<p><strong>TL;DR:</strong></p>

<p>Sleeping with <code>asyncio</code> for a negative duration schedules tasks to be "ready" in the past. This means that they go to the top of the list of scheduled tasks, and are run as soon as the event loop checks. Effectively, 0 comes first because it doesn't even schedule, but everything else registers to the scheduler as "running late" and is handled immediately in order of how late it is.</p>

python sorting sleep python-asyncio