On 14.3.2014. 19:33, Guido van Rossum wrote:
I haven't actually tried to run/debug this, but I've looked at the code
a bit. Let me explain what's happening in the demo program first.
(Thanks for the nice clean demo BTW!)
Thank you for replying. I was beginning to think there weren't any takers.
You call start_serving_pipe() and yield to ensure its callback is
entered. This creates a native pipe and wraps it in a Future ('f', set
on line 157 of windows_events.py in the current Tulip repo head). The
Future will be completed if/when an incoming connection is accepted, but
your program never gets there. Instead, you close the server object.
This will (IIUC) cause the Future to get an exception. The normal flow
(if the event loop weren't stopped) would be for this exception to wake
up the callback. The callback calls f.result() (line 149) which raises
that exception. The exception is caught in the "except OSError" clause
(line 158) and since the 'pipe' local is still None at this point, no
exception is logged.
>
But when you stop the loop, the callback never runs (it is stuck in the
loop's _ready queue). Then you close the loop and exit the program, and
everything gets GC'ed. At some point this discovers the Future with the
exception set. Its callback has been scheduled but not yet called (and
it will never be called). Because of this the Future's exception is
logged in the way you see ("Future/Task exception was never retrieved").
Just tested your theory, and you are right. However, connecting the
client still doesn't help, after that you are in the same position as
before (extended example attached).
I think that the clean solution would be to make the PipeServer aware of
its async accept operation, so that it can be cancelled in
PipeServer.close() before the pipe is closed.
I think the most reasonable response is to declare that there is a bug
in asyncio -- it shouldn't log unretrieved exceptions before the
callbacks have run, since a typical callback will retrieve the exception
and hence silence the log (perhaps substituting a different exception if
the callback doesn't handle it).
Looking at the code of set_exception() (in future.py, line 316) it looks
like in the Python 3.3 branch of the code, the tb logger isn't activated
until after all scheduled callbacks have also run (the event loop
promises that callbacks scheduled with call_soon() will be run in the
order they were scheduled). But in the 3.4 branch it immediately sets
the _log_traceback flag.
I have two suggestions for how you can verify my theory:
- If my analysis is right, you won't see this issue with Python 3.3.
>
- For Python 3.4, it could perhaps be fixed by putting the
"self._log_traceback = True" call in a callback and scheduling that.
Here's a tentative patch to try that:
diff -r 86fc55b570ff asyncio/futures.py
--- a/asyncio/futures.py Thu Mar 06 01:00:03 2014 +0100
+++ b/asyncio/futures.py Fri Mar 14 11:30:30 2014 -0700
@@ -314,7 +314,9 @@
self._state = _FINISHED
self._schedule_callbacks()
if _PY34:
- self._log_traceback = True
+ def _set_log_traceback():
+ self._log_traceback = True
+ self._loop.call_soon(_set_log_traceback)
else:
self._tb_logger = _TracebackLogger(exception, self._loop)
# Arrange for the logger to be activated after all callbacks
Correct, both suggestions run without displaying the error message.
import asyncio
loop = asyncio.ProactorEventLoop()
address = "\\\\.\\pipe\\DUMMY_PIPE"
@asyncio.coroutine
def start_and_stop_server():
[server] = yield from loop.start_serving_pipe(asyncio.Protocol, address)
# Allow the server to enter its loop.
yield from asyncio.sleep(0.1, loop=loop)
server.close()
loop.stop()
@asyncio.coroutine
def connect_client():
transport, protocol = yield from
loop.create_pipe_connection(asyncio.Protocol, address)
print("client connected")
transport.close()
def run_loop_a_bit_more():
@asyncio.coroutine
def dummy():
pass
loop.run_until_complete(dummy())
asyncio.async(start_and_stop_server(), loop=loop)
asyncio.async(connect_client(), loop=loop)
loop.run_forever()
#run_loop_a_bit_more() # <-- uncomment to apply workaround
loop.close()