Skip to content

Add an internal pausable writer #1245

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
merged 3 commits into from
Sep 2, 2021
Merged

Conversation

glbrntt
Copy link
Collaborator

@glbrntt glbrntt commented Aug 20, 2021

Motivation:

Async RPCs which stream outbound messages (i.e. requests on the client,
responses on the server) will be provided with a means to asynchronously
send messages. This object should suspend if the underlying channel is
not currently writable.

Modifications:

Add an 'AsyncWriter' as the underlying type for these objects which
suspends if the writer is 'paused' and resumes when the writer is
resumed.

Result:

We have a writer capable of suspending writes.

@glbrntt glbrntt added the 🔨 semver/patch No public API change. label Aug 20, 2021
@glbrntt glbrntt requested a review from fabianfett August 20, 2021 14:27
@glbrntt
Copy link
Collaborator Author

glbrntt commented Aug 20, 2021

cc @simonjbeaumont

Comment on lines 70 to 76
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
fileprivate final class AsyncWriterStorage<
Element,
End,
Delegate: AsyncWriterDelegate
> where Delegate.Element == Element, Delegate.End == End {
/// A value pending a write.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a hill I will die on, but if we put the Storage into the AsyncWriter, we don't need to redefine the types here, since they would be inherited from their enclosing struct.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd rather have this than the extra level of indentation tbh!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, In this case we should still remove the Element and End from the generic parameters. They are implicit in the Delegate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agh, yes, missed this one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I think this change wouldn't be great for public APIs since Xcode suggests the "full" type name rather than any type aliases, e.g.: "AsyncWriter<DelegateImplType<String, Int>>.Element" rather than "Element".

}

@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
fileprivate final class AsyncWriterStorage<
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider making this an actor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No -- pause and resume won't be called from asynchronous contexts.

@glbrntt glbrntt requested a review from fabianfett August 24, 2021 10:46
}

/// A lock which must be help when accessing or modifying mutable state held by this storage.
private var lock: Lock
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a var?

Comment on lines 165 to 167
case let .element(pendingElement):
self.delegate.write(pendingElement.value)
pendingElement.continuation.resume()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this. Let's consider we have a buffer full with 16 writes waiting. After we have written the first element, do we want to allow more writes from the users side? There are still 15 writes pending...

Further this is in a non detached loop. This means we will def. write all 16 items, and we will signal to everyone waiting, you can write again...

If this was an actor, we could consider using await Task.yield to check after a number of writes if the target is still writable and only in those cases to start succeed the continuations.

Comment on lines 16 to 30
#if compiler(>=5.5)

extension Result where Failure == Error {
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
internal init(catching operation: @escaping () async throws -> Success) async {
do {
let success = try await operation()
self = .success(success)
} catch {
self = .failure(error)
}
}
}

#endif // compiler(>=5.5)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used anywhere? CMD+F doesn't show me a use.

writer.resume()
try await written1
try await written2
XCTAssertEqual(delegate.elements.sorted(), ["wunch", "meat"].sorted())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have sorted() here? I would expect that we check that the elements arrive in order here.

writer.pause()

async let w1: Void = writer.write("hitchcock")
await Task.yield()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we have the Task.yield() here?

}

