Skip to content

Commit a68ef61

Browse files
authored
Merge pull request #3 from heckj/grammar-fixes
fixes grammar and some word order issues
2 parents 91f7e78 + 062b6a4 commit a68ef61

File tree

1 file changed

+44
-44
lines changed

1 file changed

+44
-44
lines changed

Evolution/0016-mutli-producer-single-consumer-channel.md

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
introduced new `Async[Throwing]Stream` types which act as root asynchronous
1818
sequences. These two types allow bridging from synchronous callbacks such as
1919
delegates to an asynchronous sequence. This proposal adds a new root primitive
20-
with the goal to model asynchronous multi-producer-single-consumer systems.
20+
with the goal of modeling asynchronous multi-producer-single-consumer systems.
2121

2222
## Motivation
2323

@@ -38,31 +38,31 @@ The below sections are providing a detailed explanation of each of those.
3838
### Backpressure
3939

4040
In general, backpressure is the mechanism that prevents a fast producer from
41-
overwhelming a slow consumer. It helps stability of the overall system by
41+
overwhelming a slow consumer. It helps the stability of the overall system by
4242
regulating the flow of data between different components. Additionally, it
43-
allows to put an upper bound on resource consumption of a system. In reality,
43+
allows us to put an upper bound on the resource consumption of a system. In reality,
4444
backpressure is used in almost all networked applications.
4545

46-
In Swift, asynchronous sequence also have the concept of internal backpressure.
47-
This modeled by the pull-based implementation where a consumer has to call
46+
In Swift, asynchronous sequences also have the concept of internal backpressure.
47+
This is modeled by the pull-based implementation where a consumer has to call
4848
`next` on the `AsyncIterator`. In this model, there is no way for a consumer to
4949
overwhelm a producer since the producer controls the rate of pulling elements.
5050

51-
However, the internal backpressure of an asynchronous isn't the only
51+
However, the internal backpressure of an asynchronous sequence isn't the only
5252
backpressure in play. There is also the source backpressure that is producing
53-
the actual elements. For a backpressured system it is important that every
53+
the actual elements. For a backpressured system, it is important that every
5454
component of such a system is aware of the backpressure of its consumer and its
5555
producer.
5656

57-
Let's take a quick look how our current root asynchronous sequences are handling
57+
Let's take a quick look at how our current root asynchronous sequences are handling
5858
this.
5959

6060
`Async[Throwing]Stream` aims to support backpressure by providing a configurable
6161
buffer and returning `Async[Throwing]Stream.Continuation.YieldResult` which
6262
contains the current buffer depth from the `yield()` method. However, only
6363
providing the current buffer depth on `yield()` is not enough to bridge a
6464
backpressured system into an asynchronous sequence since this can only be used
65-
as a "stop" signal but we are missing a signal to indicate resuming the
65+
as a "stop" signal, but we are missing a signal to indicate resuming the
6666
production. The only viable backpressure strategy that can be implemented with
6767
the current API is a timed backoff where we stop producing for some period of
6868
time and then speculatively produce again. This is a very inefficient pattern
@@ -90,8 +90,8 @@ when more than one iterator has to suspend. The original proposal states:
9090
creating multiple iterators and iterating over them separately, may produce an
9191
unexpected series of values.
9292

93-
While that statement leaves room for any behavior we learned that a clear distinction
94-
of behavior for root asynchronous sequences is beneficial; especially, when it comes to
93+
While that statement leaves room for any behavior, we learned that a clear distinction
94+
of behavior for root asynchronous sequences is beneficial; especially when it comes to
9595
how transformation algorithms are applied on top.
9696

9797
### Downstream consumer termination
@@ -106,16 +106,16 @@ terminate.
106106

107107
### Upstream producer termination
108108

109-
Upstream producer termination is the inverse of downstream consumer termination
109+
Upstream producer termination is the inverse of downstream consumer termination,
110110
where the producer is notified once the consumption has terminated. Currently,
111111
`Async[Throwing]Stream` does expose the `onTermination` property on the
112112
`Continuation`. The `onTermination` closure is invoked once the consumer has
113113
terminated. The consumer can terminate in four separate cases:
114114

115-
1. The asynchronous sequence was `deinit`ed and no iterator was created
116-
2. The iterator was `deinit`ed and the asynchronous sequence is unicast
117-
3. The consuming task is canceled
118-
4. The asynchronous sequence returned `nil` or threw
115+
1. The asynchronous sequence was `deinit`ed and no iterator was created.
116+
2. The iterator was `deinit`ed and the asynchronous sequence is unicast.
117+
3. The consuming task is canceled.
118+
4. The asynchronous sequence returned `nil` or threw.
119119

