Skip to content

Commit 810f0c1

Browse files
authored
Update AsyncProcess with bug fixes (#221)
Namely, this works around the following issues in Foundation.Process: swiftlang/swift-corelibs-foundation#4810 swiftlang/swift-corelibs-foundation#4795 swiftlang/swift-corelibs-foundation#4772 --- Mostly the same as #156 but even newer code.
1 parent 8daa19c commit 810f0c1

16 files changed

+1668
-301
lines changed

Package.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,22 @@ let package = Package(
7676
]
7777
),
7878
.systemLibrary(name: "SystemSQLite", pkgConfig: "sqlite3"),
79+
80+
// `AsyncProcess` modules and dependencies
81+
82+
.target(name: "CProcessSpawnSync"),
83+
.target(
84+
name: "ProcessSpawnSync",
85+
dependencies: [
86+
"CProcessSpawnSync",
87+
.product(name: "Atomics", package: "swift-atomics"),
88+
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
89+
]
90+
),
7991
.target(
8092
name: "AsyncProcess",
8193
dependencies: [
94+
"ProcessSpawnSync",
8295
.product(name: "Atomics", package: "swift-atomics"),
8396
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
8497
.product(name: "Logging", package: "swift-log"),

Sources/AsyncProcess/ChunkSequence.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -33,17 +33,17 @@ public struct ChunkSequence: AsyncSequence & Sendable {
3333

3434
public func makeAsyncIterator() -> AsyncIterator {
3535
// This will close the file handle.
36-
AsyncIterator(try! self.fileHandle?.fileContentStream(eventLoop: self.group.any()))
36+
return AsyncIterator(try! self.fileHandle?.fileContentStream(eventLoop: group.any()))
3737
}
3838

3939
public typealias Element = ByteBuffer
4040
public struct AsyncIterator: AsyncIteratorProtocol {
4141
public typealias Element = ByteBuffer
42-
typealias UnderlyingSequence = FileContentStream
42+
internal typealias UnderlyingSequence = FileContentStream
4343

4444
private var underlyingIterator: UnderlyingSequence.AsyncIterator?
4545

46-
init(_ underlyingSequence: UnderlyingSequence?) {
46+
internal init(_ underlyingSequence: UnderlyingSequence?) {
4747
self.underlyingIterator = underlyingSequence?.makeAsyncIterator()
4848
}
4949

Sources/AsyncProcess/EOFSequence.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -15,13 +15,13 @@ public struct EOFSequence<Element>: AsyncSequence & Sendable {
1515

1616
public struct AsyncIterator: AsyncIteratorProtocol {
1717
public mutating func next() async throws -> Element? {
18-
nil
18+
return nil
1919
}
2020
}
2121

2222
public init(of type: Element.Type = Element.self) {}
2323

2424
public func makeAsyncIterator() -> AsyncIterator {
25-
AsyncIterator()
25+
return AsyncIterator()
2626
}
2727
}

Sources/AsyncProcess/FileContentStream.swift

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -19,12 +19,13 @@ import NIO
1919
// - Known issues:
2020
// - no tests
2121
// - most configurations have never run
22-
struct FileContentStream: AsyncSequence {
22+
internal typealias FileContentStream = _FileContentStream
23+
public struct _FileContentStream: AsyncSequence & Sendable {
2324
public typealias Element = ByteBuffer
2425
typealias Underlying = AsyncThrowingChannel<Element, Error>
2526

2627
public func makeAsyncIterator() -> AsyncIterator {
27-
AsyncIterator(underlying: self.asyncChannel.makeAsyncIterator())
28+
return AsyncIterator(underlying: self.asyncChannel.makeAsyncIterator())
2829
}
2930

3031
public struct AsyncIterator: AsyncIteratorProtocol {
@@ -33,21 +34,31 @@ struct FileContentStream: AsyncSequence {
3334
var underlying: Underlying.AsyncIterator
3435

3536
public mutating func next() async throws -> ByteBuffer? {
36-
try await self.underlying.next()
37+
return try await self.underlying.next()
3738
}
3839
}
3940

4041
public struct IOError: Error {
4142
public var errnoValue: CInt
4243

4344
public static func makeFromErrnoGlobal() -> IOError {
44-
IOError(errnoValue: errno)
45+
return IOError(errnoValue: errno)
4546
}
4647
}
4748

4849
private let asyncChannel: AsyncThrowingChannel<ByteBuffer, Error>
4950

50-
public init(
51+
public static func makeReader(
52+
fileDescriptor: CInt,
53+
eventLoop: EventLoop = MultiThreadedEventLoopGroup.singleton.any(),
54+
blockingPool: NIOThreadPool = .singleton
55+
) async throws -> _FileContentStream {
56+
return try await eventLoop.submit {
57+
try FileContentStream(fileDescriptor: fileDescriptor, eventLoop: eventLoop, blockingPool: blockingPool)
58+
}.get()
59+
}
60+
61+
internal init(
5162
fileDescriptor: CInt,
5263
eventLoop: EventLoop,
5364
blockingPool: NIOThreadPool? = nil
@@ -64,7 +75,7 @@ struct FileContentStream: AsyncSequence {
6475

6576
switch statInfo.st_mode & S_IFMT {
6677
case S_IFREG:
67-
guard let blockingPool else {
78+
guard let blockingPool = blockingPool else {
6879
throw IOError(errnoValue: EINVAL)
6980
}
7081
let fileHandle = NIOLoopBound(
@@ -86,7 +97,7 @@ struct FileContentStream: AsyncSequence {
8697
.whenComplete { result in
8798
try! fileHandle.value.close()
8899
switch result {
89-
case let .failure(error):
100+
case .failure(let error):
90101
asyncChannel.fail(error)
91102
case .success:
92103
asyncChannel.finish()
@@ -140,7 +151,7 @@ private final class ReadIntoAsyncChannelHandler: ChannelDuplexHandler {
140151
return data
141152
case .error:
142153
return nil
143-
case var .sending(queue):
154+
case .sending(var queue):
144155
queue.append(data)
145156
self = .sending(queue)
146157
return nil
@@ -153,7 +164,7 @@ private final class ReadIntoAsyncChannelHandler: ChannelDuplexHandler {
153164
preconditionFailure("didSendOne during .idle")
154165
case .error:
155166
return nil
156-
case var .sending(queue):
167+
case .sending(var queue):
157168
if queue.isEmpty {
158169
self = .idle
159170
return nil
@@ -212,7 +223,7 @@ private final class ReadIntoAsyncChannelHandler: ChannelDuplexHandler {
212223
eventLoop.makeFutureWithTask {
213224
// note: We're _not_ on an EventLoop thread here
214225
switch data {
215-
case let .chunk(data):
226+
case .chunk(let data):
216227
await sink.send(data)
217228
case .finish:
218229
sink.finish()
@@ -255,18 +266,15 @@ private final class ReadIntoAsyncChannelHandler: ChannelDuplexHandler {
255266

256267
extension FileHandle {
257268
func fileContentStream(eventLoop: EventLoop) throws -> FileContentStream {
258-
let asyncBytes = try FileContentStream(
259-
fileDescriptor: self.fileDescriptor,
260-
eventLoop: eventLoop
261-
)
269+
let asyncBytes = try FileContentStream(fileDescriptor: self.fileDescriptor, eventLoop: eventLoop)
262270
try self.close()
263271
return asyncBytes
264272
}
265273
}
266274

267275
extension FileContentStream {
268276
var lines: AsyncByteBufferLineSequence<FileContentStream> {
269-
AsyncByteBufferLineSequence(
277+
return AsyncByteBufferLineSequence(
270278
self,
271279
dropTerminator: true,
272280
maximumAllowableBufferSize: 1024 * 1024,
@@ -281,7 +289,7 @@ extension AsyncSequence where Element == ByteBuffer, Self: Sendable {
281289
maximumAllowableBufferSize: Int = 1024 * 1024,
282290
dropLastChunkIfNoNewline: Bool = false
283291
) -> AsyncByteBufferLineSequence<Self> {
284-
AsyncByteBufferLineSequence(
292+
return AsyncByteBufferLineSequence(
285293
self,
286294
dropTerminator: dropTerminator,
287295
maximumAllowableBufferSize: maximumAllowableBufferSize,
@@ -290,7 +298,7 @@ extension AsyncSequence where Element == ByteBuffer, Self: Sendable {
290298
}
291299

292300
public var strings: AsyncMapSequence<Self, String> {
293-
self.map { String(buffer: $0) }
301+
return self.map { String(buffer: $0) }
294302
}
295303
}
296304

@@ -312,28 +320,26 @@ where Base: AsyncSequence, Base.Element == ByteBuffer {
312320

313321
struct Buffer {
314322
private var buffer: [ByteBuffer] = []
315-
private(set) var byteCount: Int = 0
323+
internal private(set) var byteCount: Int = 0
316324

317325
mutating func append(_ buffer: ByteBuffer) {
318326
self.buffer.append(buffer)
319327
self.byteCount += buffer.readableBytes
320328
}
321329

322330
func allButLast() -> ArraySlice<ByteBuffer> {
323-
self.buffer.dropLast()
331+
return self.buffer.dropLast()
324332
}
325333

326334
var byteCountButLast: Int {
327-
self.byteCount - (self.buffer.last?.readableBytes ?? 0)
335+
return self.byteCount - (self.buffer.last?.readableBytes ?? 0)
328336
}
329337

330338
var lastChunkView: ByteBufferView? {
331-
self.buffer.last?.readableBytesView
339+
return self.buffer.last?.readableBytesView
332340
}
333341

334-
mutating func concatenateEverything(upToLastChunkLengthToConsume lastLength: Int)
335-
-> ByteBuffer
336-
{
342+
mutating func concatenateEverything(upToLastChunkLengthToConsume lastLength: Int) -> ByteBuffer {
337343
var output = ByteBuffer()
338344
output.reserveCapacity(lastLength + self.byteCountButLast)
339345

@@ -359,7 +365,7 @@ where Base: AsyncSequence, Base.Element == ByteBuffer {
359365
}
360366
}
361367

362-
init(
368+
internal init(
363369
underlying: Base.AsyncIterator,
364370
dropTerminator: Bool,
365371
maximumAllowableBufferSize: Int,
@@ -446,7 +452,7 @@ where Base: AsyncSequence, Base.Element == ByteBuffer {
446452
}
447453

448454
public func makeAsyncIterator() -> AsyncIterator {
449-
AsyncIterator(
455+
return AsyncIterator(
450456
underlying: self.underlying.makeAsyncIterator(),
451457
dropTerminator: self.dropTerminator,
452458
maximumAllowableBufferSize: self.maximumAllowableBufferSize,

Sources/AsyncProcess/NIOAsyncPipeWriter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information

0 commit comments

Comments
 (0)