Skip to content

Provide doc comments for AsyncStream/AsyncThrowingStream #38535

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 21 commits into from
Aug 25, 2021

Conversation

invalidname
Copy link
Contributor

Source documentation for AsyncStream, AsyncThrowingStream, and their related types.

Copy link
Member

@amartini51 amartini51 left a comment

Choose a reason for hiding this comment

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

Looks good - minor comments throughout. I read through AsyncStream.swift in detail and it looks like the API in AsyngThrowingStream.swift is largely similar. If there are symbols I missed by skimming the latter, please call them out.

/// Use this convenience initializer when you have an asychronous function
/// that can produce elements for the stream, and don't want to invoke
/// a continuation manually. This initializer "unfolds" your closure into
/// a full-blown asynchronous stream. The created stream handles adherence to
Copy link
Member

Choose a reason for hiding this comment

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

This is the first time I've seen "adherence" to a protocol. What the sentence is describing isn't quite protocol conformance, which is the usual term, but seems a bit more general.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is kind of borrowing from the swift-evolution proposal:

This affords the case in which that unfolded function can leave the specification adherence of AsyncSequence to AsyncStream. In short the init(unfolding:onCancel:) initializer handles the terminal cases as well as the cancellation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Conformance might be better? Since it is a behavioral requirement of AsyncSequence (not a compile time enforced property)?

Or perhaps qualify it as "well behaved conformance"?

Copy link
Member

@amartini51 amartini51 Jul 27, 2021

Choose a reason for hiding this comment

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

I think we can just call it "conformance".

By analogy, supposed I made a type that implemented the methods that Collection requires, but crashed if you tried to iterate more than 3 times. That type doesn't correctly conform to the Collection protocol because a collection must support multiple iteration. Many protocols have requirements that can't be expressed in code like this.

Except in the context of discussing this sort of incorrect conformance, we shouldn't need to distinguish well-behaved conformance.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can just call it "conformance".

By analogy, supposed I made a type that implemented the methods that Collection requires, but crashed if you tried to iterate more than 3 times. That type doesn't correctly conform to the Collection protocol because a collection must support multiple iteration. Many protocols have requirements that can't be expressed in code like this.

Except in the context of discussing this sort of incorrect conformance, we shouldn't need to distinguish well-behaved conformance.

@natecook1000 what do you think?

Copy link
Contributor Author

@invalidname invalidname Jul 29, 2021

Choose a reason for hiding this comment

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

Personally, I'm fine with "conformance". I kept "adherence" as a synonym borrowed from the API proposal, because AsyncStream conforms to AsyncSequence in the first place, so it's almost a tautology to talk about conformance at all. I think the real point here is that the developer sends in a closure and this initializer provides all the other piping to serve as an AsyncStream, which in turn is an AsyncSequence. And that's what the final sentence here is about.

Comment on lines 287 to 293
/// let stream = AsyncStream<Int>(
/// unfolding: {
/// await Task.sleep(1 * 1_000_000_000)
/// return Int.random(in: 1...10)
/// },
/// onCancel: { @Sendable () in print ("Canceled.") }
/// )
Copy link
Member

Choose a reason for hiding this comment

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

Can/should this use the multiple trailing closure syntax from Swift 5.3?

/// yielding.
///
/// > Note: Acting on the remaining count is valid only when calls to
/// yield are mutually exclusive.
Copy link
Contributor

Choose a reason for hiding this comment

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

The info here is more than that - the remaining is a hint to how much may be remaining, a subsequent call may race on the potentials of consuming values so the remaining is technically an underestimate (from a thread safety standpoint)

case terminated
}

/// A strategy that handles exhaustion of a buffer’s capacity.
public enum BufferingPolicy {
/// Continue to add to the buffer, treating its capacity as infinite.
Copy link
Contributor

Choose a reason for hiding this comment

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

it might be good to point out as a reiteration that this is just a no limit buffer, in that it does not allocate unlimited ram ;) or something more eloquent than that...

case unbounded

/// When the buffer is full, discard the newly received element.
/// This enforces keeping the specified amount of oldest values.
///
/// This strategy enforces keeping the specified number of oldest values.
Copy link
Contributor

Choose a reason for hiding this comment

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

might want to add an "at most" to qualify the "specified number"

case bufferingOldest(Int)

/// When the buffer is full, discard the oldest element in the buffer.
/// This enforces keeping the specified amount of newest values.
///
/// This strategy enforces keeping the specified number of newest values.
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto here on the "at most"

Comment on lines 204 to 205
/// needed cleanup in the cancellation handler. After reaching a terminal
/// state, the `AsyncStream` disposes of the callback.
Copy link
Contributor

