packages feed

ki-0.2.0: README.md

# ki

[![GitHub CI](https://github.com/mitchellwrosen/ki/workflows/CI/badge.svg)](https://github.com/mitchellwrosen/ki/actions)
[![Hackage](https://img.shields.io/hackage/v/ki.svg?label=ki&logo=haskell)](https://hackage.haskell.org/package/ki-0/candidate)
[![Stackage LTS](https://stackage.org/package/ki/badge/lts)](https://www.stackage.org/lts/package/ki)
[![Stackage Nightly](https://stackage.org/package/ki/badge/nightly)](https://www.stackage.org/nightly/package/ki)
[![Dependencies](https://img.shields.io/hackage-deps/v/ki)](https://packdeps.haskellers.com/reverse/ki)


`ki` is a lightweight structured-concurrency library inspired by many other projects:

* [`libdill`](http://libdill.org/)
* [`trio`](https://github.com/python-trio/trio)
* [Kotlin coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [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
```

### Testing

(Some of) the implementation is tested for deadlocks, race conditions, and other concurrency anomalies by
[`dejafu`](http://hackage.haskell.org/package/dejafu), a fantastic unit-testing library for concurrent programs.

Nonetheless this library should not considered production-ready!

## Recommended reading

In chronological order of publication,

  * https://vorpus.org/blog/timeouts-and-cancellation-for-humans/
  * https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
  * http://250bpm.com/blog:124
  * http://250bpm.com/blog:137
  * http://250bpm.com/blog:139
  * http://250bpm.com/blog:146
  * http://libdill.org/structured-concurrency.html