writer.resume()
try await writer.finish(0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe check delegate.element.isEmpty

try await writer.write("cheddar")
} verify: { error in
XCTAssertEqual(error as? AsyncWriterError, .alreadyFinished)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe check that write was not forwarded to delegate


@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
internal func XCTAssertThrowsError<T>(
_ expression: @escaping () async throws -> T,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just out of interest for AHC, can we make this an @autoclosure?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't supported when I implemented this but seems to work now!


// `writes` will never exceed `maxWritesBeforeYield` so unchecked arithmetic is okay here.
writes &+= 1
if writes == self.maxWritesBeforeYield {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, we also need to check, if the queue is empty? You want to reset your counter to 0 once you have written all elements.

Copy link
Collaborator Author

@glbrntt glbrntt Aug 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

writes is local to this function so doesn't need to be reset?

Although I suppose if writes == self.maxWritesBeforeYield and self.pendingElements.isEmpty then we'll yield even though we're about to exit the function anyway. Maybe that doesn't matter so much though.

// Still 0.
XCTAssertEqual(delegate.end, 0)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs

  • testCallToFinishWhilePending()
  • testTooManyCallsToFinishWhilePending()

Motivation:

Async RPCs which stream outbound messages (i.e. requests on the client,
responses on the server) will be provided with a means to asynchronously
send messages. This object should suspend if the underlying channel is
not currently writable.

Modifications:

Add an 'AsyncWriter' as the underlying type for these objects which
suspends if the writer is 'paused' and resumes when the writer is
resumed.

Result:

We have a writer capable of suspending writes.
Copy link
Collaborator

@fabianfett fabianfett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Really like how it turned out! Thanks!

@glbrntt glbrntt merged commit bae3349 into grpc:async-await Sep 2, 2021
@glbrntt glbrntt deleted the gb-async-writer branch September 2, 2021 15:45
simonjbeaumont pushed a commit to simonjbeaumont/grpc-swift that referenced this pull request Sep 3, 2021
Motivation:

Async RPCs which stream outbound messages (i.e. requests on the client,
responses on the server) will be provided with a means to asynchronously
send messages. This object should suspend if the underlying channel is
not currently writable.

Modifications:

Add an 'AsyncWriter' as the underlying type for these objects which
suspends if the writer is 'paused' and resumes when the writer is
resumed.

Result:

We have a writer capable of suspending writes.
glbrntt pushed a commit that referenced this pull request Sep 7, 2021
This commit implements some of the types required by the proposal for async/await support, added in #1231.

To aid reviewing, only the types required for the server are included. They have been pulled in from the proof-of-concept implementation linked from the proposal PR. It is a complimentary PR to #1243 ("Async-await: Base types for client implementation").

It provides a unified `AsyncServerHandler` for all of the RPC types which avoids a substantial amount of code duplication that is found in the existing handlers. Wrappers are provided for the four RPC types. Otherwise it is analogous to the existing `BidirectionalStreamingServerHandler`.

It's worth calling out that this PR makes use of some placeholder types which are not intended to be final. Specifically:

* `AsyncResponseStreamWriter` is expected to be superseded by the `AsyncWriter` from #1245.
* `AsyncServerCallContext` conformance has been added to the existing `ServerCallContextBase`. It is expected that we will provide a new implementation of `AsyncServerCallContext` that is independent from the existing call context types.
glbrntt added a commit to glbrntt/grpc-swift that referenced this pull request Sep 16, 2021
Motivation:

Async RPCs which stream outbound messages (i.e. requests on the client,
responses on the server) will be provided with a means to asynchronously
send messages. This object should suspend if the underlying channel is
not currently writable.

Modifications:

Add an 'AsyncWriter' as the underlying type for these objects which
suspends if the writer is 'paused' and resumes when the writer is
resumed.

Result:

We have a writer capable of suspending writes.
glbrntt pushed a commit to glbrntt/grpc-swift that referenced this pull request Sep 16, 2021
This commit implements some of the types required by the proposal for async/await support, added in grpc#1231.

To aid reviewing, only the types required for the server are included. They have been pulled in from the proof-of-concept implementation linked from the proposal PR. It is a complimentary PR to grpc#1243 ("Async-await: Base types for client implementation").

It provides a unified `AsyncServerHandler` for all of the RPC types which avoids a substantial amount of code duplication that is found in the existing handlers. Wrappers are provided for the four RPC types. Otherwise it is analogous to the existing `BidirectionalStreamingServerHandler`.

It's worth calling out that this PR makes use of some placeholder types which are not intended to be final. Specifically:

* `AsyncResponseStreamWriter` is expected to be superseded by the `AsyncWriter` from grpc#1245.
* `AsyncServerCallContext` conformance has been added to the existing `ServerCallContextBase`. It is expected that we will provide a new implementation of `AsyncServerCallContext` that is independent from the existing call context types.
glbrntt added a commit to glbrntt/grpc-swift that referenced this pull request Sep 16, 2021
Motivation:

Async RPCs which stream outbound messages (i.e. requests on the client,
responses on the server) will be provided with a means to asynchronously
send messages. This object should suspend if the underlying channel is
not currently writable.

Modifications:

Add an 'AsyncWriter' as the underlying type for these objects which
suspends if the writer is 'paused' and resumes when the writer is
resumed.

Result:

We have a writer capable of suspending writes.
glbrntt pushed a commit to glbrntt/grpc-swift that referenced this pull request Sep 16, 2021
This commit implements some of the types required by the proposal for async/await support, added in grpc#1231.

To aid reviewing, only the types required for the server are included. They have been pulled in from the proof-of-concept implementation linked from the proposal PR. It is a complimentary PR to grpc#1243 ("Async-await: Base types for client implementation").

It provides a unified `AsyncServerHandler` for all of the RPC types which avoids a substantial amount of code duplication that is found in the existing handlers. Wrappers are provided for the four RPC types. Otherwise it is analogous to the existing `BidirectionalStreamingServerHandler`.

It's worth calling out that this PR makes use of some placeholder types which are not intended to be final. Specifically:

* `AsyncResponseStreamWriter` is expected to be superseded by the `AsyncWriter` from grpc#1245.
* `AsyncServerCallContext` conformance has been added to the existing `ServerCallContextBase`. It is expected that we will provide a new implementation of `AsyncServerCallContext` that is independent from the existing call context types.
glbrntt added a commit that referenced this pull request Sep 16, 2021
Motivation:

Async RPCs which stream outbound messages (i.e. requests on the client,
responses on the server) will be provided with a means to asynchronously
send messages. This object should suspend if the underlying channel is
not currently writable.

Modifications:

Add an 'AsyncWriter' as the underlying type for these objects which
suspends if the writer is 'paused' and resumes when the writer is
resumed.

Result:

We have a writer capable of suspending writes.
glbrntt pushed a commit that referenced this pull request Sep 16, 2021
This commit implements some of the types required by the proposal for async/await support, added in #1231.

To aid reviewing, only the types required for the server are included. They have been pulled in from the proof-of-concept implementation linked from the proposal PR. It is a complimentary PR to #1243 ("Async-await: Base types for client implementation").

It provides a unified `AsyncServerHandler` for all of the RPC types which avoids a substantial amount of code duplication that is found in the existing handlers. Wrappers are provided for the four RPC types. Otherwise it is analogous to the existing `BidirectionalStreamingServerHandler`.

It's worth calling out that this PR makes use of some placeholder types which are not intended to be final. Specifically:

* `AsyncResponseStreamWriter` is expected to be superseded by the `AsyncWriter` from #1245.
* `AsyncServerCallContext` conformance has been added to the existing `ServerCallContextBase`. It is expected that we will provide a new implementation of `AsyncServerCallContext` that is independent from the existing call context types.
glbrntt added a commit that referenced this pull request Nov 26, 2021
Motivation:

Async RPCs which stream outbound messages (i.e. requests on the client,
responses on the server) will be provided with a means to asynchronously
send messages. This object should suspend if the underlying channel is
not currently writable.

Modifications:

Add an 'AsyncWriter' as the underlying type for these objects which
suspends if the writer is 'paused' and resumes when the writer is
resumed.

Result:

We have a writer capable of suspending writes.
glbrntt pushed a commit that referenced this pull request Nov 26, 2021
This commit implements some of the types required by the proposal for async/await support, added in #1231.

To aid reviewing, only the types required for the server are included. They have been pulled in from the proof-of-concept implementation linked from the proposal PR. It is a complimentary PR to #1243 ("Async-await: Base types for client implementation").

It provides a unified `AsyncServerHandler` for all of the RPC types which avoids a substantial amount of code duplication that is found in the existing handlers. Wrappers are provided for the four RPC types. Otherwise it is analogous to the existing `BidirectionalStreamingServerHandler`.

It's worth calling out that this PR makes use of some placeholder types which are not intended to be final. Specifically:

* `AsyncResponseStreamWriter` is expected to be superseded by the `AsyncWriter` from #1245.
* `AsyncServerCallContext` conformance has been added to the existing `ServerCallContextBase`. It is expected that we will provide a new implementation of `AsyncServerCallContext` that is independent from the existing call context types.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants