When you did the busy-looping, die you call yield() inside? Since dataAvailableForRead is s non-blocking function, it needs something to keep the event loop going or nothing will arrive.

Ah yes good point. Adding yield makes the busy-wait version work.

If you look at the output with the highest verbosity level (--vvvv or LogLevel.trace), does it print anything about "Socket event on fd ..." or "Connection was closed"? It's in libevent2_tcp.d/onSocketEvent where the path of execution should lead to lines 531, 549 and 557 if everything goes right.

Here's the output:
Connection was closed (fd 508).
resuming corresponding task...
resume
evbuffer_read 8 bytes (fd 508)
.. got 0 bytes
yield

It does detect the connection close in libevent and follow the path that you note, but it never seems to resume the fiber that was blocked waiting... not really sure why or how to debug.

Thanks for the help so far.