Skip to content

Commit 9c6703f

Browse files
committed
Remove DispatchData._ContiguousBufferView since Linux no longer relies on DispatchIO and we can just use DispatchData.Region on Darwin
1 parent ba8c6c8 commit 9c6703f

File tree

4 files changed

+111
-107
lines changed

4 files changed

+111
-107
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import PackageDescription
66
var dep: [Package.Dependency] = [
77
.package(
88
url: "https://github.com/apple/swift-system",
9-
from: "1.5.0"
9+
exact: "1.5.0"
1010
)
1111
]
1212
#if !os(Windows)

Sources/Subprocess/Buffer.swift

Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ extension AsyncBufferSequence {
2020
#if canImport(Darwin)
2121
// We need to keep the backingData alive while Slice is alive
2222
internal let backingData: DispatchData
23-
internal let data: DispatchData._ContiguousBufferView
23+
internal let data: DispatchData.Region
2424

25-
internal init(data: DispatchData._ContiguousBufferView, backingData: DispatchData) {
25+
internal init(data: DispatchData.Region, backingData: DispatchData) {
2626
self.data = data
2727
self.backingData = backingData
2828
}
2929

3030
internal static func createFrom(_ data: DispatchData) -> [Buffer] {
31-
let slices = data.contiguousBufferViews
31+
let slices = data.regions
3232
// In most (all?) cases data should only have one slice
3333
if _fastPath(slices.count == 1) {
3434
return [.init(data: slices[0], backingData: data)]
@@ -98,54 +98,27 @@ extension AsyncBufferSequence.Buffer: Equatable, Hashable {
9898
}
9999

100100
public func hash(into hasher: inout Hasher) {
101-
hasher.combine(self.data)
101+
return self.data.hash(into: &hasher)
102102
}
103103
#endif
104104
// else Compiler generated conformances
105105
}
106106

107-
// MARK: - DispatchData.Block
108-
#if canImport(Darwin) || canImport(Glibc) || canImport(Android) || canImport(Musl)
109-
extension DispatchData {
110-
/// Unfortunately `DispatchData.Region` is not available on Linux, hence our own wrapper
111-
internal struct _ContiguousBufferView: @unchecked Sendable, RandomAccessCollection, Hashable {
112-
typealias Element = UInt8
113-
114-
internal let bytes: UnsafeBufferPointer<UInt8>
115-
116-
internal var startIndex: Int { self.bytes.startIndex }
117-
internal var endIndex: Int { self.bytes.endIndex }
118-
119-
internal init(bytes: UnsafeBufferPointer<UInt8>) {
120-
self.bytes = bytes
121-
}
122-
123-
internal func withUnsafeBytes<ResultType>(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType {
124-
return try body(UnsafeRawBufferPointer(self.bytes))
125-
}
126-
127-
internal func hash(into hasher: inout Hasher) {
128-
hasher.combine(bytes: UnsafeRawBufferPointer(self.bytes))
129-
}
130-
131-
internal static func == (lhs: DispatchData._ContiguousBufferView, rhs: DispatchData._ContiguousBufferView) -> Bool {
132-
return lhs.bytes.elementsEqual(rhs.bytes)
133-
}
134-
135-
subscript(position: Int) -> UInt8 {
136-
_read {
137-
yield self.bytes[position]
107+
#if canImport(Darwin)
108+
extension DispatchData.Region {
109+
static func == (lhs: DispatchData.Region, rhs: DispatchData.Region) -> Bool {
110+
return lhs.withUnsafeBytes { lhsBytes in
111+
return rhs.withUnsafeBytes { rhsBytes in
112+
return lhsBytes.elementsEqual(rhsBytes)
138113
}
139114
}
140115
}
141116

142-
internal var contiguousBufferViews: [_ContiguousBufferView] {
143-
var slices = [_ContiguousBufferView]()
144-
enumerateBytes { (bytes, index, stop) in
145-
slices.append(_ContiguousBufferView(bytes: bytes))
117+
internal func hash(into hasher: inout Hasher) {
118+
return self.withUnsafeBytes { ptr in
119+
return hasher.combine(bytes: ptr)
146120
}
147-
return slices
148121
}
149122
}
150-
151123
#endif
124+

Sources/Subprocess/Configuration.swift

Lines changed: 95 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import Musl
2929

3030
internal import Dispatch
3131

32+
import Synchronization
33+
3234
/// A collection of configurations parameters to use when
3335
/// spawning a subprocess.
3436
public 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+
778790
internal 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()

Sources/Subprocess/IO/AsyncIO+Darwin.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ final class AsyncIO: Sendable {
4646
dispatchIO.read(
4747
offset: 0,
4848
length: maxLength,
49-
queue: .global()
49+
queue: DispatchQueue(label: "SubprocessReadQueue")
5050
) { done, data, error in
5151
if error != 0 {
5252
continuation.resume(

0 commit comments

Comments
 (0)