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

Reply via email to