The normal cleanup procedure for epoll and win_iocp proactors waits for all async activity to complete before freeing memory.
pn_proactor_disconnect can't actually force a close so it launches a separate async activity piggy-backed on the internal wake mechanism of any connections to be closed.
If the disconnect is happening at the same time as a separate thread doing a normal close, a new wake can result after concluding there are none left.
The solution is to mark the connection as "already awake" before entering the cleanup code so new wakes are no-ops. The libuv proactor doesn't need this as the disconnect function is managed within libuv and never competes with the normal close operation.