@@ -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