-
Notifications
You must be signed in to change notification settings - Fork 432
[async-await] Base types for server implementation #1249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
glbrntt
merged 37 commits into
grpc:async-await
from
simonjbeaumont:async-await-server-only
Sep 7, 2021
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
1713de2
Async-await: Base types for server implementation
simonjbeaumont 3d100a7
fixup: Add GRPC prefix and remove Payload suffix from types
simonjbeaumont 49e633f
fixup: Remove comment
simonjbeaumont a09de1a
fixup: swiftformat now some type names are longer
simonjbeaumont 1967797
fixup: Implement separate context and move status promise to handler
simonjbeaumont cb80654
fixup: Rename GRPCAsyncResponseStreamWriter.sendResponse to just .send
simonjbeaumont 587259c
fixup: Rename AsyncServerHandlerTests.swift -> GRPCAsyncServerHandler…
simonjbeaumont 9f306b5
fixup: Move #if compiler guards before imports
simonjbeaumont b4aa358
fixup: Remove unused imports
simonjbeaumont 1d89bae
fixup: Add testHandlerThrowsGRPCStatusOK
simonjbeaumont 2acfcb5
fixup: Validate just one request in unary and server-streaming handle…
simonjbeaumont 04341f2
fixup: Rename GRPCAsyncStream to GRPCAsyncRequestStream
simonjbeaumont d8c0a2f
fixup: Remove __consuming from GRPCAsyncRequestStream.makeAsyncIterat…
simonjbeaumont c99c0bd
fixup: Rename State.observing to active and observer to userHandler
simonjbeaumont db2620f
fixup: Run swiftformat
simonjbeaumont 9da3672
fixup: Make handler class internal and wrap in public struct
simonjbeaumont e77bc1f
fixup: @inlinable and @usableFromInline
simonjbeaumont b7b60d9
fixup: Prepend _ to @inlinable/@usableFromInline internal properties …
simonjbeaumont fd80c6b
fixup: Remove public init from context
simonjbeaumont c85eef3
fixup: Check for exactly one request in unary and server-streaming wr…
simonjbeaumont 97cd688
fixup: Rename internal type from _GRPCAsyncServerHandler to AsyncServ…
simonjbeaumont 0ea704a
fixup: Make GRPCAsyncServerCallContext.logger mutable
simonjbeaumont cdd1428
fixup: Treat the user handler throwing GRPCStatus.ok as an error
simonjbeaumont d668315
Make use of new stream source and pausable writer
simonjbeaumont c658d7e
fixup: Move tasks out of enum assoc data to avoid race
simonjbeaumont 16e880c
fixup: swiftformat
simonjbeaumont 96bc859
fixup: Remove .finishingSuccessfully state and use writer end state
simonjbeaumont 9211bb8
fixup: Some @inlinable to request stream type
simonjbeaumont 209c6fe
fixup: For consistency fix comments to refer to user handler (cf. fun…
simonjbeaumont 32a35e5
fixup: Add TODO for supporting response headers
simonjbeaumont 6443b08
fixup: Use a void promise now we are using the writer to carry the st…
simonjbeaumont 73dfaae
fixup: await responseStreamWriter.cancel() in catch blocks
simonjbeaumont 0c819a5
fixup: Eventloop dances for delegate callbacks
simonjbeaumont 33b6582
fixup: Update docs for State.active
simonjbeaumont d2904bc
fixup: Remove unused promise param to interceptResponse
simonjbeaumont 5a45fd0
fixup: Remove stale comment and move task cancellation higher in hand…
simonjbeaumont 732f864
fixup: Add comments explaining use of class for context
simonjbeaumont File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
26 changes: 26 additions & 0 deletions
26
Sources/GRPC/AsyncAwaitSupport/CancellationError+GRPCStatusTransformable.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* Copyright 2021, gRPC Authors All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#if compiler(>=5.5) | ||
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) | ||
extension CancellationError: GRPCStatusTransformable { | ||
public func makeGRPCStatus() -> GRPCStatus { | ||
return GRPCStatus(code: .unavailable, message: nil) | ||
} | ||
} | ||
|
||
#endif |
55 changes: 55 additions & 0 deletions
55
Sources/GRPC/AsyncAwaitSupport/GRPCAsyncRequestStream.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* Copyright 2021, gRPC Authors All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#if compiler(>=5.5) | ||
|
||
/// This is currently a wrapper around AsyncThrowingStream because we want to be | ||
/// able to swap out the implementation for something else in the future. | ||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) | ||
public struct GRPCAsyncRequestStream<Element>: AsyncSequence { | ||
@usableFromInline | ||
internal typealias _WrappedStream = PassthroughMessageSequence<Element, Error> | ||
|
||
@usableFromInline | ||
internal let _stream: _WrappedStream | ||
|
||
@inlinable | ||
internal init(_ stream: _WrappedStream) { | ||
self._stream = stream | ||
} | ||
|
||
@inlinable | ||
public func makeAsyncIterator() -> Iterator { | ||
Self.AsyncIterator(self._stream) | ||
} | ||
|
||
public struct Iterator: AsyncIteratorProtocol { | ||
@usableFromInline | ||
internal var iterator: _WrappedStream.AsyncIterator | ||
|
||
@usableFromInline | ||
internal init(_ stream: _WrappedStream) { | ||
self.iterator = stream.makeAsyncIterator() | ||
} | ||
|
||
@inlinable | ||
public mutating func next() async throws -> Element? { | ||
try await self.iterator.next() | ||
} | ||
} | ||
} | ||
|
||
#endif |
112 changes: 112 additions & 0 deletions
112
Sources/GRPC/AsyncAwaitSupport/GRPCAsyncResponseStreamWriter.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* | ||
* Copyright 2021, gRPC Authors All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#if compiler(>=5.5) | ||
|
||
/// Writer for server-streaming RPC handlers to provide responses. | ||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) | ||
public struct GRPCAsyncResponseStreamWriter<Response> { | ||
@usableFromInline | ||
internal typealias Element = (Response, Compression) | ||
|
||
@usableFromInline | ||
internal typealias Delegate = AsyncResponseStreamWriterDelegate<Response> | ||
|
||
@usableFromInline | ||
internal let asyncWriter: AsyncWriter<Delegate> | ||
|
||
@inlinable | ||
internal init(wrapping asyncWriter: AsyncWriter<Delegate>) { | ||
self.asyncWriter = asyncWriter | ||
} | ||
|
||
@inlinable | ||
public func send( | ||
_ response: Response, | ||
compression: Compression = .deferToCallDefault | ||
) async throws { | ||
try await self.asyncWriter.write((response, compression)) | ||
} | ||
} | ||
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) | ||
@usableFromInline | ||
internal final class AsyncResponseStreamWriterDelegate<Response>: AsyncWriterDelegate { | ||
glbrntt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@usableFromInline | ||
internal typealias Element = (Response, Compression) | ||
|
||
@usableFromInline | ||
internal typealias End = GRPCStatus | ||
|
||
@usableFromInline | ||
internal let _context: GRPCAsyncServerCallContext | ||
|
||
@usableFromInline | ||
internal let _send: (Response, MessageMetadata) -> Void | ||
|
||
@usableFromInline | ||
internal let _finish: (GRPCStatus) -> Void | ||
|
||
@usableFromInline | ||
internal let _compressionEnabledOnServer: Bool | ||
|
||
// Create a new AsyncResponseStreamWriterDelegate. | ||
// | ||
// - Important: the `send` and `finish` closures must be thread-safe. | ||
@inlinable | ||
internal init( | ||
context: GRPCAsyncServerCallContext, | ||
compressionIsEnabled: Bool, | ||
send: @escaping (Response, MessageMetadata) -> Void, | ||
finish: @escaping (GRPCStatus) -> Void | ||
) { | ||
self._context = context | ||
self._compressionEnabledOnServer = compressionIsEnabled | ||
self._send = send | ||
self._finish = finish | ||
} | ||
|
||
@inlinable | ||
internal func _shouldCompress(_ compression: Compression) -> Bool { | ||
guard self._compressionEnabledOnServer else { | ||
return false | ||
} | ||
return compression.isEnabled(callDefault: self._context.compressionEnabled) | ||
} | ||
|
||
@inlinable | ||
internal func _send( | ||
_ response: Response, | ||
compression: Compression = .deferToCallDefault | ||
) { | ||
let compress = self._shouldCompress(compression) | ||
self._send(response, .init(compress: compress, flush: true)) | ||
} | ||
|
||
// MARK: - AsyncWriterDelegate conformance. | ||
|
||
@inlinable | ||
internal func write(_ element: (Response, Compression)) { | ||
self._send(element.0, compression: element.1) | ||
} | ||
|
||
@inlinable | ||
internal func writeEnd(_ end: GRPCStatus) { | ||
self._finish(end) | ||
} | ||
} | ||
|
||
#endif |
111 changes: 111 additions & 0 deletions
111
Sources/GRPC/AsyncAwaitSupport/GRPCAsyncServerCallContext.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* | ||
* Copyright 2021, gRPC Authors All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
#if compiler(>=5.5) | ||
|
||
import Logging | ||
import NIOConcurrencyHelpers | ||
import NIOHPACK | ||
|
||
// We use a `class` here because we do not want copy-on-write semantics. The instance that the async | ||
// handler holds must not diverge from the instance the implementor of the RPC holds. They hold these | ||
// instances on different threads (EventLoop vs Task). | ||
// | ||
// We considered wrapping this in a `struct` and pass it `inout` to the RPC. This would communicate | ||
// explicitly that it stores mutable state. However, without copy-on-write semantics, this could | ||
// make for a surprising API. | ||
// | ||
// We also considered an `actor` but that felt clunky at the point of use since adopters would need | ||
// to `await` the retrieval of a logger or the updating of the trailers and each would requrie a | ||
// promise to glue the NIO and async-await paradigms in the handler. | ||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) | ||
public final class GRPCAsyncServerCallContext { | ||
glbrntt marked this conversation as resolved.
Show resolved
Hide resolved
simonjbeaumont marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private let lock = Lock() | ||
|
||
/// Request headers for this request. | ||
public let headers: HPACKHeaders | ||
|
||
/// The logger used for this call. | ||
public var logger: Logger { | ||
get { self.lock.withLock { | ||
self._logger | ||
} } | ||
set { self.lock.withLock { | ||
self._logger = newValue | ||
} } | ||
} | ||
|
||
@usableFromInline | ||
internal var _logger: Logger | ||
|
||
/// Whether compression should be enabled for responses, defaulting to `true`. Note that for | ||
/// this value to take effect compression must have been enabled on the server and a compression | ||
/// algorithm must have been negotiated with the client. | ||
public var compressionEnabled: Bool { | ||
get { self.lock.withLock { | ||
self._compressionEnabled | ||
} } | ||
set { self.lock.withLock { | ||
self._compressionEnabled = newValue | ||
} } | ||
} | ||
|
||
private var _compressionEnabled: Bool = true | ||
|
||
/// A `UserInfo` dictionary which is shared with the interceptor contexts for this RPC. | ||
/// | ||
/// - Important: While `UserInfo` has value-semantics, this property retrieves from, and sets a | ||
/// reference wrapped `UserInfo`. The contexts passed to interceptors provide the same | ||
/// reference. As such this may be used as a mechanism to pass information between interceptors | ||
/// and service providers. | ||
public var userInfo: UserInfo { | ||
get { self.lock.withLock { | ||
self.userInfoRef.value | ||
} } | ||
set { self.lock.withLock { | ||
self.userInfoRef.value = newValue | ||
} } | ||
} | ||
|
||
/// A reference to an underlying `UserInfo`. We share this with the interceptors. | ||
@usableFromInline | ||
internal let userInfoRef: Ref<UserInfo> | ||
simonjbeaumont marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// Metadata to return at the end of the RPC. If this is required it should be updated before | ||
/// the `responsePromise` or `statusPromise` is fulfilled. | ||
public var trailers: HPACKHeaders { | ||
get { self.lock.withLock { | ||
return self._trailers | ||
} } | ||
set { self.lock.withLock { | ||
self._trailers = newValue | ||
} } | ||
} | ||
|
||
private var _trailers: HPACKHeaders = [:] | ||
|
||
@inlinable | ||
internal init( | ||
headers: HPACKHeaders, | ||
logger: Logger, | ||
userInfoRef: Ref<UserInfo> | ||
) { | ||
self.headers = headers | ||
self.userInfoRef = userInfoRef | ||
self._logger = logger | ||
} | ||
} | ||
|
||
#endif |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.