1717introduced new ` Async[Throwing]Stream `  types which act as root asynchronous
1818sequences. These two types allow bridging from synchronous callbacks such as
1919delegates 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
4040In 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
4242regulating 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,
4444backpressure 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
4949overwhelm 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
5252backpressure 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
5454component of such a system is aware of the backpressure of its consumer and its
5555producer.
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
5858this.
5959
6060` Async[Throwing]Stream `  aims to support backpressure by providing a configurable
6161buffer and returning ` Async[Throwing]Stream.Continuation.YieldResult `  which
6262contains the current buffer depth from the ` yield() `  method. However, only
6363providing the current buffer depth on ` yield() `  is not enough to bridge a
6464backpressured 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
6666production. The only viable backpressure strategy that can be implemented with
6767the current API is a timed backoff where we stop producing for some period of
6868time 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
9191unexpected 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
9595how 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, 
110110where 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
113113terminated. 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,
121121since ` 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
132132This 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
134134solution is taking advantage of ` ~Copyable `  types to model the
135135multi-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
137137proposed channel to an asynchronous sequence. This leaves us room to support any
138138potential future asynchronous streaming protocol that supports ` ~Copyable ` .
139139
140140### Creating a MultiProducerSingleConsumerAsyncChannel  
141141
142142You 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
144144channel and the source. The source can be used to send new values to the
145145asynchronous channel. The new API specifically provides a
146146multi-producer/single-consumer pattern.
@@ -157,9 +157,9 @@ let source = consume channelAndSource.source
157157``` 
158158
159159The 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
164164The source is used to send values to the channel. It provides different APIs for
165165synchronous 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
197197result either indicates that more values should be produced or that a callback
198198should be enqueued by calling the ` enqueueCallback(onProduceMore:) `  method.
199199This 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
201201flexibility with the greatest performance. The callback only has to be allocated
202202in the case where the producer needs to pause production.
203203
@@ -221,14 +221,14 @@ try await source.send(contentsOf: sequence)
221221``` 
222222
223223With 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,
225225blocking, 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
232232region 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- a  single consumer channel.
257+ >  When reading the next two examples of  termination behavior,  keep in mind
258+ 
259259
260260Calling ` finish() `  terminates the downstream consumer. Below is an example of
261261this:
@@ -296,7 +296,7 @@ print(try await channel.next()) // Throws SomeError
296296``` 
297297
298298The 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
300300will 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 ` 
758758inbound 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
760760heavily 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.
767767The high/low watermark strategy is common in networking code; however, there are
768768other strategies such as an adaptive strategy that we could offer in the future.
769769An 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
771771strategies.
772772
773773### Support ` ~Copyable `  elements  
774774
775775In 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
777777continuations 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
779779road.
780780
781781## Alternatives considered  
@@ -793,28 +793,28 @@ the current pattern of setting the `onTermination` closure on the source.
793793During the pitch phase, it was raised that we should provide a
794794` onConsumerCancellation `  callback which gets invoked once the asynchronous
795795channel 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, 
798798the 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
801801when implemented incorrectly. Additionally, asynchronous sequences are not the
802802only 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
804804terminating immediately brings little value when the next call in the iterating
805805task 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
810810The ` onProducerMore `  callback takes a ` Result<Void, Error> `  which is used to
811811indicate 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
813813against 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
818818approach that takes a closure which gets the ` Source `  passed into. A similar API
819819has 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.
827827On Swift versions before 6.1, there is a https://github.com/swiftlang/swift/issues/78048  when using; hence, this type
828828is only usable with Swift 6.1 and later compilers. 
829829
0 commit comments