@@ -29,6 +29,8 @@ import Musl
29
29
30
30
internal import Dispatch
31
31
32
+ import Synchronization
33
+
32
34
/// A collection of configurations parameters to use when
33
35
/// spawning a subprocess.
34
36
public struct Configuration : Sendable {
@@ -775,6 +777,16 @@ internal struct IOChannel: ~Copyable, @unchecked Sendable {
775
777
}
776
778
}
777
779
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
+
778
790
internal struct CreatedPipe : ~ Copyable {
779
791
internal enum Purpose : CustomStringConvertible {
780
792
/// This pipe is used for standard input. This option maps to
@@ -817,77 +829,96 @@ internal struct CreatedPipe: ~Copyable {
817
829
818
830
internal init ( closeWhenDone: Bool , purpose: Purpose ) throws {
819
831
#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
+ )
837
884
}
838
885
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
+ }
856
896
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
+ }
861
913
switch purpose {
862
914
case . input:
863
- targetAccess = DWORD ( GENERIC_READ)
915
+ self . _readFileDescriptor = . init( childEnd, closeWhenDone: closeWhenDone)
916
+ self . _writeFileDescriptor = . init( parentEnd, closeWhenDone: closeWhenDone)
864
917
case . output:
865
- targetAccess = DWORD ( GENERIC_WRITE)
918
+ self . _readFileDescriptor = . init( parentEnd, closeWhenDone: closeWhenDone)
919
+ self . _writeFileDescriptor = . init( childEnd, closeWhenDone: closeWhenDone)
866
920
}
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
891
922
}
892
923
#else
893
924
let pipe = try FileDescriptor . pipe ( )
0 commit comments