keepcalm 0.6.0

Simple shared types for multi-threaded programs
Documentation
# Keep Calm (and call Clone)

![Build Status](https://github.com/mmastrac/keepcalm/actions/workflows/rust.yml/badge.svg)
[![docs.rs](https://docs.rs/keepcalm/badge.svg)](https://docs.rs/keepcalm)
[![crates.io](https://img.shields.io/crates/v/keepcalm.svg)](https://crates.io/crates/keepcalm)

Simple shared types for multi-threaded Rust programs: `keepcalm` gives you permission to simplify your synchronization code in concurrent Rust applications.

Name inspired by @luser's [Keep Calm and Call Clone](https://github.com/luser/keep-calm-and-call-clone).

## Overview

This library simplifies a number of shared-object patterns that are used in multi-threaded programs such as web-servers.

Advantages of `keepcalm`:

 * You don't need to decide on your synchronization primitives up-front. Everything is a [`Shared`] or [`SharedMut`], no matter whether it's
   a mutex, read/write lock, read/copy/update primitive, or a read-only shared [`std::sync::Arc`].
 * Everything is [`project!`]able, which means you can adjust the granularity of your locks at any time without having to refactor the whole
   system. If you want finer-grained locks at a later date, the code that uses the shared containers doesn't change!
 * Writeable containers can be turned into read-only containers, while still retaining the ability for other code to update the contents.
 * Read and write guards are `!Send`, but an opt-in `read_send`/`write_send` is available when you genuinely need a `Send` guard and are aware of the potential deadlock consequences.
 * Each synchronization primitive transparently manages the poisoned state (if code `panic!`s while the lock is being held). If you don't want to
   poison on `panic!`, constructors are available to disable this option entirely.
 * `static` Globally-scoped containers for both `Sync` and `!Sync` objects are easily constructed using [`SharedGlobal`], and can provide [`Shared`]
   containers. Mutable global containers can similarly be constructed with [`SharedGlobalMut`].
 * The same primitives work in both synchronous and `async` contents (caveat: the latter being experimental at this time): you can simply `await` an asynchronous
   version of the lock using `read_async` and `write_async`.
 * Minimal performance impact: benchmarks shows approximately the same performance between the raw `parking_lot` primitives/`tokio` async containers and those
   in `keepcalm`.

## Performance

A rough benchmark shows approximately equivalent performance to both `tokio` and `parking_lot` primitives in `async` and `sync` contexts. While
`keepcalm` shows performance slightly faster than `parking_lot` in some cases, this is probably measurement noise.

| Benchmark                    | `keepcalm`       | `tokio`       | `parking_lot`      |
|------------------------------|------------------|---------------|--------------------|
| Mutex (async, uncontended)   | 23ns             | 49ns          | n/a                |
| Mutex (async, contented)     | 1.3ms            | 1.3ms         | n/a                |
| RwLock (async, uncontended)  | 14ns             | 46ns          | n/a                |
| RwLock (async, contended)    | (untested)       | (untested)    | (untested)         |
| RwLock (sync)                | 6.8ns            | n/a           | (untested)         |
| Mutex (sync)                 | 7.3ns            | n/a           | 8.5ns              |

## Container types

The following container types are available:

| Container                      | Equivalent               | Notes |
|--------------------------------|--------------------------|-------|
| [`SharedMut::new`]             | `Arc<RwLock<T>>`         | This is the default shared-mutable type.
| [`SharedMut::new_mutex`]       | `Arc<Mutex<T>>`          | In some cases it may be necessary to serialize both read and writes. For example, with types that are not `Sync`.
| [`SharedMut::new_rcu`]         | `Arc<RwLock<Arc<T>`      | When the write lock of an RCU container is dropped, the values written are committed to the value in the container.
| [`Shared::new`]                | `Arc`                    | This is the default shared-immutable type. Note that this is slightly more verbose: [`Shared`] does not [`std::ops::Deref`] to the underlying type and requires calling [`Shared::read`].
| [`Shared::new_mutex`]          | `Arc<Mutex<T>>`          | For types that are not `Sync`, a `Mutex` is used to serialize read-only access.
| [`SharedMut::shared`]          | n/a                      | This provides a read-only view into a read-write container and has no direct equivalent.

The following global container types are available:

| Container                      | Equivalent               | Notes |
|--------------------------------|--------------------------|-------|
| [`SharedGlobal::new`]          | `static T`               | This is a global `const`-style object, for types that are `Send` + `Sync`.
| [`SharedGlobal::new_lazy`]     | `static Lazy<T>`         | This is a lazily-initialized global `const`-style object, for types that are `Send` + `Sync`.
| [`SharedGlobal::new_mutex`]    | `static Mutex<T>`        | This is a global `const`-style object, for types that are `Send` but not necessarily `Sync`
| [`SharedGlobalMut::new`]       | `static RwLock<T>`       | This is a global mutable object, for types that are `Send` + `Sync`.
| [`SharedGlobalMut::new_lazy`]  | `static Lazy<RwLock<T>>` | This is a lazily-initialized global mutable object, for types that are `Send` + `Sync`.
| [`SharedGlobalMut::new_mutex`] | `static Mutex<T>`        | This is a global mutable object, for types that are `Send` but not necessarily `Sync`.

## Choosing a mutable primitive

All three mutable backings are interchangeable through the [`SharedMut`] interface, so you can start
with the default and change your mind later. They differ in a few properties worth knowing:

| Constructor              | Backing          | Poison on panic | Reads         | Concurrent reads | Writes                       | Participates in deadlock graph |
|--------------------------|------------------|-----------------|---------------|------------------|------------------------------|--------------------------------|
| [`SharedMut::new`]       | `RwLock`         | yes (default)   | blocks writers| yes              | exclusive                    | yes                            |
| [`SharedMut::new_mutex`] | `Mutex`          | yes (default)   | exclusive     | no               | exclusive                    | yes                            |
| [`SharedMut::new_rcu`]   | `RwLock<Arc<T>>` | no (ignored)    | **lock-free** `Arc` snapshot | yes | clone-on-write (or [`SharedMut::set`], which skips the clone) | **no** |

The stand-out row is RCU. A read is just an `Arc` snapshot of the current value: it never blocks a
writer, and — because it takes no lock that any other acquisition can wait on — it can never be part
of a lock-order cycle. See [Deadlock detection](#deadlock-detection) for why that makes it the
escape hatch for lock-ordering bugs.

Every mutable container can also be replaced wholesale with [`SharedMut::set`] (or the non-blocking
[`SharedMut::try_set`]) instead of `*container.write() = value`. For RCU this is not just sugar: it
installs the new value without the read-copy that a write guard performs.

## Basic syntax

The traditional Rust shared object patterns tend to be somewhat verbose and repetitive, for example:

```rust
# use std::sync::{Arc, Mutex};
# fn use_string(s: &str) {}
struct Foo {
    my_string: Arc<Mutex<String>>,
    my_integer: Arc<Mutex<u16>>,
}
let foo = Foo {
    my_string: Arc::new(Mutex::new("123".to_string())),
    my_integer: Arc::new(Mutex::new(1)),
};
use_string(&*foo.my_string.lock().expect("Mutex was poisoned"));
```

If we want to switch our shared fields from [`std::sync::Mutex`] to [`std::sync::RwLock`], we need to change four lines just for types, and
switch the `lock` method for a `read` method.

We can increase flexibility, and reduce some of the ceremony and verbosity with `keepcalm`:

```rust
# use keepcalm::*;
# fn use_string(s: &str) {}
struct Foo {
    my_string: SharedMut<String>,
    my_integer: SharedMut<u16>,
}
let foo = Foo {
    my_string: SharedMut::new("123".to_string()),
    my_integer: SharedMut::new(1),
};
use_string(&*foo.my_string.read());
```

If we want to use a `Mutex` instead of the default `RwLock` that [`SharedMut`] uses under the hood, we only need to change [`SharedMut::new`] to
[`SharedMut::new_mutex`]! 

## SharedMut

The [`SharedMut`] object hides the complexity of managing `Arc<Mutex<T>>`, `Arc<RwLock<T>>`, and other synchronization types
behind a single interface:

```rust
# use keepcalm::*;
let object = "123".to_string();
let shared = SharedMut::new(object);
shared.read();
```

By default, a [`SharedMut`] object uses `Arc<RwLock<T>>` under the hood, but you can choose the synchronization primitive at
construction time. The [`SharedMut`] object *erases* the underlying primitive and you can use them interchangeably:

```rust
# use keepcalm::*;
fn use_shared(shared: SharedMut<String>) {
    shared.read();
}

let shared = SharedMut::new("123".to_string());
use_shared(shared);
let shared = SharedMut::new_mutex("123".to_string());
use_shared(shared);
```

Managing the poison state of synchronization primitives can be challenging as well. Rust will poison a `Mutex` or `RwLock` if you
hold a lock while a `panic!` occurs.

The `SharedMut` type allows you to specify a [`PoisonPolicy`] at construction time. By default, if a synchronization
primitive is poisoned, the `SharedMut` will `panic!` on access. This can be configured so that poisoning is ignored:

```rust
# use keepcalm::*;
let shared = SharedMut::new_with_policy("123".to_string(), PoisonPolicy::Ignore);
```

## Shared

The default [`Shared`] object is similar to Rust's [`std::sync::Arc`], but adds the ability to project. [`Shared`] objects may also be
constructed as a `Mutex`, or may be a read-only view into a [`SharedMut`].

Note that because of this flexibility, the [`Shared`] object is slightly more complex than a traditional [`std::sync::Arc`], as all accesses
must be performed through the [`Shared::read`] accessor.

## Globals

While `static` globals may often be an anti-pattern in Rust, this library also offers easily-to-use alternatives that are compatible with
the [`Shared`] and [`SharedMut`] types.

Global [`Shared`] references can be created using [`SharedGlobal`]:

```rust
# use keepcalm::*;
static GLOBAL: SharedGlobal<usize> = SharedGlobal::new(1);

fn use_global() {
    assert_eq!(GLOBAL.read(), 1);

    // ... or ...

    let shared: Shared<usize> = GLOBAL.shared();
    assert_eq!(shared.read(), 1);
}
```

Similarly, global [`SharedMut`] references can be created using [`SharedGlobalMut`]:

```rust
# use keepcalm::*;
static GLOBAL: SharedGlobalMut<usize> = SharedGlobalMut::new(1);

fn use_global() {
    *GLOBAL.write() = 12;
    assert_eq!(GLOBAL.read(), 12);

    // ... or ...

    let shared: SharedMut<usize> = GLOBAL.shared_mut();
    *shared.write() = 12;
    assert_eq!(shared.read(), 12);
}
```

Both [`SharedGlobal`] and [`SharedGlobalMut`] offer a `new_lazy` constructor that allows initialization to be deferred to first
access:

```rust
# use keepcalm::*;
# use std::collections::HashMap;
static GLOBAL_LAZY: SharedGlobalMut<HashMap<&str, usize>> =
    SharedGlobalMut::new_lazy(|| HashMap::from_iter([("a", 1), ("b", 2)]));
```

Globals can be projected in `const` contexts using [`project_global!`], so a field of a global
can itself be exposed as a `static` [`Shared`] or [`SharedMut`] — without allocation, and while
still sharing the root's lock:

```rust
# use keepcalm::*;
struct App {
    name: String,
    port: u16,
}
static APP: SharedGlobalMut<App> = SharedGlobalMut::new_lazy(|| App {
    name: "server".to_string(),
    port: 80,
});
static NAME: SharedMut<String> = project_global!(mut APP => name);
static PORT: Shared<u16> = project_global!(APP => port);

*NAME.write() += "-1";
assert_eq!(APP.read().name, "server-1");
assert_eq!(*PORT.read(), 80);
```

## Deadlock detection

***NOTE**: This requires the `--feature deadlock_detection` flag*

With the `deadlock_detection` feature enabled, keepcalm tracks the order in which locks are
acquired across the whole process (in the style of the Linux kernel's lockdep). If a thread
is about to acquire a lock in an order that is inverted somewhere else in the program — the
classic recipe for a deadlock — it `panic!`s immediately with a report that includes a
backtrace of where the opposite ordering was first observed. Re-acquiring a lock the current
thread already holds (including through a projection, which shares its root's lock) is also
reported.

The check happens *before* the acquisition blocks, so a lock-order bug is caught the first
time the inverted order runs — the threads don't have to actually race into the deadlock:

```rust,no_run
# use keepcalm::*;
let a = SharedMut::new(1);
let b = SharedMut::new(2);
{
    let _a = a.write();
    let _b = b.write(); // records the ordering a -> b
}
let _b = b.write();
let _a = a.write();     // panics: b -> a inverts the recorded order
```

Because every container funnels through the same erased lock implementation, this works
uniformly across `RwLock`, `Mutex`, globals and projected containers. The report names each lock
by the type it guards (e.g. `lock #1 (my_app::HotSet)`). Plain `Arc` and RCU containers never
block while held and are exempt. `try_read`/`try_write` cannot deadlock and are never reported,
but locks they successfully acquire are tracked as held.

A detected cycle `panic!`s but does **not** poison the locks involved: the detector aborts
*before* acquiring the second lock, so the data under every currently-held lock is still
consistent. This means a single proactive warning brings down one code path (e.g. one request on
a server) rather than cascading into a poison outage across every subsequent access. (A genuine
panic while a lock is held still poisons as usual.)

This feature is intended for debug and test builds: every blocking acquisition consults a
global registry. Enable it in CI (`cargo test --features deadlock_detection`) and leave it
off in release builds, where it costs nothing.

## EXPERIMENTAL: Async

***NOTE**: This requires the `--feature async_experimental` flag*

This is extremely experimental and may have soundness and/or performance issues!

The [`Shared`] and [`SharedMut`] types support a `read_async` and `write_async` method that will block using an async runtime's `spawn_blocking`
method (or equivalent). Create a [`Spawner`] using `make_spawner` and pass that to the appropriate lock method.

Note that this relies on an async runtime to provide a blocking task thread-pool, so this may not be suitable for all use-cases.

```rust
# use keepcalm::*;
# #[cfg(feature="async_experimental")]
static SPAWNER: Spawner = make_spawner!(tokio::task::spawn_blocking);

# #[cfg(feature="async_experimental")]
async fn get_locked_value(shared: Shared<usize>) -> usize {
    *shared.read_async(&SPAWNER).await
}

# #[cfg(feature="async_experimental")]
{
    let shared = Shared::new(1);
    get_locked_value(shared);
}
```


## Projection

Both [`Shared`] and [`SharedMut`] allow *projection* into the underlying type. Projection can be used to select
either a subset of a type, or to cast a type to a trait. The [`project!`] and [`project_cast!`] macros can simplify
this code.

Note that projections are always linked to the root object! **A projection shares its source's
lock — and its lock-order identity.** Locking a projection locks the whole root, so two projections
of the same source (e.g. `state.config` and `state.sessions` obtained via `project_fn`) are *not*
independent locks: reading one contends with, and orders against, all the others. Under the
`deadlock_detection` feature a projection is reported as its root lock for exactly this reason.

Casting:

```rust
# use keepcalm::*;
let shared = SharedMut::new("123".to_string());

// Supported for most built-in traits
let shared_asref: SharedMut<dyn AsRef<str>> = shared.cast();

// Any trait may be projected using `project_cast!`
let shared_asref: SharedMut<dyn AsRef<str>> = shared.project(project_cast!(x: String => dyn AsRef<str>));
```

Subset of a struct/tuple — pass the container and the field path to [`project!`] and get the
projected container back, with no type annotations (tuple-index chains like `tuple.0` work too):

```rust
# use keepcalm::*;
#[derive(Default)]
struct Foo {
    tuple: (String, usize)
}

let shared = SharedMut::new(Foo::default());
let shared_string = project!(shared => tuple.0);

*shared_string.write() += "hello, world";
assert_eq!(shared.read().tuple.0, "hello, world");
assert_eq!(*shared_string.read(), "hello, world");
```

This form works on both [`SharedMut`] and [`Shared`] receivers. For projections that aren't
simple field paths, build a standalone projector with the original form,
`shared.project(project!(x: Foo, x.tuple.0))`, or supply the projection functions directly
via `project_fn`.

Read and write guards can also be projected after the lock is taken, using
[`SharedReadLock::map`] and [`SharedWriteLock::map`]:

```rust
# use keepcalm::*;
let shared = SharedMut::new((1, "hello".to_string()));
let guard = shared.read().map(|t| &t.1);
assert_eq!(*guard, "hello");
```

## Unsized types

Both [`Shared`] and [`SharedMut`] support unsized types, but due to current limitations in the language (see [`std::ops::CoerceUnsized`] for details),
you need to construct them in special ways.

Unsized traits are supported, but you will either need to specify `Send + Sync` in the shared type, or [`project_cast!`] the object:

```rust
# use keepcalm::*;

// In this form, `Send + Sync` are visible in the shared type
let boxed: Box<dyn AsRef<str> + Send + Sync> = Box::new("123".to_string());
let shared: SharedMut<dyn AsRef<str> + Send + Sync> = SharedMut::from_box(boxed);

// In this form, `Send + Sync` are erased via projection
let shared = SharedMut::new("123".to_string());
let shared_asref: SharedMut<dyn AsRef<str>> = shared.project(project_cast!(x: String => dyn AsRef<str>));
```

Unsized slices are supported using a box:

```rust
# use keepcalm::*;
let boxed: Box<[i32]> = Box::new([1, 2, 3]);
let shared: SharedMut<[i32]> = SharedMut::from_box(boxed);
```