Skip to content

Commit 22e4b38

Browse files
committed
Move platform specific AsyncIO implementations to separate files
1 parent 44a9e97 commit 22e4b38

File tree

5 files changed

+1112
-1077
lines changed

5 files changed

+1112
-1077
lines changed

Sources/Subprocess/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ add_library(Subprocess
1717
Result.swift
1818
IO/Output.swift
1919
IO/Input.swift
20-
IO/AsyncIO.swift
20+
IO/AsyncIO+Darwin.swift
21+
IO/AsyncIO+Linux.swift
22+
IO/AsyncIO+Windows.swift
2123
Span+Subprocess.swift
2224
AsyncBufferSequence.swift
2325
API.swift
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
/// Darwin AsyncIO implementation based on DispatchIO
13+
14+
// MARK: - macOS (DispatchIO)
15+
#if canImport(Darwin)
16+
17+
#if canImport(System)
18+
@preconcurrency import System
19+
#else
20+
@preconcurrency import SystemPackage
21+
#endif
22+
23+
internal import Dispatch
24+
25+
final class AsyncIO: Sendable {
26+
static let shared: AsyncIO = AsyncIO()
27+
28+
private init() {}
29+
30+
internal func read(
31+
from diskIO: borrowing IOChannel,
32+
upTo maxLength: Int
33+
) async throws -> DispatchData? {
34+
return try await self.read(
35+
from: diskIO.channel,
36+
upTo: maxLength,
37+
)
38+
}
39+
40+
internal func read(
41+
from dispatchIO: DispatchIO,
42+
upTo maxLength: Int
43+
) async throws -> DispatchData? {
44+
return try await withCheckedThrowingContinuation { continuation in
45+
var buffer: DispatchData = .empty
46+
dispatchIO.read(
47+
offset: 0,
48+
length: maxLength,
49+
queue: .global()
50+
) { done, data, error in
51+
if error != 0 {
52+
continuation.resume(
53+
throwing: SubprocessError(
54+
code: .init(.failedToReadFromSubprocess),
55+
underlyingError: .init(rawValue: error)
56+
)
57+
)
58+
return
59+
}
60+
if let data = data {
61+
if buffer.isEmpty {
62+
buffer = data
63+
} else {
64+
buffer.append(data)
65+
}
66+
}
67+
if done {
68+
if !buffer.isEmpty {
69+
continuation.resume(returning: buffer)
70+
} else {
71+
continuation.resume(returning: nil)
72+
}
73+
}
74+
}
75+
}
76+
}
77+
78+
#if SubprocessSpan
79+
internal func write(
80+
_ span: borrowing RawSpan,
81+
to diskIO: borrowing IOChannel
82+
) async throws -> Int {
83+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Int, any Error>) in
84+
let dispatchData = span.withUnsafeBytes {
85+
return DispatchData(
86+
bytesNoCopy: $0,
87+
deallocator: .custom(
88+
nil,
89+
{
90+
// noop
91+
}
92+
)
93+
)
94+
}
95+
self.write(dispatchData, to: diskIO) { writtenLength, error in
96+
if let error = error {
97+
continuation.resume(throwing: error)
98+
} else {
99+
continuation.resume(returning: writtenLength)
100+
}
101+
}
102+
}
103+
}
104+
#endif // SubprocessSpan
105+
106+
internal func write(
107+
_ array: [UInt8],
108+
to diskIO: borrowing IOChannel
109+
) async throws -> Int {
110+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Int, any Error>) in
111+
let dispatchData = array.withUnsafeBytes {
112+
return DispatchData(
113+
bytesNoCopy: $0,
114+
deallocator: .custom(
115+
nil,
116+
{
117+
// noop
118+
}
119+
)
120+
)
121+
}
122+
self.write(dispatchData, to: diskIO) { writtenLength, error in
123+
if let error = error {
124+
continuation.resume(throwing: error)
125+
} else {
126+
continuation.resume(returning: writtenLength)
127+
}
128+
}
129+
}
130+
}
131+
132+
internal func write(
133+
_ dispatchData: DispatchData,
134+
to diskIO: borrowing IOChannel,
135+
queue: DispatchQueue = .global(),
136+
completion: @escaping (Int, Error?) -> Void
137+
) {
138+
diskIO.channel.write(
139+
offset: 0,
140+
data: dispatchData,
141+
queue: queue
142+
) { done, unwritten, error in
143+
guard done else {
144+
// Wait until we are done writing or encountered some error
145+
return
146+
}
147+
148+
let unwrittenLength = unwritten?.count ?? 0
149+
let writtenLength = dispatchData.count - unwrittenLength
150+
guard error != 0 else {
151+
completion(writtenLength, nil)
152+
return
153+
}
154+
completion(
155+
writtenLength,
156+
SubprocessError(
157+
code: .init(.failedToWriteToSubprocess),
158+
underlyingError: .init(rawValue: error)
159+
)
160+
)
161+
}
162+
}
163+
}
164+
165+
#endif

0 commit comments

Comments
 (0)