Summary
The background writer threads used for bound stream pairs silently swallow all errors. If a FileHandle.read() fails mid-stream or OutputStream.write() returns an error, the thread exits without logging or propagating the failure. The output stream closes, the input stream sees EOF, and URLSession receives fewer bytes than Content-Length promised — resulting in a network error with no indication of the root cause.
Locations
On trunk:
RequestBody.makePipedFileSliceStream — ios/Sources/GutenbergKitHTTP/RequestBody.swift (~line 217)
On feat/leverage-host-media-processing (PR #419):
DefaultMediaUploader.multipartBodyStream — ios/Sources/GutenbergKit/Sources/Media/MediaUploadServer.swift (same pattern, copied from above)
The pattern
Thread.detachNewThread {
defer {
output.close()
try? fileHandle.close()
}
var remaining = length
while remaining > 0 {
let chunkSize = min(65_536, remaining)
// FileHandle.read error silently swallowed by try?
guard let chunk = try? fileHandle.read(upToCount: chunkSize),
!chunk.isEmpty else {
break // silent exit — no logging
}
// write error — silent exit
guard Self.writeAll(chunk, to: output) else { return }
remaining -= chunk.count
}
}
Problems
- No logging: When the writer thread fails, there is no log message indicating what went wrong (I/O error, stream closed, etc.), making production issues hard to diagnose.
- No error propagation: The caller has no way to distinguish "stream completed successfully" from "stream failed after 50% of the file." Both look like the output stream closing.
- Root cause masked:
URLSession will report a generic network/content-length mismatch error, hiding the actual cause (disk I/O failure, file deleted, permissions changed, etc.).
Suggested improvements
- Log errors from
FileHandle.read() and OutputStream.write() before exiting
- Consider using
os.Logger consistent with other upload server logging
- Optionally, track whether the writer completed successfully so callers can report a more specific error
Summary
The background writer threads used for bound stream pairs silently swallow all errors. If a
FileHandle.read()fails mid-stream orOutputStream.write()returns an error, the thread exits without logging or propagating the failure. The output stream closes, the input stream sees EOF, andURLSessionreceives fewer bytes thanContent-Lengthpromised — resulting in a network error with no indication of the root cause.Locations
On
trunk:RequestBody.makePipedFileSliceStream—ios/Sources/GutenbergKitHTTP/RequestBody.swift(~line 217)On
feat/leverage-host-media-processing(PR #419):DefaultMediaUploader.multipartBodyStream—ios/Sources/GutenbergKit/Sources/Media/MediaUploadServer.swift(same pattern, copied from above)The pattern
Problems
URLSessionwill report a generic network/content-length mismatch error, hiding the actual cause (disk I/O failure, file deleted, permissions changed, etc.).Suggested improvements
FileHandle.read()andOutputStream.write()before exitingos.Loggerconsistent with other upload server logging