dabeaz / curio

Good Curio!
Other
4.02k stars 241 forks source link

Large timeouts cause IndexError #269

Closed kdart closed 6 years ago

kdart commented 6 years ago

When using the timeout_after wrapper with a large timeout value you'll get an IndexError.

Here's a little test program:

#!/usr/bin/env python3.6

"""
Demonstrate curio timeout bug.

"""

import sys

import curio

def test(argv):
    timeout = float(argv[1]) if len(argv) > 1 else None
    coro = _run_test()
    if timeout:
        coro = curio.timeout_after(timeout, coro)
    kern = curio.Kernel()
    return kern.run(coro, shutdown=True)

async def _run_test(sleeptime=10.0):
    print(f"sleeping {sleeptime} seconds.")
    await curio.sleep(sleeptime)

if __name__ == "__main__":
    test(sys.argv)

# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab

When run like this, it works: ~/tmp ⇒ python3.6 curio_timeout_bug.py 15 sleeping 10.0 seconds.

Or, like this, you get the expected TaskTimeout error. ~/tmp ⇒ python3.6 curio_timeout_bug.py 5

sleeping 10.0 seconds. Traceback (most recent call last): File "curio_timeout_bug.py", line 27, in test(sys.argv) File "curio_timeout_bug.py", line 19, in test return kern.run(coro, shutdown=True) File "/home/keith/opensrc/curio/curio/kernel.py", line 180, in run raise ret_exc File "/home/keith/opensrc/curio/curio/kernel.py", line 738, in _run_coro trap = current._send(current.next_value) File "/home/keith/opensrc/curio/curio/task.py", line 166, in _task_runner self.next_value = await coro File "/home/keith/opensrc/curio/curio/task.py", line 859, in _timeout_after_func return await coro File "curio_timeout_bug.py", line 24, in _run_test await curio.sleep(sleeptime) File "/home/keith/opensrc/curio/curio/task.py", line 600, in sleep return await _sleep(seconds, False) File "/home/keith/opensrc/curio/curio/traps.py", line 82, in _sleep return (yield (_trap_sleep, clock, absolute)) curio.errors.TaskTimeout: 85873.667945047

But with some larger number, an IndexError when coro ends: ~/tmp ⇒ python3.6 curio_timeout_bug.py 32000 sleeping 10.0 seconds.

Task Crash: Task(id=2, name='_timeout_after_func', state='TERMINATED') Traceback (most recent call last): File "/home/keith/opensrc/curio/curio/kernel.py", line 741, in _run_coro trap = current._throw(current.next_exc) File "/home/keith/opensrc/curio/curio/task.py", line 166, in _task_runner self.next_value = await coro File "/home/keith/opensrc/curio/curio/task.py", line 859, in _timeout_after_func return await coro File "/home/keith/opensrc/curio/curio/task.py", line 787, in aexit current_clock = await _unset_timeout(self._prior) File "/home/keith/opensrc/curio/curio/traps.py", line 148, in _unset_timeout return (yield (_trap_unset_timeout, previous)) File "/home/keith/opensrc/curio/curio/kernel.py", line 747, in _run_coro trapstrap[0] File "/home/keith/opensrc/curio/curio/kernel.py", line 567, in _trap_unset_timeout _set_timeout(previous) File "/home/keith/opensrc/curio/curio/kernel.py", line 345, in _set_timeout sleepq.cancel((current.id, sleep_type), getattr(current, sleep_type)) File "/home/keith/opensrc/curio/curio/timequeue.py", line 162, in cancel while self.far_deadlines[bucketno] <= expires and bucketno < 8: IndexError: list index out of range Traceback (most recent call last): File "curio_timeout_bug.py", line 27, in test(sys.argv) File "curio_timeout_bug.py", line 19, in test return kern.run(coro, shutdown=True) File "/home/keith/opensrc/curio/curio/kernel.py", line 180, in run raise ret_exc File "/home/keith/opensrc/curio/curio/kernel.py", line 741, in _run_coro trap = current._throw(current.next_exc) File "/home/keith/opensrc/curio/curio/task.py", line 166, in _task_runner self.next_value = await coro File "/home/keith/opensrc/curio/curio/task.py", line 859, in _timeout_after_func return await coro File "/home/keith/opensrc/curio/curio/task.py", line 787, in aexit current_clock = await _unset_timeout(self._prior) File "/home/keith/opensrc/curio/curio/traps.py", line 148, in _unset_timeout return (yield (_trap_unset_timeout, previous)) File "/home/keith/opensrc/curio/curio/kernel.py", line 747, in _run_coro trapstrap[0] File "/home/keith/opensrc/curio/curio/kernel.py", line 567, in _trap_unset_timeout _set_timeout(previous) File "/home/keith/opensrc/curio/curio/kernel.py", line 345, in _set_timeout sleepq.cancel((current.id, sleep_type), getattr(current, sleep_type)) File "/home/keith/opensrc/curio/curio/timequeue.py", line 162, in cancel while self.far_deadlines[bucketno] <= expires and bucketno < 8: IndexError: list index out of range

dabeaz commented 6 years ago

Interesting. This is probably something extremely minor (like a bounds check). Will take a quick look.

dabeaz commented 6 years ago

Just pushed a fix for this. A test that included the bounds check wasn't written in the right order.

kdart commented 6 years ago

Cool, fixed it. :-)