more documentation work

This commit is contained in:
Mitchell Rosen 2021-01-31 15:33:41 -05:00
parent eb5df7f378
commit 3d29f59aff
8 changed files with 310 additions and 292 deletions

124
README.md
View File

@ -15,126 +15,6 @@
* [Go Concurrency Patterns: Context](https://blog.golang.org/context)
* [.NET 4 Cancellation Framework](https://devblogs.microsoft.com/pfxteam/net-4-cancellation-framework/)
## Overview
### Structured concurrency
Structured concurrency aims to make concurrent programs easier to understand by delimiting the lifetime of all
concurrently threads to a syntactic block, akin to structured programming.
This library defines five primary functions; please read the Haddocks for more comprehensive usage information.
```haskell
-- Perform an IO action within a new scope
scoped :: (Scope -> IO a) -> IO a
-- Create a background thread (propagates exceptions to its parent)
fork :: Scope -> IO a -> IO (Thread a)
-- Create a background thread (does not propagate exceptions to its parent)
async :: Scope -> IO a -> IO (Either ThreadFailed a)
-- Wait for a thread to finish
await :: Thread a -> IO a
-- Wait for all threads created within a scope to finish
wait :: Scope -> IO ()
```
A `Scope` is an explicit data structure from which threads can be created, with the property that by the time the
`Scope` itself "goes out of scope", all threads created within it will have finished.
When viewing a concurrent program as a "call tree" (analogous to a call stack), this approach, in contrast to to
directly creating green threads in the style of Haskell's `forkIO` or Golang's `go`, respects the basic function
abstraction, in that each function has a single ingress and a single egress.
Please read [Notes on structured concurrency](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/)
for a more detailed overview on structured concurrency.
### Error propagation
When a parent thread throws or is thrown an exception, it first throws exceptions to all of its children and waits for
them to finish. This makes threads hierarchical: a thread cannot outlive the thread that created it.
When a child thread throws or is thrown an exception, depending on how it was created (see `fork` and `async` above), it
_may_ propagate the exception to its parent. This is intended to cover both of the following cases:
* It is is _unexpected_ for a thread to fail; if it does, the program should crash loudly.
* It is _conceivable_ for a thread to fail; if it does, this is not an exceptional circumstance, so should not require
installing an exception handler.
### Soft-cancellation
Sometimes it is desirable to inform threads that they should endeavor to complete their work and then gracefully
terminate. This is a "cooperative" or "soft" cancellation, in contrast to throwing a thread an exception so that it
terminates immediately.
In `ki`, soft-cancellation is exposed as an alternative superset of the core API, because it involves additional
plumbing of an opaque `Context` type.
```haskell
withGlobalContext :: (Context => IO a) -> IO a
scoped :: Context => (Context => Scope -> IO a) -> IO a
fork :: Scope -> (Context => IO a) -> IO (Thread a)
```
Creating a new scope _requires_ a context, whereas the callbacks provided to `scoped` and `fork` are _provided_
a context. (Above, the context is passed around as an implicit parameter, but could instead be passed around in a
reader monad or similar).
The core API is extended with two functions to soft-cancel a scope, and to observe whether one's own scope has been
canceled.
```haskell
cancel :: Scope -> IO ()
cancelled :: Context => IO (Maybe CancelToken)
```
Canceling a scope is observable by all threads created within it, all threads created within _those_ threads, and so on.
#### A small soft-cancellation example
A worker thread may be written to perform a task in a loop, and cooperatively check for cancellation before doing work.
```haskell
worker :: Ki.Context => IO ()
worker =
forever do
checkCancellation
doWork
where
checkCancellation :: IO ()
checkCancellation = do
maybeCancelToken <- Ki.cancelled
case maybeCancelToken of
Nothing -> pure ()
Just cancelToken -> do
putStrLn "I'm cancelled! Time to clean up."
doCleanup
throwIO cancelToken
```
The parent of such worker threads may (via some signaling mechanism) determine that it should cancel them, do so, and
then defensively fall back to _hard_-cancelling in case some worker is not respecting the soft-cancel signal, for
whatever reason.
```haskell
Ki.scoped \scope -> do
worker
-- Some time later, we decide to soft-cancel
Ki.cancel scope
-- Give the workers up to 10 seconds to finish
Ki.waitFor scope (10 * Ki.seconds)
-- Fall through the bottom of `scoped`, which throws hard-cancels all
-- remaining threads by throwing each one an asynchronous exceptions
```
## Recommended reading
In chronological order of publication,
@ -146,3 +26,7 @@ In chronological order of publication,
* http://250bpm.com/blog:139
* http://250bpm.com/blog:146
* http://libdill.org/structured-concurrency.html
## Documentation
See [Ki.Documentation](https://hackage.haskell.org/package/ki/docs/Ki-Documentation.html)

View File

@ -16,15 +16,7 @@ version: 0.2.0.1
description:
A lightweight structured-concurrency library.
.
This package provides two variants:
.
* "Ki" exposes a simplified version of the full API that does not include support for soft-cancellation. Thus, none of
the functions mention a __context__ type, and you may use any @MonadUnliftIO@-compatible monad, including plain
@IO@. If you do not intend to use soft-cancellation, there is no benefit to using the full API. Start here :)
.
* "Ki.Implicit" and "Ki.Reader" extend "Ki" with a __context__ type that's used to propagate soft-cancellation
signals. Because manually threading the right __context__ throughout a program is error-prone boilerplate,
package offers two ways of handling it implicitly: using implicit parameters, and using a reader monad.
Please read "Ki.Documentation" for an overview of how to use this library.
extra-source-files:
CHANGELOG.md
@ -82,6 +74,7 @@ library
unliftio-core
exposed-modules:
Ki,
Ki.Documentation,
Ki.Implicit,
Ki.Internal,
Ki.Reader

View File

@ -1,3 +1,4 @@
-- | Please read "Ki.Documentation" for an overview of how to use this library.
module Ki
( -- * Scope
Scope,
@ -6,20 +7,14 @@ module Ki
waitSTM,
waitFor,
-- * Creating threads
-- * Thread
Thread,
-- ** Fork
fork,
fork_,
forkWithUnmask,
forkWithUnmask_,
-- ** Async
async,
asyncWithUnmask,
-- ** Await
await,
awaitSTM,
awaitFor,
@ -52,11 +47,9 @@ import Ki.Internal.Thread
)
import Ki.Internal.Timeout (timeoutSTM)
-- | Create a child __thread__ within a __scope__.
-- | Create a child thread within a scope.
--
-- /Throws/:
--
-- * Calls 'error' if the __scope__ is /closed/.
-- Reference manual: "Ki.Documentation#reference_manual_async"
async ::
MonadUnliftIO m =>
-- |
@ -68,11 +61,9 @@ async =
threadAsync
{-# INLINE async #-}
-- | Variant of 'Ki.async' that provides the __thread__ a function that unmasks asynchronous exceptions.
-- | Variant of 'Ki.async' that provides the thread a function that unmasks asynchronous exceptions.
--
-- /Throws/:
--
-- * Calls 'error' if the __scope__ is /closed/.
-- Reference manual: "Ki.Documentation#reference_manual_async"
asyncWithUnmask ::
MonadUnliftIO m =>
-- |

280
src/Ki/Documentation.hs Normal file
View File

@ -0,0 +1,280 @@
module Ki.Documentation
( -- * Introduction
-- $introduction
-- * Tutorial
-- ** Background
-- $tutorial-background
-- ** Structured concurrency
-- $tutorial-structured-concurrency
-- ** Creating threads
-- $tutorial-creating-threads
-- ** Exception propagation
-- $tutorial-exception-propagation
-- ** Soft-cancellation
-- $tutorial-soft-cancellation
-- * Examples
-- $example-has-context-anchor
-- ** Integrating ki with your application monad
-- $example-has-context
-- * Reference manual
-- $reference-manual
)
where
-- $introduction
--
-- This package provides two variants:
-- .
-- * "Ki" exposes a simplified version of the full API that does not include support for soft-cancellation. Thus, none of
-- the functions mention a __context__ type, and you may use any @MonadUnliftIO@-compatible monad, including plain
-- @IO@. If you do not intend to use soft-cancellation, there is no benefit to using the full API. Start here :)
-- .
-- * "Ki.Implicit" and "Ki.Reader" extend "Ki" with a __context__ type that's used to propagate soft-cancellation
-- signals. Because manually threading the right __context__ throughout a program is error-prone boilerplate,
-- package offers two ways of handling it implicitly: using implicit parameters, and using a reader monad.
-- $tutorial-background
--
-- In GHC Haskell, a thread can be created with 'Control.Concurrent.forkIO' at any time, and its lifetime is unrelated
-- to the lifetime of its parent: if the child dies, it has no effect whatsoever on the parent, and vice-versa. The
-- programmer is responsible for managing the two parallel threads of execution independently.
--
-- Commonly, though, there is some higher-level structure to this relationship between parent and child, and we'd like
-- to codify that relationship in a general way that permits reuse. For example, a child thread can be considered to
-- have a return value, which can be retrieved by making a blocking "await" call. This is idea explored in many
-- ecosystems, including in the venerable @async@ package.
--
-- Exception-handling needs some careful thought in this setting, as a parent thread may want to consider the exceptions
-- its children may throw, and a child thread may want to consider the exception its parent may throw.
--
-- Furthermore, in Haskell, there is a distinction between synchronous and asynchronous exceptions. A synchronous
-- exception is thrown directly from an @IO@ action that a thread attempts to perform; it is typically used to indicate
-- that the @IO@ action, or some sub-action attempted within, failed in some way. Such exceptions are sometimes
-- anticipated and caught by the surrounding context, rather than allowed to propagate indefinitely up the call stack.
--
-- An asynchronous exception is instead delivered to a thread anonymously, and is not related to whatever @IO@ action
-- the target thread happened to be performing at that time. It is strongly recommended to always yield to such
-- exceptions, no matter how important of a job the target thread is performing, as ignoring them can can easily bring a
-- program into a chaotic state wherein one thread remains running while another thread believes it to have terminated.
-- $tutorial-structured-concurrency
--
-- "Structured concurrency", in a nutshell, extends some sensible of implementation of parent-child lifetime and
-- exception handling semantics with the additional constraint that a child thread cannot outlive the scope in which it
-- is created.
--
-- The primary benefit of this restriction is that even the concurrent parts of an application respect the basic
-- function abstraction. While "structured programming" argues that a program becomes significantly easier to understand
-- if each function only has a single exit point (that is, the use of @goto@ to jump arbitrarily from one instruction
-- to another is avoided entirely), "structured concurrency" makes the exact same argument, only with
-- concurrently-running threads in mind.
--
-- By the time a function returns, all threads it may have created to accomplish its goal are guaranteed to have
-- terminated, so whether or not it created any threads is irrelevant to the calling code's basic understanding of the
-- control structure of the program. Functions that create "background threads", like functions that may jump elsewhere
-- with @goto@, are avoided entirely.
-- $tutorial-creating-threads
--
-- In @ki@, the scope in which threads are created is a first-class value. It can only be created by the
-- 'Ki.scoped' (cf. _implicit_ 'Ki.Implicit.scoped', _reader_ 'Ki.Reader.scoped') function, which is a "with-style" (or
-- "bracket-style") function that accepts a callback, and the scope is only valid for the duration of the callback.
--
-- A thread can be only created within a scope, because all variants of creating a thread, such as 'Ki.fork' (cf.
-- _implicit_ 'Ki.Implicit.fork', _reader_ 'Ki.Reader.fork') and 'Ki.async' (cf. _implicit_ 'Ki.Implicit.async',
-- _reader_ 'Ki.Reader.async') accept a scope as an explicit argument. Each thread created within a scope is said to be
-- a sibling of the others, distinct from the parent thread which created the scope itself, and related to each other
-- only implicitly by the relationship each has to the parent thread.
--
-- When the callback provided to 'Ki.scoped' returns, the scope becomes "closed", and no new threads can be created
-- within it. All remaining threads that were created within the scope are delivered an asynchronous exception, and
-- 'Ki.scoped' does not return until all of them have terminated. This satisfies the basic requirement of structured
-- concurrency: a thread cannot outlive the scope in which it was created.
--
-- Here's a simple example, annotated below.
--
-- @
-- __1.__
-- (result1, result2) <-
-- Ki.'Ki.scoped' \\scope ->
-- __2.__
-- thread1 <- Ki.'Ki.async' scope worker1
-- thread2 <- Ki.'Ki.async' scope worker2
-- __3.__
-- result1 <- Ki.'Ki.await' thread1
-- result2 <- Ki.'Ki.await' thread2
-- __4.__
-- pure (result1, result2)
-- __5.__
-- @
--
-- 1. First, we open a new scope with 'Ki.scoped'. It's only "open" for the duration of the callback we provide.
-- 2. Next, we create two worker threads within the scope.
-- 3. Next, we wait for both threads to return with either a value or an exception.
-- 4. Finally, we reach the end of the callback. The scope is "closed", and all remaining threads are terminated,
-- 5. Here, all threads that were created within it scope are guaranteed to have terminated.
--
-- An explicit scope is a powerful abstraction: although it is indeed an additional argument to pass around as compared
-- to simpler thread creation APIs such as 'Control.Concurrent.forkIO' and
-- @<https://hackage.haskell.org/package/async/docs/Control-Concurrent-Async.html#v:async async>@, the relationship
-- between threads created within a scope can often simply be read off the page.
--
-- In the example above, we can immediately see that that @worker1@ and @worker2@ are the only threads created within
-- the scope; there are no additional lifetimes to consider when attempting to understand the behavior of this program.
--
-- This would not be the case if the scope was explicitly passed down into @worker1@, for example, which would bestow
-- @worker1@ with the ability to create its own siblings, nor would it be the case if the scope was implicitly passed
-- around.
--
-- Passing a scope value around is still an option, and is necessary for certain advanced use cases, as well as
-- implementing concurrency abstractions such as worker pools, actors, and supervisors. But be careful - wherever the
-- scope goes, so goes the ability to create threads within it!
-- $tutorial-exception-propagation
--
-- In @ki@, exception propagation is bi-directional between parent and child threads. We've already discussed one
-- circumstance in which a parent throws exceptions to its children: when the callback provided to 'Ki.scoped' returns.
-- But this is also the case if the parent terminates abnormally by throwing an exception, or if it is thrown an
-- asynchronous exception from another thread. In short, no matter what happens to a parent thread with an open scope,
-- the scope will be closed, at which point all remaining child threads are terminated.
--
-- @
-- 'Ki.scoped' \\scope ->
-- __1.__
-- __2.__
-- @
--
-- 1. It does not matter how many threads are created within here, whether whether the callback itself throws an
-- exception, or whether the parent thread is thrown an asynchronous exception...
-- 2. ...by the time we get here, all threads created within the scope are guaranteed to have terminated.
--
-- Sometimes, a child thread may be performing an operation that is expected to sometimes fail; for this case, @ki@
-- provides 'Ki.async', which creates a thread that does not propagate any synchronous exceptions to its parent.
-- Rather, these exceptions are made available for the parent thread to 'Ki.await' and handle however it wishes.
--
-- Other times, it is considered very unexpected or erroneous for a child thread to fail; for this case, @ki@ provides
-- 'Ki.fork', which creates a thread that immediately propagates any synchronous exception it throws to its
-- parent. The intention is to facillitate "failing fast and loud" when there is little or nothing sensible for the
-- programmer to do besides propagate the exception up the call tree.
--
-- In either case, if a child thread is deliviered an asynchronous exception, it is immediately propagated to its
-- parent. This is in accordance with exception-handling best practices, which dictate that asynchronous exceptions
-- should always be respected, never ignored, and if caught, should always be re-thrown after performing any desired
-- cleanup actions.
--
-- Each child thread can be thought to increases the "surface area" of the parent thread's identity, because any
-- asynchronous exception delivered to any child will ultimately be propagated to the parent.
-- $tutorial-soft-cancellation
--
-- TODO
-- $example-has-context-anchor
--
-- #example_has_context#
-- $example-has-context
--
-- You may have an application monad that is defined similar to the following.
--
-- @
-- data Env
-- = Env
-- { ...
-- }
--
-- newtype App a
-- = App { runApp :: Env -> IO a }
--
-- instance MonadUnliftIO App where ...
-- @
--
-- To use this module, first add one field to your @Env@ type that holds a __context__.
--
-- @
-- data Env
-- = Env
-- { ...
-- , envContext :: 'Ki.Reader.Context'
-- , ...
-- }
-- @
--
-- Then, write a 'Ki.Reader.HasContext' instance, which is a bit of boilerplate that encapsulates how to get and set
-- this field.
--
-- @
-- instance 'Ki.Reader.HasContext' App where
-- 'Ki.Reader.askContext' =
-- App \\env -> pure (envContext env)
--
-- 'Ki.Reader.withContext' context action =
-- App \\env -> runApp action env{ envContext = context }
-- @
--
-- And finally, when running your monad down to @IO@ in @main@ by providing an initial environment, use
-- 'Ki.Reader.globalContext'.
--
-- @
-- main :: IO ()
-- main =
-- runApp initialEnv action
--
-- initialEnv :: Env
-- initialEnv =
-- Env
-- { ...
-- , envContext = 'Ki.Reader.globalContext'
-- , ...
-- }
--
-- action :: App ()
-- action =
-- ...
-- @
-- $reference-manual
--
-- This reference manual contains implementation details for all of the major types and functions provided by the "full"
-- variant of this library (i.e. "Ki.Implicit" or "Ki.Reader"), which includes support for soft-cancellation.
--
-- The implementation of the stripped-down "Ki" variant is the same, but all references to cancellation and contexts can
-- simply be ignored.
--
-- ==== 'Ki.Reader.asyncWithUnmask' #reference_manual_async#
--
-- 'Ki.Reader.asyncWithUnmask' creates a thread within a scope.
--
-- If the scope is closed, this function calls 'error'. Otherwise, it creates a thread with the same masking state as
-- the thread that created it, and provides the thread with an @unmask@ function, which unmasks asynchronous exceptions.
--
-- The new thread is tracked in a data structure inside the scope, keyed by a monotonically increasing integer, so that
-- when the scope is closed, all remaining threads can be thrown an asynchronous exception in the order they were
-- created.
--
-- When the thread terminates, if it terminated with an exception, it first determines whether or not it should
-- propagate the exception to its parent.
--
-- - If the exception is a 'Ki.Internal.ScopeClosing', it is not propagated, as it is assumed to have come from the
-- parent thread directly.
-- - If the exception is a 'Ki.Internal.CancelToken' that was observable by the thread calling 'Ki.Reader.cancelled',
-- it is not propagated, as the parent thread either initiated the cancellation directly, or else some ancestor of
-- the parent thread initiated the cancellation (in which case the parent thread could observe the same cancel token
-- with 'Ki.Reader.cancelled'); either way, the parent thread could "know about" the cancellation, so it would be
-- incorrect to propagate this exception to the parent thread and induce an immediate termination of the thread's
-- siblings.
-- - If the exception is asynchronous (e.g. a subclass of 'Control.Exception.SomeAsyncException'), it is propagated.
-- - Otherwise, the exception is not propagated.
--
-- The thread's result is then made available via 'Ki.Reader.await', and finally the thread removes itself from the data
-- structure in its scope that tracks its existence, because the purpose of the data structure is to track all threads
-- still running within a scope, so they can all be terminated when the scope closes.

View File

@ -1,6 +1,8 @@
{-# LANGUAGE PatternSynonyms #-}
-- | This module exposes an API that uses an implicit parameter to pass around the __context__ implicitly. If you do not
-- | Please read "Ki.Documentation" for an overview of how to use this library.
--
-- This module exposes an API that uses an implicit parameter to pass around the __context__ implicitly. If you do not
-- intend to use soft-cancellation, you may want to use the simpler API exposed by "Ki".
module Ki.Implicit
( -- * Context
@ -15,20 +17,14 @@ module Ki.Implicit
waitSTM,
waitFor,
-- * Creating threads
-- * Thread
Thread,
-- ** Fork
fork,
fork_,
forkWithUnmask,
forkWithUnmask_,
-- ** Async
async,
asyncWithUnmask,
-- ** Await
Ki.await,
awaitSTM,
awaitFor,
@ -81,11 +77,9 @@ import Ki.Internal.Timeout (timeoutSTM)
type Context =
?context :: Ki.Internal.Context.Context
-- | Create a __thread__ within a __scope__.
-- | Create a thread within a scope.
--
-- /Throws/:
--
-- * Calls 'error' if the __scope__ is /closed/.
-- Reference manual: "Ki.Documentation#reference_manual_async"
async ::
MonadUnliftIO m =>
-- |
@ -98,11 +92,9 @@ async scope action =
threadAsync scope (with scope action)
{-# INLINE async #-}
-- | Variant of 'Ki.Implicit.async' that provides the __thread__ a function that unmasks asynchronous exceptions.
-- | Variant of 'Ki.Implicit.async' that provides the thread a function that unmasks asynchronous exceptions.
--
-- /Throws/:
--
-- * Calls 'error' if the __scope__ is /closed/.
-- Reference manual: "Ki.Documentation#reference_manual_async"
asyncWithUnmask ::
MonadUnliftIO m =>
-- |

View File

@ -1,3 +1,4 @@
-- | Internals. This module does not follow the <https://pvp.haskell.org/ Haskell Package Versioning Policy>.
module Ki.Internal
( module Ki.Internal.CancelToken,
module Ki.Internal.Context,

View File

@ -1,8 +1,9 @@
-- | This module exposes an API that uses a reader monad to pass around the __context__ implicitly. If you do not intend
-- | Please read "Ki.Documentation" for an overview of how to use this library.
--
-- This module exposes an API that uses a reader monad to pass around the __context__ implicitly. If you do not intend
-- to use soft-cancellation, you may want to use the simpler API exposed by "Ki".
--
-- For an example of how to integrate this library with your reader monad, click @Example@ on the left or scroll down to
-- the bottom of this module.
-- For an example of how to integrate this library with your reader monad, see "Ki.Documentation#example_has_context".
module Ki.Reader
( -- * Context
Context,
@ -17,20 +18,14 @@ module Ki.Reader
waitSTM,
waitFor,
-- * Creating threads
-- * Thread
Thread,
-- ** Fork
fork,
fork_,
forkWithUnmask,
forkWithUnmask_,
-- ** Async
async,
asyncWithUnmask,
-- ** Await
Ki.await,
awaitSTM,
awaitFor,
@ -49,9 +44,6 @@ module Ki.Reader
seconds,
timeoutSTM,
sleep,
-- * Example
-- $example
)
where
@ -75,66 +67,6 @@ import Ki.Internal.Thread
)
import Ki.Internal.Timeout (timeoutSTM)
-- $example
--
-- You may have an application monad that is defined similar to the following.
--
-- @
-- data Env
-- = Env
-- { ...
-- }
--
-- newtype App a
-- = App { runApp :: Env -> IO a }
--
-- instance MonadUnliftIO App where ...
-- @
--
-- To use this module, first add one field to your @Env@ type that holds a __context__.
--
-- @
-- data Env
-- = Env
-- { ...
-- , envContext :: 'Ki.Reader.Context'
-- , ...
-- }
-- @
--
-- Then, write a 'Ki.Reader.HasContext' instance, which is a bit of boilerplate that encapsulates how to get and set
-- this field.
--
-- @
-- instance 'Ki.Reader.HasContext' App where
-- 'Ki.Reader.askContext' =
-- App \\env -> pure (envContext env)
--
-- 'Ki.Reader.withContext' context action =
-- App \\env -> runApp action env{ envContext = context }
-- @
--
-- And finally, when running your monad down to @IO@ in @main@ by providing an initial environment, use
-- 'Ki.Reader.globalContext'.
--
-- @
-- main :: IO ()
-- main =
-- runApp initialEnv action
--
-- initialEnv :: Env
-- initialEnv =
-- Env
-- { ...
-- , envContext = 'Ki.Reader.globalContext'
-- , ...
-- }
--
-- action :: App ()
-- action =
-- ...
-- @
-- | The class of reader monads that contain a __context__ in their environment.
class MonadUnliftIO m => HasContext m where
-- | Project the __context__ from the environment.
@ -143,11 +75,9 @@ class MonadUnliftIO m => HasContext m where
-- | Run an @m@ action, replacing its __context__ with the one provided.
withContext :: Context -> m a -> m a
-- | Create a __thread__ within a __scope__.
-- | Create a thread within a scope.
--
-- /Throws/:
--
-- * Calls 'error' if the __scope__ is /closed/.
-- Reference manual: "Ki.Documentation#reference_manual_async"
async ::
HasContext m =>
-- |
@ -158,11 +88,9 @@ async ::
async scope action =
threadAsync scope (with scope action)
-- | Variant of 'Ki.Reader.async' that provides the __thread__ a function that unmasks asynchronous exceptions.
-- | Variant of 'Ki.Reader.async' that provides the thread a function that unmasks asynchronous exceptions.
--
-- /Throws/:
--
-- * Calls 'error' if the __scope__ is /closed/.
-- Reference manual: "Ki.Documentation#reference_manual_async"
asyncWithUnmask ::
HasContext m =>
-- |

View File

@ -1,51 +0,0 @@
module Ki.Tutorial
( -- * Introduction
-- $introduction
)
where
-- $introduction
--
-- In GHC Haskell, a background thread can be spawned with "Control.Concurrent.forkIO" at any time, and its lifetime is
-- wholly disconnected from the lifetime of its parent: if the child dies, it has no effect whatsoever on the parent,
-- and vice-versa. It is hereafter the programmer's responsibility to manage the two parallel threads of execution.
--
-- Commonly, though, there is some higher-level structure to this relationship between parent and child, and we'd like
-- to codify that relationship in a general way that permits reuse. For example, the notion that a child thread has a
-- "return value" is an idea explored in many ecosystems and packages, including the venerable @async@ Haskell package,
-- wherein a child thread can be "awaited", which is an operation that blocks until the child thread terminates with a
-- value or an exception.
--
-- Here it is supposed, but not required, that whatever parent thread is creating child threads is also awaiting their
-- termination.
--
-- And when a child thread is though to have a "return value", we of course have to reason through how our program
-- should behave if that return value never materializes, because the child thread threw (or, in Haskell, was thrown) an
-- exception.
--
-- Sometimes, it might be erroneous for a parent thread to continue running if one of its children has thrown an
-- exception, so we might endeavor to eagerly throw an asynchronous exception from child to parent in this case, so the
-- exception is noticed in a timely manner.
--
-- Other times, it would not make sense
--
--
--
--
--
--
--
-- it would not it would not make sense for a child thread to continue running
-- if its parent is no longer alive, so we might endeavor to kill all of a thread's children just before it terminates.
--
-- These ideas and more have been explored in various Haskell libraries that are built on top of @forkIO@, most notably
-- the venerable @async@ library, which has an entire book authored by Simon Marlowe that motivates its design.
--
-- Structured concurrency takes the idea of a sensible parent-child relationship one step further by requiring _all_
-- concurrently running threads to be created within some "scope", such that when the scope exits, or closes, or ceases
-- to exist for one reason or another, all threads that were created within it, if any, are terminated. In short, a
-- function cannot create
--
-- Structured concurrency, in a nutshell, is a restricted style of programming wherein a thread is prevented from
-- outliving some sort of scope in which it was created.