Choose a reason for hiding this comment

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

the closure internally is set to nil, removing any references it has to it.

///
/// - Parameter elementType: The type of element the `AsyncStream`
/// produces.
/// - Parameter limit: The maximum number of elements to hold in the buffer.
Copy link
Contributor

Choose a reason for hiding this comment

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

it is no longer called limit, it is now bufferingPolicy

///
/// let stream = AsyncStream<Int>(Int.self,
/// bufferingPolicy: .bufferingNewest(5)) { continuation in
/// Task.detached {
Copy link
Contributor

Choose a reason for hiding this comment

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

Im not sure how keepRunning can be safe (at least easily...) and likely that should be set to false in the onTermination

Copy link
Contributor Author

@invalidname invalidname Jul 29, 2021

Choose a reason for hiding this comment

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

Would you recommend a while true here, instead? That would prevent us from ever reaching continuation.finish(). Maybe it would be better pedagogy to change the stream to use a for-in loop to produce 100 random numbers, one per second, and then finish.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@phausler Changed this to a for-in loop. Does this look OK now?

/// Use this convenience initializer when you have an asychronous function
/// that can produce elements for the stream, and don't want to invoke
/// a continuation manually. This initializer "unfolds" your closure into
/// a full-blown asynchronous stream. The created stream handles adherence to
Copy link
Contributor

Choose a reason for hiding this comment

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

Conformance might be better? Since it is a behavioral requirement of AsyncSequence (not a compile time enforced property)?

Or perhaps qualify it as "well behaved conformance"?

///
/// This can be called more than once and returns to the caller immediately
/// without blocking for any awaiting consumption from the iteration.
/// without blocking for any awaiting consuption from the iteration.
Copy link
Contributor

Choose a reason for hiding this comment

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

consumption meaning the consuming of values. this is a spelling mistake?

@invalidname invalidname changed the base branch from release/5.5 to release/5.5-08092021 August 11, 2021 01:57
Copy link

@chuckdude chuckdude left a comment

Choose a reason for hiding this comment

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

Edits and comments throughout. I think there's one FIXME in the first file (AsyncStream).

/// property, return an `AsyncStream`, whose `build` closure -- called at
/// runtime to create the stream -- does the following:
///
/// 1. Creates a `QuakeMonitor` instance.

Choose a reason for hiding this comment

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

This should be a bulleted list, not a numbered list.

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 thought we preferred numbered lists for steps performed in sequence?

Choose a reason for hiding this comment

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

The preceding text isn't clear that these happen in sequence. If that's the case, then maybe tweak the lead.

/// The closure you provide to the `AsyncStream` in
/// `init(_:bufferingPolicy:_:)` receives an instance of this type when
/// invoked. Use this continuation to provide elements to the stream by
/// calling one of the `yield` methods, then terminate the stream normally by

Choose a reason for hiding this comment

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

Suggested change
/// calling one of the `yield` methods, then terminate the stream normally by
/// calling one of the `yield` methods, then terminate the stream by

You don't need the adverb here.

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 kind of think we do; terminate "normally" means to end the stream without throwing an error. If we did throw an error here, we'd say "terminate the stream with an error".

Copy link

@chuckdude chuckdude left a comment

Choose a reason for hiding this comment

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

OK to go.

/// property, return an `AsyncStream`, whose `build` closure -- called at
/// runtime to create the stream -- does the following:
///
/// 1. Creates a `QuakeMonitor` instance.

Choose a reason for hiding this comment

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

The preceding text isn't clear that these happen in sequence. If that's the case, then maybe tweak the lead.

Copy link

@chuckdude chuckdude left a comment

Choose a reason for hiding this comment

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

Approved with changes. Some typo fixes and what looks like edits from the first pass that were skipped.

/// Use this convenience initializer when you have an asychronous function
/// that can produce elements for the stream, and don't want to invoke
/// a continuation manually. This initializer "unfolds" your closure into
/// a full-blown asynchronous stream. The created stream handles adherence to

Choose a reason for hiding this comment

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

Suggested change
/// a full-blown asynchronous stream. The created stream handles adherence to
/// an asynchronous stream. The created stream handles adherence to

Choose a reason for hiding this comment

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

Don't use "full-blown" as it's jargon that can cause issues for localization.

@amartini51
Copy link
Member

@swift-ci Please test.

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 717d5c6

@shahmishal
Copy link
Member

@swift-ci Please test macOS

@najacque najacque merged commit 83fa59d into release/5.5-08092021 Aug 25, 2021
@najacque najacque deleted the asyncstream_doc_comments_80515639 branch August 25, 2021 00:47
amartini51 added a commit that referenced this pull request Aug 25, 2021
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.