Fix control pipe races: treat ERROR_PIPE_CONNECTED as success, flush+disconnect before close so replies are never discarded
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
@@ -78,19 +79,43 @@ func Serve(ctx context.Context, h Handler, log *slog.Logger) error {
|
|||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
// Blocks until a client connects; on process exit the
|
// Blocks until a client connects; on process exit the
|
||||||
// handle goes away with everything else.
|
// handle goes away with everything else. A client that
|
||||||
if err := windows.ConnectNamedPipe(pipe, nil); err != nil {
|
// raced us and connected between CreateNamedPipe and
|
||||||
|
// ConnectNamedPipe reports ERROR_PIPE_CONNECTED — that is
|
||||||
|
// a success, not a failure.
|
||||||
|
if err := windows.ConnectNamedPipe(pipe, nil); err != nil && err != errnoPipeConnected {
|
||||||
windows.CloseHandle(pipe)
|
windows.CloseHandle(pipe)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f := os.NewFile(uintptr(pipe), pipePath)
|
serveConn(&pipeConn{f: os.NewFile(uintptr(pipe), pipePath), h: pipe}, h)
|
||||||
serveConn(f, h) // closes f, and with it the pipe handle
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errnoPipeConnected is ConnectNamedPipe's "the client connected before we
|
||||||
|
// called" result, which means the connection is established.
|
||||||
|
var errnoPipeConnected = syscall.Errno(535) // ERROR_PIPE_CONNECTED
|
||||||
|
|
||||||
|
// pipeConn adapts a pipe handle to io.ReadWriteCloser. Close flushes first
|
||||||
|
// (FlushFileBuffers blocks until the client has read the reply) and then
|
||||||
|
// disconnects — closing the bare handle right after writing can discard
|
||||||
|
// unread reply bytes, which clients see as an empty, failed request.
|
||||||
|
type pipeConn struct {
|
||||||
|
f *os.File
|
||||||
|
h windows.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *pipeConn) Read(p []byte) (int, error) { return c.f.Read(p) }
|
||||||
|
func (c *pipeConn) Write(p []byte) (int, error) { return c.f.Write(p) }
|
||||||
|
|
||||||
|
func (c *pipeConn) Close() error {
|
||||||
|
windows.FlushFileBuffers(c.h) //nolint:errcheck // best effort
|
||||||
|
windows.DisconnectNamedPipe(c.h) //nolint:errcheck // best effort
|
||||||
|
return c.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// Ask sends one command to the running service and returns its reply.
|
// Ask sends one command to the running service and returns its reply.
|
||||||
func Ask(cmd string) (string, error) {
|
func Ask(cmd string) (string, error) {
|
||||||
name, err := windows.UTF16PtrFromString(pipePath)
|
name, err := windows.UTF16PtrFromString(pipePath)
|
||||||
|
|||||||
Reference in New Issue
Block a user