Skip to content

Commit bfb4f10

Browse files
committed
Add more documentation to Async
1 parent f66a696 commit bfb4f10

File tree

1 file changed

+143
-34
lines changed

1 file changed

+143
-34
lines changed

shared/src/main/scala/async/Async.scala

Lines changed: 143 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,40 @@ import gears.async.Listener.withLock
77
import gears.async.Listener.NumberedLock
88
import scala.util.boundary
99

10-
/** A context that allows to suspend waiting for asynchronous data sources
10+
/** The async context: provides the capability to asynchronously [[Async.await await]] for [[Async.Source Source]]s, and
11+
* defines a scope for structured concurrency through a [[CompletionGroup]].
12+
*
13+
* As both a context and a capability, the idiomatic way of using [[Async]] is to be implicitly passed around
14+
* functions, as an `using` parameter:
15+
* {{{
16+
* def function()(using Async): T = ???
17+
* }}}
18+
*
19+
* It is not recommended to store [[Async]] in a class field, since it complicates scoping rules.
20+
*
21+
* @param support
22+
* An implementation of the underlying asynchronous operations (suspend and resume). See [[AsyncSupport]].
23+
* @param scheduler
24+
* An implementation of a scheduler, for scheduling computation as they are spawned or resumed. See [[Scheduler]].
25+
*
26+
* @see
27+
* [[Async$.blocking Async.blocking]] for a way to construct an [[Async]] instance.
28+
* @see
29+
* [[Async$.group Async.group]] and [[Future$.apply Future.apply]] for [[Async]]-subscoping operations.
1130
*/
1231
trait Async(using val support: AsyncSupport, val scheduler: support.Scheduler):
13-
/** Wait for completion of async source `src` and return the result */
32+
/** Waits for completion of source `src` and returns the result. Suspends the computation.
33+
*
34+
* @see
35+
* [[Async.Source.awaitResult]] and [[Async$.await]] for extension methods calling [[Async!.await]] from the source
36+
* itself.
37+
*/
1438
def await[T](src: Async.Source[T]): T
1539

16-
/** The cancellation group for this Async */
40+
/** Returns the cancellation group for this [[Async]] context. */
1741
def group: CompletionGroup
1842

19-
/** An Async of the same kind as this one, with a new cancellation group */
43+
/** Returns an [[Async]] context of the same kind as this one, with a new cancellation group. */
2044
def withGroup(group: CompletionGroup): Async
2145

2246
object Async:
@@ -53,7 +77,7 @@ object Async:
5377
def blocking[T](body: Async.Spawn ?=> T)(using support: AsyncSupport, scheduler: support.Scheduler): T =
5478
group(body)(using Blocking(CompletionGroup.Unlinked))
5579

56-
/** The currently executing Async context */
80+
/** Returns the currently executing Async context. Equivalent to `summon[Async]`. */
5781
inline def current(using async: Async): Async = async
5882

5983
/** [[Async.Spawn]] is a special subtype of [[Async]], also capable of spawning runnable [[Future]]s.
@@ -63,8 +87,8 @@ object Async:
6387
*/
6488
opaque type Spawn <: Async = Async
6589

66-
/** Runs [[body]] inside a spawnable context where it is allowed to spawning concurrently runnable [[Future]]s. When
67-
* the body returns, all spawned futures are cancelled and waited for.
90+
/** Runs `body` inside a spawnable context where it is allowed to spawn concurrently runnable [[Future]]s. When the
91+
* body returns, all spawned futures are cancelled and waited for.
6892
*/
6993
def group[T](body: Async.Spawn ?=> T)(using Async): T =
7094
withNewCompletionGroup(CompletionGroup().link())(body)
@@ -86,51 +110,78 @@ object Async:
86110
group.waitCompletion()(using completionAsync)
87111

88112
/** An asynchronous data source. Sources can be persistent or ephemeral. A persistent source will always pass same
89-
* data to calls of `poll and `onComplete`. An ephemeral source can pass new data in every call. An example of a
90-
* persistent source is `Future`. An example of an ephemeral source is `Channel`.
113+
* data to calls of [[Source!.poll]] and [[Source!.onComplete]]. An ephemeral source can pass new data in every call.
114+
*
115+
* @see
116+
* An example of a persistent source is [[gears.async.Future]].
117+
* @see
118+
* An example of an ephemeral source is [[gears.async.Channel]].
91119
*/
92120
trait Source[+T]:
93-
94-
/** Check whether data is available at present and pass it to k if so. If no element is available, does not lock k
95-
* and returns false immediately. If there is (or may be) data available, the listener is locked and if it fails,
96-
* true is returned to signal this source's general availability. If locking k succeeds, only return true iff k's
97-
* complete is called. Calls to `poll` are always synchronous.
121+
/** Checks whether data is available at present and pass it to `k` if so. Calls to `poll` are always synchronous and
122+
* non-blocking.
123+
*
124+
* If no element is available, returns `false` immediately. If there is (or may be) data available, `k` is locked
125+
* and if it fails, `true` is returned to signal this source's general availability. If locking `k` succeeds, only
126+
* return `true` iff `k` is completed (it is always unlocked nevertheless).
127+
*
128+
* @return
129+
* Whether poll was able to pass data to `k`. Note that this is regardless of `k` being available to receive the
130+
* data. In most cases, one should pass `k` into [[Source!.onComplete]] if `poll` returns `false`.
98131
*/
99132
def poll(k: Listener[T]): Boolean
100133

