Yup, I think you've found a bug. wait_for() should definitely cancel the thing it's waiting for -- if you don't want that, you should call wait_for(shield(<coroutine_or_task>), <timeout>).
It's likely that most of the wait_for() (or its unittests :-) implementation predates the rethinking of cancellation. Can you help fixing this before Python 3.4 beta 3 goes out 9Jan 26 I think)? I'm really busy through the 31st so I need help. On Mon, Jan 20, 2014 at 7:41 AM, Gustavo Carneiro <[email protected]> wrote: > Hi. > > I spent a few hours chasing a nasty bug in either tulip or my code. I got > this traceback: > > Traceback (most recent call last): > File "/home/gjc/projects/mollytest/tests/base.py", line 63, in runTest > return loop.run_until_complete(self.runTestTask()) > File "lib/asyncio/base_events.py", line 177, in run_until_complete > return future.result() > File "lib/asyncio/futures.py", line 236, in result > raise self._exception > File "lib/asyncio/tasks.py", line 279, in _step > result = coro.send(value) > File "tests/450-api-place-hide-exchange.py", line 42, in runTestTask > lambda line: line[:2] == ['betslip_account_added', '3'] and line[5] == > 'dummyex')), > File "tests/450-api-place-hide-exchange.py", line 35, in wait_for_line > line = yield from api.read_async_line() > File "/home/gjc/projects/mollytest/mollyapiclient.py", line 68, in > read_async_line > data = yield from read > File "lib/asyncio/streams.py", line 321, in readline > assert self._waiter is None > AssertionError > > After much debugging I discovered that: > > 1. That assertion fails when there are two concurrent StreamReader read > operations; > > 2. I eventually found out that the "previous" running read operation was > this one: > > # self.async_reader is a StreamReader object > data = yield from asyncio.wait_for(self.async_reader.readline(), timeout) > > In the above code, if the timeout actually happens, then > self.async_reader.readline(), which has been converted to a Task by > asyncio.wait_for(), is left running. After the timeout, my script keeps > running, but the next call to StreamReader.readline() will fail and generate > the above traceback because the previous Task is still running. I confirmed > this by adding a "fut.cancel()" call to tasks.wait_for() right before "raise > futures.TimeoutError()", which made the problem go away. > > I was a bit surprised by the behaviour of wait_for(). If wait_for() creates > a Task, I reckon it should be the one to cancel it when the timeout happens. > If you think shouldn't cancel a Task, then I think wait_for() shouldn't > create it in the first place, and so it should accept only Task as > parameter, and not coroutine (generator): in this case it's the application > programmer who should take care of cancelling the task on timeout. > > Thoughts? > > -- > Gustavo J. A. M. Carneiro > Gambit Research LLC > "The universe is always one step beyond logic." -- Frank Herbert -- --Guido van Rossum (python.org/~guido)