120120
`Async[Throwing]Stream` currently invokes `onTermination` in all cases; however,
121121
since `Async[Throwing]Stream` supports multiple consumers (as discussed in the
@@ -130,17 +130,17 @@ system and compares them to the behaviors of `Async[Throwing]Stream` and
130130
`Async[Throwing]Channel`.
131131

132132
This section proposes a new type called `MultiProducerSingleConsumerAsyncChannel`
133-
that implement all of the above-mentioned behaviors. Importantly, this proposed
133+
that implements all of the above-mentioned behaviors. Importantly, this proposed
134134
solution is taking advantage of `~Copyable` types to model the
135135
multi-producer-single-consumer behavior. While the current `AsyncSequence`
136-
protocols are not supporting `~Copyable` types we provide a way to convert the
136+
protocols are not supporting `~Copyable` types, we provide a way to convert the
137137
proposed channel to an asynchronous sequence. This leaves us room to support any
138138
potential future asynchronous streaming protocol that supports `~Copyable`.
139139

140140
### Creating a MultiProducerSingleConsumerAsyncChannel
141141

142142
You can create an `MultiProducerSingleConsumerAsyncChannel` instance using the
143-
`makeChannel(of: backpressureStrategy:)` method. This method returns you the
143+
`makeChannel(of:backpressureStrategy:)` method. This method returns you the
144144
channel and the source. The source can be used to send new values to the
145145
asynchronous channel. The new API specifically provides a
146146
multi-producer/single-consumer pattern.
@@ -157,9 +157,9 @@ let source = consume channelAndSource.source
157157
```
158158

159159
The new proposed APIs offer two different backpressure strategies:
160-
- Watermark: Using a low and high watermark
160+
- Watermark: Using a low and high watermark.
161161
- Unbounded: Unbounded buffering of the channel. **Only** use this if the
162-
production is limited through some other mean.
162+
production is limited through some other means.
163163

164164
The source is used to send values to the channel. It provides different APIs for
165165
synchronous and asynchronous producers. All of the APIs are relaying the
@@ -197,7 +197,7 @@ to send values using the `send(contentsOf:)` which returns a `SendResult`. The
197197
result either indicates that more values should be produced or that a callback
198198
should be enqueued by calling the `enqueueCallback(onProduceMore:)` method.
199199
This callback is invoked once the backpressure strategy
200-
decided that more values should be produced. This API aims to offer the most
200+
decides that more values should be produced. This API aims to offer the most
201201
flexibility with the greatest performance. The callback only has to be allocated
202202
in the case where the producer needs to pause production.
203203

@@ -221,14 +221,14 @@ try await source.send(contentsOf: sequence)
221221
```
222222

223223
With the above APIs, we should be able to effectively bridge any system into a
224-
`MultiProducerSingleConsumerAsyncChannel` regardless if the system is callback-based,
224+
`MultiProducerSingleConsumerAsyncChannel` regardless of whether the system is callback-based,
225225
blocking, or asynchronous.
226226

227227
### Multi producer
228228

229-
To support multiple producers the source offers a `copy` method to produce a new
230-
source. The source is returned `sending` so it is in a disconnected isolation
231-
region than the original source allowing to pass it into a different isolation
229+
To support multiple producers, the source offers a `copy` method to produce a new
230+
source. The source is returned `sending`, so it is in a disconnected isolation
231+
region from the original source, allowing it to be passed into a different isolation
232232
region to concurrently produce elements.
233233

234234
```swift
@@ -254,8 +254,8 @@ print(await channel.next()) // Prints either 1 or 2 depending on which child tas
254254

255255
### Downstream consumer termination
256256

257-
> When reading the next two examples around termination behaviour keep in mind
258-
that the newly proposed APIs are providing a strict a single consumer channel.
257+
> When reading the next two examples of termination behavior, keep in mind
258+
that the newly proposed APIs are providing a strict single consumer channel.
259259

260260
Calling `finish()` terminates the downstream consumer. Below is an example of
261261
this:
@@ -296,7 +296,7 @@ print(try await channel.next()) // Throws SomeError
296296
```
297297

298298
The other way to terminate the consumer is by deiniting the source. This has the
299-
same effect as calling `finish()`. Since the source is a `~Copyable` type this
299+
same effect as calling `finish()`. Since the source is a `~Copyable` type, this
300300
will happen automatically when the source is last used or explicitly consumed.
301301

302302
```swift
@@ -753,12 +753,12 @@ can handle multiple consumers and resumes them in FIFO order.
753753

754754
### swift-nio: NIOAsyncSequenceProducer
755755

756-
The NIO team have created their own root asynchronous sequence with the goal to
757-
provide a high performance sequence that can be used to bridge a NIO `Channel`
756+
The NIO team has created their own root asynchronous sequence with the goal to
757+
provide a high-performance sequence that can be used to bridge a NIO `Channel`
758758
inbound stream into Concurrency. The `NIOAsyncSequenceProducer` is a highly
759-
generic and fully inlinable type and quite unwiedly to use. This proposal is
759+
generic and fully inlinable type and quite unwieldy to use. This proposal is
760760
heavily inspired by the learnings from this type but tries to create a more
761-
flexible and easier to use API that fits into the standard library.
761+
flexible and easier-to-use API that fits into the standard library.
762762

763763
## Future directions
764764

@@ -767,15 +767,15 @@ flexible and easier to use API that fits into the standard library.
767767
The high/low watermark strategy is common in networking code; however, there are
768768
other strategies such as an adaptive strategy that we could offer in the future.
769769
An adaptive strategy regulates the backpressure based on the rate of
770-
consumption and production. With the proposed new APIs we can easily add further
770+
consumption and production. With the proposed new APIs, we can easily add further
771771
strategies.
772772

773773
### Support `~Copyable` elements
774774

775775
In the future, we can extend the channel to support `~Copyable` elements. We
776-
only need an underlying buffer primitive that can hold `~Copyable` types and the
776+
only need an underlying buffer primitive that can hold `~Copyable` types, and the
777777
continuations need to support `~Copyable` elements as well. By making the
778-
channel not directly conform to `AsyncSequence` we can support this down the
778+
channel not directly conform to `AsyncSequence`, we can support this down the
779779
road.
780780

781781
## Alternatives considered
@@ -793,28 +793,28 @@ the current pattern of setting the `onTermination` closure on the source.
793793
During the pitch phase, it was raised that we should provide a
794794
`onConsumerCancellation` callback which gets invoked once the asynchronous
795795
channel notices that the consuming task got cancelled. This callback could be
796-
used to customize how cancellation is handled by the channel e.g. one could
797-
imagine writing a few more elements to the channel before finishing it. Right now
796+
used to customize how cancellation is handled by the channel, e.g. one could
797+
imagine writing a few more elements to the channel before finishing it. Right now,
798798
the channel immediately returns `nil` or throws a `CancellationError` when it
799-
notices cancellation. This proposal decided to not provide this customization
800-
because it opens up the possiblity that asynchronous channels are not terminating
799+
notices cancellation. This proposal decided not to provide this customization
800+
because it opens up the possibility that asynchronous channels are not terminating
801801
when implemented incorrectly. Additionally, asynchronous sequences are not the
802802
only place where task cancellation leads to an immediate error being thrown i.e.
803803
`Task.sleep()` does the same. Hence, the value of the asynchronous not
804804
terminating immediately brings little value when the next call in the iterating
805805
task might throw. However, the implementation is flexible enough to add this in
806-
the future and we can just default it to the current behaviour.
806+
the future, and we can just default it to the current behaviour.
807807

808808
### Create a custom type for the `Result` of the `onProduceMore` callback
809809

810810
The `onProducerMore` callback takes a `Result<Void, Error>` which is used to
811811
indicate if the producer should produce more or if the asynchronous channel
812-
finished. We could introduce a new type for this but the proposal decided
812+
finished. We could introduce a new type for this, but the proposal decided
813813
against it since it effectively is a result type.
814814

815815
### Use an initializer instead of factory methods
816816

817-
Instead of providing a `makeChannel` factory method we could use an initializer
817+
Instead of providing a `makeChannel` factory method, we could use an initializer
818818
approach that takes a closure which gets the `Source` passed into. A similar API
819819
has been offered with the `Continuation` based approach and
820820
[SE-0388](https://github.com/apple/swift-evolution/blob/main/proposals/0388-async-stream-factory.md)
@@ -823,7 +823,7 @@ the initializer based APIs.
823823

824824
### Provide the type on older compilers
825825

826-
To achieve maximum performance the implementation is using `~Copyable` extensively.
826+
To achieve maximum performance, the implementation is using `~Copyable` extensively.
827827
On Swift versions before 6.1, there is a https://github.com/swiftlang/swift/issues/78048 when using; hence, this type
828828
is only usable with Swift 6.1 and later compilers.
829829

0 commit comments

Comments
 (0)