Skip to content

iOS: OutputStream.write return value 0 treated as fatal in bound stream writers #437

@jkmassel

Description

@jkmassel

Summary

The writeAll pattern used in bound-stream-pair writers treats OutputStream.write(_:maxLength:) returning 0 as a fatal error. Apple's documentation defines a 0 return as "a fixed-length stream has reached its capacity." It is unclear whether a bound stream pair (from Stream.getBoundStreams(withBufferSize:)) qualifies as a "fixed-length stream," meaning write() could return 0 when the internal buffer is full rather than blocking.

If 0 is returned as a backpressure signal, the current code silently aborts the write, closes the stream, and leaves URLSession waiting for Content-Length bytes that never arrive — causing the upload to hang.

Locations

  1. RequestBody.makePipedFileSliceStreamios/Sources/GutenbergKitHTTP/RequestBody.swift (~line 236)
  2. DefaultMediaUploader.writeAllios/Sources/GutenbergKit/Sources/Media/MediaUploadServer.swift (introduced in fix: improve upload server error handling and memory efficiency #419, same pattern)

Both use:

let result = output.write(base.advanced(by: written), maxLength: ...)
if result <= 0 { return }

Apple documentation

  • OutputStream.write(_:maxLength:): returns 0 for "a fixed-length stream that has reached its capacity"
  • CFWriteStreamWrite: "If the stream is not full, this call blocks until at least one byte is written"
  • Neither document explicitly states whether bound stream pairs block or return 0 when the buffer is full

Suggested fix

Distinguish 0 (retry) from -1 (error):

if result == -1 { return false }
if result == 0 {
    Thread.sleep(forTimeInterval: 0.001)
    continue
}

Risk

Low probability in practice — uploads would need to outpace URLSession's read rate long enough to fill the 65KB buffer. More likely with large files on slow connections.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions