Skip to content

Commit 9f789df

Browse files
committed
fix(helper): box pipe-drain buffer for Swift strict concurrency
CaddyManager.drainPipe captured a `var buffer = Data()` inside its readabilityHandler closure. Swift 5.10 strict concurrency rejects the mutation because the closure is @sendable. Wrap it in a small `@unchecked Sendable` LineBufferBox — readabilityHandler is documented to fire serially per file handle, so single-instance access is safe.
1 parent 239add9 commit 9f789df

1 file changed

Lines changed: 15 additions & 6 deletions

File tree

Sources/CloudTunnelsProxyHelper/CaddyManager.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -300,20 +300,20 @@ public actor CaddyManager {
300300
/// but shouldn't touch our actor state.
301301
private static func drainPipe(_ pipe: Pipe, label: String, log: Logger) {
302302
let handle = pipe.fileHandleForReading
303-
var buffer = Data()
303+
let state = LineBufferBox()
304304
handle.readabilityHandler = { fh in
305305
let chunk = fh.availableData
306306
if chunk.isEmpty {
307307
fh.readabilityHandler = nil
308-
if !buffer.isEmpty, let tail = String(data: buffer, encoding: .utf8) {
308+
if !state.buffer.isEmpty, let tail = String(data: state.buffer, encoding: .utf8) {
309309
log.info("[\(label, privacy: .public)] \(tail, privacy: .public)")
310310
}
311311
return
312312
}
313-
buffer.append(chunk)
314-
while let newlineIdx = buffer.firstIndex(of: 0x0a) {
315-
let lineData = buffer.subdata(in: 0..<newlineIdx)
316-
buffer.removeSubrange(0...newlineIdx)
313+
state.buffer.append(chunk)
314+
while let newlineIdx = state.buffer.firstIndex(of: 0x0a) {
315+
let lineData = state.buffer.subdata(in: 0..<newlineIdx)
316+
state.buffer.removeSubrange(0...newlineIdx)
317317
if let line = String(data: lineData, encoding: .utf8), !line.isEmpty {
318318
log.info("[\(label, privacy: .public)] \(line, privacy: .public)")
319319
}
@@ -384,3 +384,12 @@ public actor CaddyManager {
384384
}
385385
}
386386
}
387+
388+
/// Boxed line-accumulator for `drainPipe`. The pipe's `readabilityHandler`
389+
/// closure is `@Sendable` under Swift 5.10+ strict concurrency, so the
390+
/// buffer it mutates must live behind a reference. `readabilityHandler`
391+
/// is documented to fire serially per file handle, which gives us the
392+
/// exclusion needed for `@unchecked Sendable`.
393+
private final class LineBufferBox: @unchecked Sendable {
394+
var buffer = Data()
395+
}

0 commit comments

Comments
 (0)