Skip to content

Commit a93d252

Browse files
Move to completed state before cancelling task during finish() (#1302)
As reported in #1299 and #1301, there are some scenarios where the closing the channel or using the response stream writer after the channel has been closed will lead to a crash. Specifically, when `finish()` is called the state was not progressed to `.completed` before cancelling the task. This was to maintain parity with the ELG-based API where the status and the trailers were still sent after `finish()` is called. We now believe this to be misguided and we shouldn't expect to be able to send anything on the channel at this point because we are tearing the handler and the channel down. This changes `finish()` to move to the `.completed` state before cancelling the `userHandlerTask`. As a result, when the completion handler for the user function fires, it will call `handleError(_:)` with `CancellationError` (as before) but now the error handler will not attempt to send the status or trailers back via the interceptors because the state will be in `.completed`. Tests for receiving an error after headers and after a message have been added.
1 parent 73d8184 commit a93d252

File tree

3 files changed

+54
-7
lines changed

3 files changed

+54
-7
lines changed

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncServerHandler.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ internal final class AsyncServerHandler<
327327
self.state = .completed
328328

329329
case .active:
330+
self.state = .completed
331+
self.interceptors = nil
330332
self.userHandlerTask?.cancel()
331333

332334
case .completed:
@@ -524,8 +526,10 @@ internal final class AsyncServerHandler<
524526
self.interceptors.send(.message(response, metadata), promise: nil)
525527

526528
case .completed:
527-
/// If we are in the completed state then the async writer delegate must have terminated.
528-
preconditionFailure()
529+
/// If we are in the completed state then the async writer delegate will have been cancelled,
530+
/// however the cancellation is asynchronous so there's a chance that we receive this callback
531+
/// after that has happened. We can drop the response.
532+
()
529533
}
530534
}
531535

Tests/GRPCTests/AsyncAwaitSupport/AsyncIntegrationTests.swift

+14-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ final class AsyncIntegrationTests: GRPCTestCase {
4949
}
5050

5151
override func tearDown() {
52-
XCTAssertNoThrow(try self.client.close().wait())
53-
XCTAssertNoThrow(try self.server.close().wait())
54-
XCTAssertNoThrow(try self.group.syncShutdownGracefully())
52+
XCTAssertNoThrow(try self.client?.close().wait())
53+
XCTAssertNoThrow(try self.server?.close().wait())
54+
XCTAssertNoThrow(try self.group?.syncShutdownGracefully())
5555
super.tearDown()
5656
}
5757

@@ -195,6 +195,17 @@ final class AsyncIntegrationTests: GRPCTestCase {
195195
])
196196
}
197197
}
198+
199+
func testServerCloseAfterMessage() {
200+
XCTAsyncTest {
201+
let update = self.echo.makeUpdateCall()
202+
try await update.requestStream.send(.with { $0.text = "hello" })
203+
_ = try await update.responses.first(where: { _ in true })
204+
XCTAssertNoThrow(try self.server.close().wait())
205+
self.server = nil // So that tearDown() does not call close() again.
206+
try await update.requestStream.finish()
207+
}
208+
}
198209
}
199210

200211
extension HPACKHeaders {

Tests/GRPCTests/GRPCAsyncServerHandlerTests.swift

+34-2
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ class AsyncServerHandlerTests: ServerHandlerTestCaseBase {
278278

279279
await assertThat(self.recorder.metadata, .nil())
280280
await assertThat(self.recorder.messages, .isEmpty())
281-
await assertThat(self.recorder.status, .notNil(.hasCode(.unavailable)))
282-
await assertThat(self.recorder.trailers, .is([:]))
281+
await assertThat(self.recorder.status, .nil())
282+
await assertThat(self.recorder.trailers, .nil())
283283
} }
284284

285285
func testFinishAfterMessage() { XCTAsyncTest {
@@ -296,6 +296,38 @@ class AsyncServerHandlerTests: ServerHandlerTestCaseBase {
296296
// Wait for tasks to finish.
297297
await handler.userHandlerTask?.value
298298

299+
await assertThat(self.recorder.messages.first, .is(ByteBuffer(string: "hello")))
300+
await assertThat(self.recorder.status, .nil())
301+
await assertThat(self.recorder.trailers, .nil())
302+
} }
303+
304+
func testErrorAfterHeaders() { XCTAsyncTest {
305+
let handler = self.makeHandler(observer: self.echo(requests:responseStreamWriter:context:))
306+
307+
handler.receiveMetadata([:])
308+
handler.receiveError(CancellationError())
309+
310+
// Wait for tasks to finish.
311+
await handler.userHandlerTask?.value
312+
313+
await assertThat(self.recorder.status, .notNil(.hasCode(.unavailable)))
314+
await assertThat(self.recorder.trailers, .is([:]))
315+
} }
316+
317+
func testErrorAfterMessage() { XCTAsyncTest {
318+
let handler = self.makeHandler(observer: self.echo(requests:responseStreamWriter:context:))
319+
320+
handler.receiveMetadata([:])
321+
handler.receiveMessage(ByteBuffer(string: "hello"))
322+
323+
// Wait for the async user function to have processed the message.
324+
try self.recorder.recordedMessagePromise.futureResult.wait()
325+
326+
handler.receiveError(CancellationError())
327+
328+
// Wait for tasks to finish.
329+
await handler.userHandlerTask?.value
330+
299331
await assertThat(self.recorder.messages.first, .is(ByteBuffer(string: "hello")))
300332
await assertThat(self.recorder.status, .notNil(.hasCode(.unavailable)))
301333
await assertThat(self.recorder.trailers, .is([:]))

0 commit comments

Comments
 (0)