-
Notifications
You must be signed in to change notification settings - Fork 159
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
Add MultiProducerSingleConsumerChannel
#305
base: main
Are you sure you want to change the base?
Conversation
ddd7523
to
65e8957
Compare
@swift-ci please test |
4904039
to
662693e
Compare
AsyncBackpressuredStream
proposal and implementationMultiProducerSingleConsumerChannel
|
||
### Upstream producer termination | ||
|
||
The producer will get notified about termination through the `onTerminate` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can you add calls to onTerminate
to the snippets below?
}) | ||
} | ||
} catch { | ||
// `send(contentsOf:)` throws if the asynchronous stream already terminated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be clearer as another case on the sendResult?
/// - Parameters: | ||
/// - low: When the number of buffered elements drops below the low watermark, producers will be resumed. | ||
/// - high: When the number of buffered elements rises above the high watermark, producers will be suspended. | ||
/// - waterLevelForElement: A closure used to compute the contribution of each buffered element to the current water level. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, neat. This works nicely for collections.
for producerContinuation in producerContinuations { | ||
switch producerContinuation { | ||
case .closure(let onProduceMore): | ||
onProduceMore(.failure(CancellationError())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a state machine bug I picked up running the OpenAPIURLSession tests with this type:
onProduceMore(.failure(CancellationError())) | |
onProduceMore(.success(())) |
Might be worth seeing if we can have similar tests in this project. This particular test, 1kChunk_10kChunks_100BDownloadWatermark
, exercised the suspend and resume of the producer a lot due to the relation of the chunk size to the watermark.
public static func makeChannel( | ||
of elementType: Element.Type = Element.self, | ||
throwing failureType: Failure.Type = Never.self, | ||
backpressureStrategy: Source.BackpressureStrategy | ||
) -> ChannelAndStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for this returning a nominal type? Previously it returned a tuple which allows for initialization of the sequence as follows:
let (channel, source) = MultiProducerSingleConsumerChannel.makeChannel(/* ... */)
This is also what the documentation comment on the type suggests.
However, with the nominal type you cannot do that and, if you are storing these as properties, are forced to do this...
let channelAndSource = MultiProducerSingleConsumerChannel.makeChannel(/* ... */)
self.channel = channelAndSource.channel
self.source = channelAndSource.source
...instead of this...
(self.channel, self.source) = MultiProducerSingleConsumerChannel.makeChannel(/* ... */)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly, tuples don't support ~Copyable
types. That's the only reason for a nominal type here.
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#if compiler(>=6.0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please can we consider offering a pre-Swift 6 variant of this type. There are several places we'd like to adopt this that need to also support Swift 5.9+ for the foreseeable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm not sure yet. Especially since it is part of the public API in the Source
. Making the Source
copyable makes the type behave way differently.
private let backing: _Backing | ||
|
||
@frozen | ||
public struct ChannelAndStream: ~Copyable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was it intended to be called ChannelAndStream
while it is actually a container for channel
and source
?
Shouldn't it be called ChannelAndSource
instead?
Hi!
|
@ser-0xff Thanks for trying it out. We don't plan on support library evolution mode in any of our packages. What we normally advise is to not compile packages as frameworks but rather add this library as an |
Thank you for the reply... |
@ser-0xff I guess we need to wrap up the API surface we want to support then and just do internal import as suggested? But let's take that offline from this PR - thanks @FranzBusch for quick reply. |
Hi, @FranzBusch Here is a minimized test showing the usage pattern we want if you will find some time to look at. |
The source has a |
Missed |
HI, Franz! |
You are right. I haven't gotten around to implement the correct handling of multiple sources yet. |
# Motivation The pitch to add external backpressure support to the standard libraries `AsyncStream` got returned for revision since there are larger open questions around `AsyncSequence`. However, having external backpressure in a source asynchronous sequence is becoming more and more important. # Modification This PR adds a modified proposal and implementation that brings the Swift Evolution proposal over to Swift Async Algorithms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think with some minor edits the pitch is good to go; id like to at the bare minimum see some of that testing reenabled to ensure it isn't leaking.
/// It supports arbitrary many elements but if only up to one ``Element`` is stored it does **not** allocate separate storage on the heap | ||
/// and instead stores the ``Element`` inline. | ||
@usableFromInline | ||
struct _TinyArray<Element> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this could perhaps be subsumed by InlineArray
instead since the original intent was to have a faster storage (this is not a blocking concept)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InlineArray
isn't capable of fully replacing the need here. The idea behind TinyArray
is to have a fast path for a single element that doesn't allocate and one that allocates if there are more. Since we can't tell at compile time how many producers we have we need this runtime dynamism.
@@ -17,24 +17,35 @@ import Glibc | |||
import WinSDK | |||
#endif | |||
|
|||
internal struct Lock { | |||
@usableFromInline | |||
internal class Lock { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
umm why is this a non-final class? If we are moving to a class then we should just use Mutex
from Synchronization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed all of the changes here since the latest code now uses Mutex
.
.../AsyncAlgorithms/MultiProducerSingleConsumerChannel/MultiProducerSingleConsumerChannel.swift
Outdated
Show resolved
Hide resolved
final class MultiProducerSingleConsumerChannelTests: XCTestCase { | ||
// MARK: - sequenceDeinitialized | ||
|
||
// Following tests are disabled since the channel is not getting deinited due to a known bug |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a requirement to landing; id rather not introduce a type with a known leak
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added significantly more tests now and also re-enabled those.
|
||
```swift | ||
// Writing new values and providing a callback when to produce more | ||
try source.send(contentsOf: sequence, onProduceMore: { result in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this be written without ambiguity as a trailing closure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes in a synchronous context both send
methods can be called without ambiguity, e.g. this compiles:
try source.send(1)
source.send(2) { result in
print(result)
}
The only thing that needs some special handling is if you want to call a sync send from an async context since the async methods are preferred; however, users can workaround this by creating an inline closure, e.g.:
try { try source.send(1) }()
662693e
to
944b727
Compare
Motivation
The pitch to add external backpressure support to the standard libraries
AsyncStream
got returned for revision since there are larger open questions aroundAsyncSequence
. However, having external backpressure in a source asynchronous sequence is becoming more and more important.Modification
This PR adds a modified proposal and implementation that brings the Swift Evolution proposal over to Swift Async Algorithms.