Skip to content

[Windows] Delete UNIX domain socket paths that NIO created - #3709

Open
jakepetroules wants to merge 1 commit into
apple:mainfrom
jakepetroules:windows-uds-cleanup
Open

[Windows] Delete UNIX domain socket paths that NIO created#3709
jakepetroules wants to merge 1 commit into
apple:mainfrom
jakepetroules:windows-uds-cleanup

Conversation

@jakepetroules

@jakepetroules jakepetroules commented Aug 15, 2026

Copy link
Copy Markdown
Member

Motivation:

cleanupUnixDomainSocket could never delete a socket path on Windows. There were three reasons.

  1. A path that does not exist. It threw IOError(windows: EBADF), which puts an errno value into the Windows error domain. The POSIX code treats a missing path as already clean and returns.

  2. The file type check was inverted. It rejected FILE_TYPE_DISK. On Windows a UNIX domain socket is a reparse point in the file system, so GetFileType reports exactly that. The check rejected the case the function exists for. The reparse tag, which the same function checks a few lines later, is what tells a socket from a normal file. The old guard also mixed the file type and the error state in one condition, so a rejected path was reported as IOError(windows: 0). That reads as "The operation completed successfully".

  3. The handle had no delete access. It was opened with GENERIC_READ only, so SetFileInformationByHandle failed with access denied.

Measured on a Windows ARM64 machine. The test binds an AF_UNIX socket, then asks NIO to clean the path up:

NIOUDS existedBefore=false existsAfterBind=false attrOK=true attrs=420 isReparse=true
NIOUDS cleanup threw UnixDomainSocketPathWrongType()   // reason 2
NIOUDS cleanup threw Access is denied.                 // then reason 3
NIOUDS cleanup succeeded                               // after both fixes

existsAfterBind=false is FileManager following the reparse point. GetFileAttributesExW reports 0x420, which includes FILE_ATTRIBUTE_REPARSE_POINT.

Modifications:

  • Treat ERROR_FILE_NOT_FOUND and ERROR_PATH_NOT_FOUND as already clean, and return. This matches how POSIX handles ENOENT.
  • Require FILE_TYPE_DISK instead of rejecting it. Check the GetFileType failure case separately, following its contract: failure is FILE_TYPE_UNKNOWN and an error is set. A path that exists but is not a socket now reports UnixDomainSocketPathWrongType, as on POSIX.
  • Open the handle with DELETE access as well.

This file is Windows-only.

Result:

NIO can now rebind a UNIX domain socket path that it used before on Windows. It also reports the same errors as the POSIX code for a missing path and for a wrong file type.

This is also what makes UNIX domain socket channel pairs usable in the tests. On a branch that enables them, StreamChannelTest goes from 1 passing test to 17.

Tested on a Windows ARM64 machine (Swift 6.3.2): builds on its own, and part of a larger branch where a full swift test passes (2360 tests, 0 failures).

Comment on lines +622 to +624
// A UNIX domain socket is a reparse point in the file system, so it *is* a disk object;
// anything else (a pipe, a character device) cannot be one. Which kind of disk object this
// is gets settled by the reparse tag checked below.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I missing something here? Why is this suddenly about UNIX?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inside cleanupUnixDomainSocket(atPath:). The hunk doesn't show the signature, which is a bit further up.

On Windows a bound AF_UNIX path is a reparse point on disk, so GetFileType returns FILE_TYPE_DISK for it. That's the case the old check rejected. I trimmed the comment to one line.

Comment thread Sources/NIOPosix/BSDSocketAPIWindows.swift Outdated

@fabianfett fabianfett left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this enable any tests?

Three things stopped `cleanupUnixDomainSocket` from ever removing a socket path
on Windows.

A path that does not exist threw `IOError(windows: EBADF)`, mixing an `errno`
value into the Windows error domain, where the POSIX implementation treats a
missing path as already cleaned up and returns successfully.

The type check then rejected `FILE_TYPE_DISK`. A UNIX domain socket on Windows
is a reparse point in the file system, so that is exactly what `GetFileType`
reports for one, meaning the check rejected the case the function exists to
handle. What distinguishes a socket from an ordinary file is the reparse tag,
which the code already inspects further down, so require a disk object rather
than reject one. The old `guard` also combined the type with the error state,
which is how a rejected path came to be reported as `IOError(windows: 0)`,
rendering as "The operation completed successfully"; check the two separately,
following `GetFileType`'s contract that failure is `FILE_TYPE_UNKNOWN` together
with an error being set.

Finally the handle was opened with `GENERIC_READ` alone, so the
`SetFileInformationByHandle` that removes the path failed with access denied.
Ask for `DELETE` as well.
@jakepetroules

Copy link
Copy Markdown
Member Author

No, it doesn't enable any tests, and it removes no skips.

The Windows UNIX domain socket tests stay skipped for a separate reason: they bind with SO_REUSEADDR, which fails on Windows with WSAEOPNOTSUPP. That's a different bug.

What this fix unblocks is UNIX domain socket channel pairs in the test helpers. On a follow-up branch that enables them, StreamChannelTest goes from 1 passing test to 17. That suite has other Windows problems, so I'm not enabling it yet.

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