https://bugs.winehq.org/show_bug.cgi?id=54413
--- Comment #4 from Zeb Figura z.figura12@gmail.com --- Ah, I have a theory.
Because ws2_32 must have an internal socket list (which is effectly exactly what this test proves), it stands to reason that sockets must be removed from that list in closesocket().
That means that if you close the NT handle directly, via CloseHandle() or NtClose(), that per-process data won't be deleted, and a following call to a ws2_32 API will see that it's still there (and, for something like WSAGetOverlappedResult(), it *only* seems to be checking that it's a valid socket handle; it doesn't actually need to do anything with the handle, at least not if there's an event).
Testing this confirms that: create a socket, close it with CloseHandle(), then duplicate another socket into the same process and call WSAGetOverlappedResult() with the duplicate. The handle values will basically always be the same [it's known that Windows uses a free list for its handles], and as a result WSAGetOverlappedResult() will succeed.
As for where we're using CloseHandle() or NtClose(): that's iocp_async_read_closesocket(), which does indeed execute before this test.
As for why this failure is intermittent: Windows sometimes just randomly opens or closes process handles, I guess in internal threads. That's something that I encountered while testing that handles are allocated from a free list.
Fixing this is... tricky. Here's the problem: It's actually perfectly possible to delete the leftover socket data by calling closesocket(). That function must obviously call NtClose() / CloseHandle() internally, but it evidently doesn't check the return value, because it really does delete the socket data and return success, and a following call to WSAGetOverlappedResult() does return ENOTSOCK with the same handle.
So in theory we could just closesocket() the handle in iocp_async_read_closesocket(). What makes me nervous about doing that is—what if we get caught in a race in that very function, and a Windows thread allocates a new handle between the CloseHandle() and the subsequent closesocket()? closesocket() would end up closing a random internal NT handle, and that could easily cause other obscure failures down the line.
Safer is probably just to pass WSAGetOverlappedResult() a garbage handle value, one that would in practice never be allocated.