Skip to content

[stdlib]: better align Async{Throwing}Stream implementations #76571

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jamieQ
Copy link
Contributor

@jamieQ jamieQ commented Sep 19, 2024

Removes some of the discrepancies between the throwing & non-throwing variants of AsyncStream. In particular, adds multi-consumer support to the throwing variant, and updates the docs. Additional minor refactoring of existing implementation to streamline the code.

Fixes #76547

Removes some of the discrepancies between the throwing & non-throwing
variants of AsyncStream. In particular, adds multi-consumer support to
the throwing variant, and updates the docs. Additional minor refactoring
of existing implementation to streamline the code.

Fixes swiftlang#76547
Copy link
Contributor Author

@jamieQ jamieQ left a comment

Choose a reason for hiding this comment

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

@FranzBusch @ktoso i'd again appreciate your insights on the proposed changes here, if & when you have some time. thanks in advance!

@@ -356,11 +356,6 @@ public struct AsyncStream<Element> {
@available(SwiftStdlib 5.1, *)
extension AsyncStream: AsyncSequence {
/// The asynchronous iterator for iterating an asynchronous stream.
///
/// This type doesn't conform to `Sendable`. Don't use it from multiple
Copy link
Contributor Author

Choose a reason for hiding this comment

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

the 'this type isn't Sendable' assertion is still accurate, but i'm not sure it's particularly relevant or helpful to call out. the other warnings and references to fatalError() are no longer correct.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd instead add why it's not sendable. People sometimes ask about it, we should explain that so they don't assume it's just because it's "missing"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

excellent point, thank you.

we should explain that so they don't assume it's just because it's "missing"

this is, in fact, my current assumption. i presume it's currently not Sendable mainly due to path-dependence/history – the original implementations intentionally did not support concurrent consumption, but then that restriction was lifted. the implementation of the relevant types appear to be effectively Sendable (basically everything gets forwarded from the public API into the various internal 'storage' types which appear generally thread-safe).

Comment on lines +129 to +134
// Presence of continuations implies no pending elements.
// TODO: which assertion flavor should be used?
assert(
state.pending.isEmpty,
"Continuations should imply no pending elements."
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

i would appreciate feedback on a reasonable way to enforce this invariant. per the docs regarding assertion flavors in the stdlib, i would be inclined to think that _internalInvariant() is maybe the most appropriate function to use here. however, that symbol isn't visible in this context since this code resides in the Concurrency module. any thoughts on this, or references to prior art would be appreciated!

Copy link
Contributor

Choose a reason for hiding this comment

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

Basically assert is this is ok to not check in a released toolchain, and precondition if it should keep getting checked. Don't worry too much about the internal versions. I'm not sure the message explains the issue very well, maybe make it more descriptive why this is a crash

unlock()
continuation.resume(returning: toSend)
} else if state.terminal {
if state.terminal {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

per @FranzBusch's prior observation, the branches in which we were handling cases that implied both pending elements and outstanding continuations should in fact be dead. the rationale is that elements should only be buffered if there are no awaiting consumers, and continuations are stored only if there are no pending elements. sanity checks appreciated regarding this analysis, however.

@@ -287,7 +267,7 @@ extension AsyncThrowingStream {
}

struct State {
var continuation: UnsafeContinuation<Element?, Error>?
var continuations = [UnsafeContinuation<Element?, Error>]()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

primary behavioral changes to make AsyncThrowingStream better match AsyncStream's existing behavior start here.

@@ -437,41 +437,257 @@ class NotSendable {}

// MARK: - Multiple consumers

tests.test("FIFO delivery order with multiple consumers") {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'm not sure this delivery order is ever publicly stated as a guarantee of these types, but it is how they currently work

Copy link
Contributor

Choose a reason for hiding this comment

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

It's fine to test for it I guess. I'd probably not surface it in docs yet though. Opinions @FranzBusch @phausler ?

@jamieQ
Copy link
Contributor Author

jamieQ commented Sep 27, 2024

ping @ktoso @FranzBusch (or lmk if i should @ someone else)

@ktoso
Copy link
Contributor

ktoso commented Sep 27, 2024

I'll look at it but we've all just been at the swift on server conference so we've been offline a bit these days. I'll be able to review shortly once I'm back home on Monday, sorry for the delay :)

@jamieQ
Copy link
Contributor Author

jamieQ commented Oct 17, 2024

@ktoso low-priority ping

Copy link
Contributor

@ktoso ktoso left a comment

Choose a reason for hiding this comment

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

This would be better reviewed by @phausler and @FranzBusch -- I'm a bit out of the loop what our plans with this type are.

@@ -356,11 +356,6 @@ public struct AsyncStream<Element> {
@available(SwiftStdlib 5.1, *)
extension AsyncStream: AsyncSequence {
/// The asynchronous iterator for iterating an asynchronous stream.
///
/// This type doesn't conform to `Sendable`. Don't use it from multiple
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd instead add why it's not sendable. People sometimes ask about it, we should explain that so they don't assume it's just because it's "missing"

Comment on lines +129 to +134
// Presence of continuations implies no pending elements.
// TODO: which assertion flavor should be used?
assert(
state.pending.isEmpty,
"Continuations should imply no pending elements."
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Basically assert is this is ok to not check in a released toolchain, and precondition if it should keep getting checked. Don't worry too much about the internal versions. I'm not sure the message explains the issue very well, maybe make it more descriptive why this is a crash

@@ -437,41 +437,257 @@ class NotSendable {}

// MARK: - Multiple consumers

tests.test("FIFO delivery order with multiple consumers") {
Copy link
Contributor

Choose a reason for hiding this comment

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

It's fine to test for it I guess. I'd probably not surface it in docs yet though. Opinions @FranzBusch @phausler ?

@ktoso
Copy link
Contributor

ktoso commented Oct 17, 2024

Please unmark this "draft" if it's expecting reviews -- a draft basically signals "not ready to review" @jamieQ :)

Thanks for the ping and your work here! I pined some other folks who are more up to date about the plans about the stream type

@jamieQ jamieQ marked this pull request as ready for review October 17, 2024 12:15
@jamieQ
Copy link
Contributor Author

jamieQ commented Oct 29, 2024

@FranzBusch @phausler do either of you have feedback to share on this? thanks in advance!

@jamieQ
Copy link
Contributor Author

jamieQ commented Nov 21, 2024

@FranzBusch @phausler – wondering if either of you have bandwidth to provide feedback on this effort in the relatively-near term. if not, is there someone you'd suggest i try to involve instead? thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

AsyncStream & AsyncThrowingStream behavioral divergences & misleading documentation
2 participants