101-
/** Once data is available, pass it to function `k`. `k` returns true iff the data was consumed in an async block.
102-
* Calls to `onComplete` are usually asynchronous, meaning that the passed continuation `k` is a suspension.
134+
/** Once data is available, pass it to the listener `k`. `onComplete` is always non-blocking.
135+
*
136+
* Note that `k`'s methods will be executed on the same thread as the [[Source]], usually in sequence. It is hence
137+
* important that the listener itself does not perform expensive operations.
103138
*/
104139
def onComplete(k: Listener[T]): Unit
105140

106-
/** Signal that listener `k` is dead (i.e. will always return `false` from now on). This permits original, (i.e.
107-
* non-derived) sources like futures or channels to drop the listener from their waiting sets.
141+
/** Signal that listener `k` is dead (i.e. will always fail to acquire locks from now on), and should be removed
142+
* from `onComplete` queues.
143+
*
144+
* This permits original, (i.e. non-derived) sources like futures or channels to drop the listener from their
145+
* waiting sets.
108146
*/
109147
def dropListener(k: Listener[T]): Unit
110148

111-
/** Utility method for direct polling. */
149+
/** Similar to [[Async.Source!.poll(k:Listener[T])* poll]], but instead of passing in a listener, directly return
150+
* the value `T` if it is available.
151+
*/
112152
def poll(): Option[T] =
113153
var resultOpt: Option[T] = None
114154
poll(Listener.acceptingListener { (x, _) => resultOpt = Some(x) })
115155
resultOpt
116156

117-
/** Utility method for direct waiting with `Async`. */
157+
/** Waits for an item to arrive from the source. Suspends until an item returns.
158+
*
159+
* This is an utility method for direct waiting with `Async`, instead of going through listeners.
160+
*/
118161
final def awaitResult(using ac: Async) = ac.await(this)
119162
end Source
120163

121164
extension [T](src: Source[scala.util.Try[T]])
122-
/** Waits for an item to arrive from the source, then automatically unwraps it. */
165+
/** Waits for an item to arrive from the source, then automatically unwraps it. Suspends until an item returns.
166+
* @see
167+
* [[Source!.awaitResult awaitResult]] for non-unwrapping await.
168+
*/
123169
inline def await(using Async) = src.awaitResult.get
124170
extension [E, T](src: Source[Either[E, T]])
125-
/** Waits for an item to arrive from the source, then automatically unwraps it. */
171+
/** Waits for an item to arrive from the source, then automatically unwraps it. Suspends until an item returns.
172+
* @see
173+
* [[Source!.awaitResult awaitResult]] for non-unwrapping await.
174+
*/
126175
inline def await(using Async) = src.awaitResult.right.get
127176

128-
/** An original source has a standard definition of `onComplete` in terms of `poll` and `addListener`. Implementations
129-
* should be the resource owner to handle listener queue and completion using an object monitor on the instance.
177+
/** An original source has a standard definition of [[Source.onComplete onComplete]] in terms of [[Source.poll poll]]
178+
* and [[OriginalSource.addListener addListener]].
179+
*
180+
* Implementations should be the resource owner to handle listener queue and completion using an object monitor on
181+
* the instance.
130182
*/
131183
abstract class OriginalSource[+T] extends Source[T]:
132-
133-
/** Add `k` to the listener set of this source */
184+
/** Add `k` to the listener set of this source. */
134185
protected def addListener(k: Listener[T]): Unit
135186

136187
def onComplete(k: Listener[T]): Unit = synchronized:
@@ -139,7 +190,12 @@ object Async:
139190
end OriginalSource
140191

141192
object Source:
142-
/** Create a [[Source]] containing the given values, resolved once for each. */
193+
/** Create a [[Source]] containing the given values, resolved once for each.
194+
*
195+
* @return
196+
* an ephemeral source of values arriving to listeners in a queue. Once all values are received, attaching a
197+
* listener with [[Source!.onComplete onComplete]] will be a no-op (i.e. the listener will never be called).
198+
*/
143199
def values[T](values: T*) =
144200
import scala.collection.JavaConverters._
145201
val q = java.util.concurrent.ConcurrentLinkedQueue[T]()
@@ -163,8 +219,14 @@ object Async:
163219

