Skip to content

Commit cf9e76a

Browse files
MaxDesiatovweissieuanh
authored
Update AsyncProcess, use it in Shell implementation (#77)
These changes make `AsyncProcess` implementation more robust and increase its code coverage. Now we no longer have to rely on `Foundation.Process` directly in `Shell`, as `AsyncProcess` abstracts rough edges away for us and also provides an `async`-friendly API. --------- Co-authored-by: Johannes Weiss <[email protected]> Co-authored-by: Euan Harris <[email protected]>
1 parent 6e55a2f commit cf9e76a

14 files changed

+972
-302
lines changed

Package.resolved

Lines changed: 17 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ let package = Package(
2121
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.2"),
2222
.package(url: "https://github.com/apple/swift-async-algorithms.git", exact: "1.0.0-beta.1"),
2323
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.1.0"),
24-
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.4"),
24+
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.5"),
2525
.package(url: "https://github.com/apple/swift-crypto.git", from: "3.1.0"),
26-
.package(url: "https://github.com/apple/swift-nio.git", from: "2.58.0"),
27-
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.19.0"),
26+
.package(url: "https://github.com/apple/swift-nio.git", from: "2.63.0"),
27+
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.20.0"),
2828
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.3"),
2929
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.3.0"),
3030
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.2"),

Sources/AsyncProcess/FileContentStream.swift

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,13 @@ struct FileContentStream: AsyncSequence {
6767
guard let blockingPool else {
6868
throw IOError(errnoValue: EINVAL)
6969
}
70-
let fileHandle = NIOFileHandle(descriptor: dupedFD)
70+
let fileHandle = NIOLoopBound(
71+
NIOFileHandle(descriptor: dupedFD),
72+
eventLoop: eventLoop
73+
)
7174
NonBlockingFileIO(threadPool: blockingPool)
7275
.readChunked(
73-
fileHandle: fileHandle,
76+
fileHandle: fileHandle.value,
7477
byteCount: .max,
7578
allocator: ByteBufferAllocator(),
7679
eventLoop: eventLoop,
@@ -81,7 +84,7 @@ struct FileContentStream: AsyncSequence {
8184
}
8285
)
8386
.whenComplete { result in
84-
try! fileHandle.close()
87+
try! fileHandle.value.close()
8588
switch result {
8689
case let .failure(error):
8790
asyncChannel.fail(error)
@@ -96,20 +99,16 @@ struct FileContentStream: AsyncSequence {
9699
}
97100
.withConnectedSocket(dupedFD)
98101
case S_IFIFO:
99-
let deadPipe = Pipe()
100102
NIOPipeBootstrap(group: eventLoop)
101103
.channelInitializer { channel in
102104
channel.pipeline.addHandler(ReadIntoAsyncChannelHandler(sink: asyncChannel))
103105
}
104-
.takingOwnershipOfDescriptors(
105-
input: dupedFD,
106-
output: dup(deadPipe.fileHandleForWriting.fileDescriptor)
106+
.takingOwnershipOfDescriptor(
107+
input: dupedFD
107108
)
108109
.whenSuccess { channel in
109110
channel.close(mode: .output, promise: nil)
110111
}
111-
try! deadPipe.fileHandleForReading.close()
112-
try! deadPipe.fileHandleForWriting.close()
113112
case S_IFDIR:
114113
throw IOError(errnoValue: EISDIR)
115114
case S_IFBLK, S_IFCHR, S_IFLNK:
@@ -206,25 +205,30 @@ private final class ReadIntoAsyncChannelHandler: ChannelDuplexHandler {
206205
private func sendOneItem(_ data: ReceivedEvent, context: ChannelHandlerContext) {
207206
context.eventLoop.assertInEventLoop()
208207
assert(self.shouldRead == false, "sendOneItem in unexpected state \(self.state)")
209-
context.eventLoop.makeFutureWithTask {
208+
let eventLoop = context.eventLoop
209+
let sink = self.sink
210+
let `self` = NIOLoopBound(self, eventLoop: context.eventLoop)
211+
let context = NIOLoopBound(context, eventLoop: context.eventLoop)
212+
eventLoop.makeFutureWithTask {
213+
// note: We're _not_ on an EventLoop thread here
210214
switch data {
211215
case let .chunk(data):
212-
await self.sink.send(data)
216+
await sink.send(data)
213217
case .finish:
214-
self.sink.finish()
218+
sink.finish()
215219
}
216220
}.map {
217-
if let moreToSend = self.state.didSendOne() {
218-
self.sendOneItem(moreToSend, context: context)
221+
if let moreToSend = self.value.state.didSendOne() {
222+
self.value.sendOneItem(moreToSend, context: context.value)
219223
} else {
220-
if self.heldUpRead {
221-
context.eventLoop.execute {
222-
context.read()
224+
if self.value.heldUpRead {
225+
eventLoop.execute {
226+
context.value.read()
223227
}
224228
}
225229
}
226230
}.whenFailure { error in
227-
self.state.fail(error)
231+
self.value.state.fail(error)
228232
}
229233
}
230234

@@ -268,7 +272,7 @@ extension FileContentStream {
268272
}
269273
}
270274

271-
public extension AsyncSequence where Element == ByteBuffer {
275+
public extension AsyncSequence where Element == ByteBuffer, Self: Sendable {
272276
func splitIntoLines(
273277
dropTerminator: Bool = true,
274278
maximumAllowableBufferSize: Int = 1024 * 1024,

Sources/AsyncProcess/NIOAsyncPipeWriter.swift

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,16 @@ import NIOExtras
1717
struct NIOAsyncPipeWriter<Chunks: AsyncSequence & Sendable> where Chunks.Element == ByteBuffer {
1818
static func sinkSequenceInto(
1919
_ chunks: Chunks,
20-
fileDescriptor fd: CInt,
20+
takingOwnershipOfFD fd: CInt,
2121
eventLoop: EventLoop
2222
) async throws {
23-
// Just so we've got an input.
24-
// (workaround for https://github.com/apple/swift-nio/issues/2444)
25-
let deadPipe = Pipe()
2623
let channel = try await NIOPipeBootstrap(group: eventLoop)
2724
.channelOption(ChannelOptions.allowRemoteHalfClosure, value: true)
2825
.channelOption(ChannelOptions.autoRead, value: false)
29-
.takingOwnershipOfDescriptors(
30-
input: dup(deadPipe.fileHandleForReading.fileDescriptor),
31-
output: dup(fd)
26+
.takingOwnershipOfDescriptor(
27+
output: fd
3228
).get()
3329
channel.close(mode: .input, promise: nil)
34-
try! deadPipe.fileHandleForReading.close()
35-
try! deadPipe.fileHandleForWriting.close()
3630
defer {
3731
channel.close(promise: nil)
3832
}

0 commit comments

Comments
 (0)