Skip to content

Commit 9929321

Browse files
authored
fix: Call-time snapshot compliance for streams (#987)
1 parent 892316f commit 9929321

File tree

4 files changed

+38
-2
lines changed

4 files changed

+38
-2
lines changed

Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,5 +330,9 @@ let package = Package(
330330
name: "SmithyHTTPAPITests",
331331
dependencies: ["SmithyHTTPAPI"]
332332
),
333+
.testTarget(
334+
name: "SmithyStreamsTests",
335+
dependencies: ["SmithyStreams", "Smithy"]
336+
),
333337
].compactMap { $0 }
334338
)

Sources/Smithy/Stream.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public enum StreamError: Error, Sendable {
6464
case invalidOffset(String)
6565
case notSupported(String)
6666
case connectionReleased(String)
67+
case writeToClosedStream(String)
6768
}
6869

6970
extension Stream {

Sources/SmithyStreams/BufferedStream.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import struct Foundation.Data
99
import class Foundation.NSRecursiveLock
1010
import protocol Smithy.Stream
11+
import enum Smithy.StreamError
1112

1213
/// A `Stream` implementation that buffers data in memory.
1314
/// The buffer size depends on the amount of data written and read.
@@ -207,8 +208,15 @@ public class BufferedStream: Stream, @unchecked Sendable {
207208
/// Writes the specified data to the stream.
208209
/// Then, continues a suspended reader (if any) to read the data.
209210
/// - Parameter data: The data to write.
211+
/// - Throws: `StreamError.writeToClosedStream` if a write is attempted after the stream is closed.
210212
public func write(contentsOf data: Data) throws {
211-
lock.withLockingClosure {
213+
try lock.withLockingClosure {
214+
// Do not allow writing to stream once it closes.
215+
// This ensures length of stream does not change once it reports a length.
216+
// Throw `StreamError.writeToClosedStream` to alert the caller.
217+
guard !isClosed else {
218+
throw StreamError.writeToClosedStream("Attempt to write to closed stream")
219+
}
212220
// append the data to the buffer
213221
// this will increase the in-memory size of the buffer
214222
_buffer.append(data)

Tests/ClientRuntimeTests/NetworkingTests/Streaming/BufferedStreamTests.swift renamed to Tests/SmithyStreamsTests/BufferedStreamTests.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77

88
import XCTest
9-
import ClientRuntime
9+
import enum Smithy.StreamError
1010
import class SmithyStreams.BufferedStream
1111

1212
final class BufferedStreamTests: XCTestCase {
@@ -208,6 +208,29 @@ final class BufferedStreamTests: XCTestCase {
208208
XCTAssertEqual(testData + additionalData, readData1)
209209
}
210210

211+
func test_write_throwsWriteToClosedStreamErrorWhenStreamIsClosed() throws {
212+
let subject = BufferedStream(data: testData, isClosed: true)
213+
XCTAssertThrowsError(
214+
try subject.write(contentsOf: additionalData),
215+
"No error was thrown when writing to a closed stream",
216+
{ error in
217+
switch error {
218+
case StreamError.writeToClosedStream:
219+
break // expected error, test passes
220+
default:
221+
XCTFail("Error was thrown, but not of expected error type")
222+
}
223+
}
224+
)
225+
}
226+
227+
func test_write_rejectsAdditionalDataWhenStreamIsClosed() throws {
228+
let subject = BufferedStream(data: testData, isClosed: true)
229+
XCTAssertThrowsError(try subject.write(contentsOf: additionalData))
230+
let readData = try subject.read(upToCount: Int.max)
231+
XCTAssertEqual(testData, readData)
232+
}
233+
211234
// MARK: - length
212235

213236
func test_length_returnsNilLengthBeforeStreamCloses() throws {

0 commit comments

Comments
 (0)