164220
extension [T](src: Source[T])
165221
/** Create a new source that requires the original source to run the given transformation function on every value
166-
* received. Note that [[f]] is **always** run on the computation that produces the values from the original
167-
* source, so this is very likely to run **sequentially** and be a performance bottleneck.
222+
* received.
223+
*
224+
* Note that `f` is **always** run on the computation that produces the values from the original source, so this is
225+
* very likely to run **sequentially** and be a performance bottleneck.
226+
*
227+
* @param f
228+
* the transformation function to be run on every value. `f` is run *before* the item is passed to the
229+
* [[Listener]].
168230
*/
169231
def transformValuesWith[U](f: T => U) =
170232
new Source[U]:
@@ -182,7 +244,23 @@ object Async:
182244
def dropListener(k: Listener[U]): Unit =
183245
src.dropListener(transform(k))
184246

247+
/** Creates a source that "races" a list of sources.
248+
*
249+
* Listeners attached to this source is resolved with the first item arriving from one of the sources. If multiple
250+
* sources are available at the same time, one of the items will be returned with no priority. Items that are not
251+
* returned are '''not''' consumed from the upstream sources.
252+
*
253+
* @see
254+
* [[raceWithOrigin]] for a race source that also returns the upstream origin of the item.
255+
* @see
256+
* [[Async$.select Async.select]] for a convenient syntax to race sources and awaiting them with [[Async]].
257+
*/
185258
def race[T](sources: Source[T]*): Source[T] = raceImpl[T, T]((v, _) => v)(sources*)
259+
260+
/** Like [[race]], but the returned value includes a reference to the upstream source that the item came from.
261+
* @see
262+
* [[Async$.select Async.select]] for a convenient syntax to race sources and awaiting them with [[Async]].
263+
*/
186264
def raceWithOrigin[T](sources: Source[T]*): Source[(T, Source[T])] =
187265
raceImpl[(T, Source[T]), T]((v, src) => (v, src))(sources*)
188266

@@ -260,28 +338,59 @@ object Async:
260338

261339
/** Cases for handling async sources in a [[select]]. [[SelectCase]] can be constructed by extension methods `handle`
262340
* of [[Source]].
341+
*
342+
* @see
343+
* [[handle Source.handle]] (and its operator alias [[~~> ~~>]])
344+
* @see
345+
* [[Async$.select Async.select]] where [[SelectCase]] is used.
263346
*/
264347
opaque type SelectCase[T] = (Source[?], Nothing => T)
265348
// ^ unsafe types, but we only construct SelectCase from `handle` which is safe
266349

267350
extension [T](src: Source[T])
268-
/** Attach a handler to [[src]], creating a [[SelectCase]]. */
351+
/** Attach a handler to `src`, creating a [[SelectCase]].
352+
* @see
353+
* [[Async$.select Async.select]] where [[SelectCase]] is used.
354+
*/
269355
inline def handle[U](f: T => U): SelectCase[U] = (src, f)
270356

271-
/** Alias for [[handle]] */
357+
/** Alias for [[handle]]
358+
* @see
359+
* [[Async$.select Async.select]] where [[SelectCase]] is used.
360+
*/
272361
inline def ~~>[U](f: T => U): SelectCase[U] = src.handle(f)
273362

274363
/** Race a list of sources with the corresponding handler functions, once an item has come back. Like [[race]],
275-
* [[select]] guarantees exactly one of the sources are polled. Unlike `map`ping a [[Source]], the handler in
364+
* [[select]] guarantees exactly one of the sources are polled. Unlike [[transformValuesWith]], the handler in
276365
* [[select]] is run in the same async context as the calling context of [[select]].
366+
*
367+
* @see
368+
* [[handle Source.handle]] (and its operator alias [[~~> ~~>]]) for methods to create [[SelectCase]]s.
369+
* @example
370+
* {{{
371+
* // Race a channel read with a timeout
372+
* val ch = SyncChannel[Int]()
373+
* // ...
374+
* val timeout = Future(sleep(1500.millis))
375+
*
376+
* Async.select(
377+
* ch.readSrc.handle: item =>
378+
* Some(item * 2),
379+
* timeout ~~> _ => None
380+
* )
381+
* }}}
277382
*/
278383
def select[T](cases: SelectCase[T]*)(using Async) =
279384
val (input, which) = raceWithOrigin(cases.map(_._1)*).awaitResult
280385
val (_, handler) = cases.find(_._1 == which).get
281386
handler.asInstanceOf[input.type => T](input)
282387

283-
/** If left (respectively, right) source succeeds with `x`, pass `Left(x)`, (respectively, Right(x)) on to the
284-
* continuation.
388+
/** Race two sources, wrapping them respectively in [[Left]] and [[Right]] cases.
389+
* @return
390+
* a new [[Source]] that resolves with [[Left]] if `src1` returns an item, [[Right]] if `src2` returns an item,
391+
* whichever comes first.
392+
* @see
393+
* [[race]] and [[select]] for racing more than two sources.
285394
*/
286395
def either[T1, T2](src1: Source[T1], src2: Source[T2]): Source[Either[T1, T2]] =
287396
race(src1.transformValuesWith(Left(_)), src2.transformValuesWith(Right(_)))

0 commit comments

Comments
 (0)