@@ -29,6 +29,8 @@ import Musl
2929
3030internal import Dispatch
3131
32+ import Synchronization
33+
3234/// A collection of configurations parameters to use when
3335/// spawning a subprocess.
3436public struct Configuration : Sendable {
@@ -775,6 +777,16 @@ internal struct IOChannel: ~Copyable, @unchecked Sendable {
775777 }
776778}
777779
780+ #if canImport(WinSDK)
781+ internal enum PipeNameCounter {
782+ private static let value = Atomic < UInt64 > ( 0 )
783+
784+ internal static func nextValue( ) -> UInt64 {
785+ return self . value. add ( 1 , ordering: . relaxed) . newValue
786+ }
787+ }
788+ #endif
789+
778790internal struct CreatedPipe : ~ Copyable {
779791 internal enum Purpose : CustomStringConvertible {
780792 /// This pipe is used for standard input. This option maps to
@@ -817,77 +829,96 @@ internal struct CreatedPipe: ~Copyable {
817829
818830 internal init ( closeWhenDone: Bool , purpose: Purpose ) throws {
819831 #if canImport(WinSDK)
820- // On Windows, we need to create a named pipe
821- let pipeName = " \\ \\ . \\ pipe \\ subprocess- \( purpose) - \( Int . random ( in: . min ..< . max) ) "
822- var saAttributes : SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES ( )
823- saAttributes. nLength = DWORD ( MemoryLayout< SECURITY_ATTRIBUTES> . size)
824- saAttributes. bInheritHandle = true
825- saAttributes. lpSecurityDescriptor = nil
826-
827- let parentEnd = pipeName. withCString (
828- encodedAs: UTF16 . self
829- ) { pipeNameW in
830- // Use OVERLAPPED for async IO
831- var openMode : DWORD = DWORD ( FILE_FLAG_OVERLAPPED)
832- switch purpose {
833- case . input:
834- openMode |= DWORD ( PIPE_ACCESS_OUTBOUND)
835- case . output:
836- openMode |= DWORD ( PIPE_ACCESS_INBOUND)
832+ /// On Windows, we need to create a named pipe.
833+ /// According to Microsoft documentation:
834+ /// > Asynchronous (overlapped) read and write operations are
835+ /// > not supported by anonymous pipes.
836+ /// See https://learn.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
837+ while true {
838+ /// Windows named pipes are system wide. To avoid creating two pipes with the same
839+ /// name, create the pipe with `FILE_FLAG_FIRST_PIPE_INSTANCE` such that it will
840+ /// return error `ERROR_ACCESS_DENIED` if we try to create another pipe with the same name.
841+ let pipeName = " \\ \\ . \\ pipe \\ LOCAL \\ subprocess- \( purpose) - \( PipeNameCounter . nextValue ( ) ) "
842+ var saAttributes : SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES ( )
843+ saAttributes. nLength = DWORD ( MemoryLayout< SECURITY_ATTRIBUTES> . size)
844+ saAttributes. bInheritHandle = true
845+ saAttributes. lpSecurityDescriptor = nil
846+
847+ let parentEnd = pipeName. withCString (
848+ encodedAs: UTF16 . self
849+ ) { pipeNameW in
850+ // Use OVERLAPPED for async IO
851+ var openMode : DWORD = DWORD ( FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE)
852+ switch purpose {
853+ case . input:
854+ openMode |= DWORD ( PIPE_ACCESS_OUTBOUND)
855+ case . output:
856+ openMode |= DWORD ( PIPE_ACCESS_INBOUND)
857+ }
858+
859+ return CreateNamedPipeW (
860+ pipeNameW,
861+ openMode,
862+ DWORD ( PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT) ,
863+ 1 , // Max instance,
864+ DWORD ( readBufferSize) ,
865+ DWORD ( readBufferSize) ,
866+ 0 ,
867+ & saAttributes
868+ )
869+ }
870+ guard let parentEnd, parentEnd != INVALID_HANDLE_VALUE else {
871+ // Since we created the pipe with `FILE_FLAG_FIRST_PIPE_INSTANCE`,
872+ // if there's already a pipe with the same name, GetLastError()
873+ // will be set to FILE_FLAG_FIRST_PIPE_INSTANCE. In this case,
874+ // try again with a different name.
875+ let errorCode = GetLastError ( )
876+ guard errorCode != FILE_FLAG_FIRST_PIPE_INSTANCE else {
877+ continue
878+ }
879+ // Throw all other errors
880+ throw SubprocessError (
881+ code: . init( . asyncIOFailed( " CreateNamedPipeW failed " ) ) ,
882+ underlyingError: . init( rawValue: GetLastError ( ) )
883+ )
837884 }
838885
839- return CreateNamedPipeW (
840- pipeNameW,
841- openMode,
842- DWORD ( PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT) ,
843- 1 , // Max instance,
844- DWORD ( readBufferSize) ,
845- DWORD ( readBufferSize) ,
846- 0 ,
847- & saAttributes
848- )
849- }
850- guard let parentEnd, parentEnd != INVALID_HANDLE_VALUE else {
851- throw SubprocessError (
852- code: . init( . asyncIOFailed( " CreateNamedPipeW failed " ) ) ,
853- underlyingError: . init( rawValue: GetLastError ( ) )
854- )
855- }
886+ let childEnd = pipeName. withCString (
887+ encodedAs: UTF16 . self
888+ ) { pipeNameW in
889+ var targetAccess : DWORD = 0
890+ switch purpose {
891+ case . input:
892+ targetAccess = DWORD ( GENERIC_READ)
893+ case . output:
894+ targetAccess = DWORD ( GENERIC_WRITE)
895+ }
856896
857- let childEnd = pipeName. withCString (
858- encodedAs: UTF16 . self
859- ) { pipeNameW in
860- var targetAccess : DWORD = 0
897+ return CreateFileW (
898+ pipeNameW,
899+ targetAccess,
900+ 0 ,
901+ & saAttributes,
902+ DWORD ( OPEN_EXISTING) ,
903+ DWORD ( FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED) ,
904+ nil
905+ )
906+ }
907+ guard let childEnd, childEnd != INVALID_HANDLE_VALUE else {
908+ throw SubprocessError (
909+ code: . init( . asyncIOFailed( " CreateFileW failed " ) ) ,
910+ underlyingError: . init( rawValue: GetLastError ( ) )
911+ )
912+ }
861913 switch purpose {
862914 case . input:
863- targetAccess = DWORD ( GENERIC_READ)
915+ self . _readFileDescriptor = . init( childEnd, closeWhenDone: closeWhenDone)
916+ self . _writeFileDescriptor = . init( parentEnd, closeWhenDone: closeWhenDone)
864917 case . output:
865- targetAccess = DWORD ( GENERIC_WRITE)
918+ self . _readFileDescriptor = . init( parentEnd, closeWhenDone: closeWhenDone)
919+ self . _writeFileDescriptor = . init( childEnd, closeWhenDone: closeWhenDone)
866920 }
867-
868- return CreateFileW (
869- pipeNameW,
870- targetAccess,
871- 0 ,
872- & saAttributes,
873- DWORD ( OPEN_EXISTING) ,
874- DWORD ( FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED) ,
875- nil
876- )
877- }
878- guard let childEnd, childEnd != INVALID_HANDLE_VALUE else {
879- throw SubprocessError (
880- code: . init( . asyncIOFailed( " CreateFileW failed " ) ) ,
881- underlyingError: . init( rawValue: GetLastError ( ) )
882- )
883- }
884- switch purpose {
885- case . input:
886- self . _readFileDescriptor = . init( childEnd, closeWhenDone: closeWhenDone)
887- self . _writeFileDescriptor = . init( parentEnd, closeWhenDone: closeWhenDone)
888- case . output:
889- self . _readFileDescriptor = . init( parentEnd, closeWhenDone: closeWhenDone)
890- self . _writeFileDescriptor = . init( childEnd, closeWhenDone: closeWhenDone)
921+ return
891922 }
892923 #else
893924 let pipe = try FileDescriptor . pipe ( )
0 commit comments