Skip to content

Fix infinite broken accept loop for Windows named pipe listeners#86

Merged
kotauskas merged 1 commit intokotauskas:mainfrom
SimendoBV:windows_fix
Dec 9, 2025
Merged

Fix infinite broken accept loop for Windows named pipe listeners#86
kotauskas merged 1 commit intokotauskas:mainfrom
SimendoBV:windows_fix

Conversation

@cloone8
Copy link
Contributor

@cloone8 cloone8 commented Dec 9, 2025

While debugging some other issue in my own code, I found that some buggy IPC client behaviour can potentially break the named pipe listener on Windows. It's been hard to reproduce consistently, but it seems like the following rough sequence of events can trigger it:

  1. Listener starts in nonblocking mode (without tokio)
  2. Client connects to listener, but sends no data.
  3. Client disconnects from listener
  4. (I'm not sure what happens here, which is why I found it hard to create a mimimum buggy example)
  5. Call accept() on listener, which returns win32 error 232 (ERROR_NO_DATA/The pipe is being closed) infinitely. No more connections are accepted.

From reading the documentation by Microsoft (https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe), it seems like ERROR_NO_DATA is returned when "a previous client has closed its pipe handle but the server has not disconnected."

Then, from the first paragraph in the "Remarks" section:

A named pipe server process can use ConnectNamedPipe with a newly created pipe instance. It can also be used with an instance that was previously connected to another client process; in this case, the server process must first call the DisconnectNamedPipe function to disconnect the handle from the previous client before the handle can be reconnected to a new client. Otherwise, ConnectNamedPipe returns zero, and GetLastError returns ERROR_NO_DATA if the previous client has closed its handle or ERROR_PIPE_CONNECTED if it has not closed its handle.

From looking at the accept code in this crate, it seems like a new named pipe instance is created only if the call to ConnectNamedPipe does not fail (which is does in this case). I modified the function to now specifically check for the ERROR_NO_DATA error, and then recreate the instance if it finds it. The error is still returned, but only once now, which I think probably makes more sense.

I'm no win32 expert, so I'm not 100% sure this solution is the right one, but it seems to have fixed the behaviour in my own code at least.

…ent connection disconnects very early in the pipe lifecycle
@kotauskas
Copy link
Owner

Here's my theory of what's going on here:

  1. Client connects to the stored instance, which immediately transitions it from the initial state it gets created in (disconnected) into the connected state (this happens solely on the client's initiative despite ConnectNamedPipe never having been called on it – ConnectNamedPipe is a misnomer and should really be called WaitForNamedPipeConnect)
  2. Client does not wait for the server to acknowledge this connection and disconnects, thereby transitioning the instance into the lost client state
  3. Server gets around to checking up on the stored instance expecting it to be in the disconnected state and calls ConnectNamedPipe, which tells it that it's in the lost client state by returning ERROR_NO_DATA

I think the correct reaction to ERROR_NO_DATA here is to call DisconnectNamedPipe (returning its error verbatim if one occurs, as that would clearly be an indicator of something having gone wrong) and retry ConnectNamedPipe as if nothing happened, repeating this an unbounded number of times in a loop. This loop can be thought of as a process of clearing bogus clients from the pipe instance, which one must do to ensure new clients can connect (which I should definitely mention in the docs).

I'll merge this now and implement the above solution when I have time. Thank you for diagnosing this!

@kotauskas kotauskas merged commit 54beb9c into kotauskas:main Dec 9, 2025
kotauskas added a commit that referenced this pull request Jan 25, 2026
@kotauskas
Copy link
Owner

aa486ab applies the permanent fix I've described above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants