Stasis-0.0.1: A simple MVCC like library

Safe HaskellSafe-Inferred




Stasis is a modified implementation of Multi-version concurrency control MVCC. The original purpose of Stasis was to provide a mechanism for having safe, "mutable" variables WITHOUT any locking whatsoever.

How Stasis works

Given an object a. When a is passed to a function it is wrapped in a Pod which creates a stasis like environment for a.

At any point, any function that received the Pod can "update" it by putting a new version of a.

i.e. a itself is not updated, it is replaced. So what the Pod does is store the address of an a and an update changes the address the Pod points to, to another a.

The address of a can only change to point to another value of a, i.e. it is type safe.

At any time, a function who's received a Pod can freeze it. In this case, every time that function fetches a from the FrozenPod, the same version that was frozen is always returned and at any time the function can also get the latest version of a by getting the current non-frozen version.

Internally b maintains a map of addresses to frozen IDs, when a function freezes a version of a it stores the address of a with a unique freeze id that is returned to the function. The function must then pass the freeze id each time it wants to get the frozen version of a for that id. This also means that a function can lock multiple versions of a.

Originally the intention was to use pointers but IORefs works with atomic operations and achieves effectively the same thing.



Stasis has two primitives, a Pod and a FrozenPod. A Pod is 'created from any value a, once created, only new versions of a can be placed in the same pod. A Pod gets passed around in place of an ordinary value. Pods can be frozen to create a FrozenPod, when frozen the value of a is stays the same

data Pod a Source




val :: IORef (PodContent a)
frozenVersions :: IORef (Map a Int)


Eq (Pod a) 

data FrozenPod a Source




addr :: a
oPod :: Pod a
frozenAt :: Int


The type b supports 6 operations, stasis, put,get,fetch,freeze,defrost.

  1. stasis - creates a new Pod which can be passed around in place of a
  2. put - Put adds a new version of a to an existing Pod
  3. get - Gets the current version of a from a Pod
  4. freeze - Freezes a version of a in stasis, use defrost on the returned FrozenPod to get the value back
  5. defrost Returns the version of a for the given FrozenPod, i.e. it gets the value of a when it was frozen
  6. fetch the same as defrost but leaves a in a FrozenPod which means it will not be garbage collected until it is defrosted

stasis :: a -> IO (Pod a)Source

version :: Pod a -> IO IntSource

Fetch the current version of a Pod

versionF :: FrozenPod a -> IntSource

Fetch the version the value was frozen at

put :: a -> Pod a -> IO BoolSource



:: a

A value that would otherwise be the new version

-> Pod a

The pod which will have its next version created from the function provided

-> (a -> a -> a)

A function which accepts the new value, current value and returns a new value made by merging the first two

-> IO Bool 

put works great when there is a single writer but if multiple threads are writing new versions it is sometimes useful to be able to merge the current value with a new value to create a new version. This function allows for this and enables the ability for multiple writers to create new versions while minimizing the probability of version loss

get :: Pod a -> IO aSource

Get the value currently in Stasis

fetch :: FrozenPod a -> aSource

Fetch the frozen value from the given FrozenPod

freeze :: Ord a => Pod a -> IO (FrozenPod a)Source

Freeze the value of a at the current version

defrost :: Ord a => FrozenPod a -> IO (Pod a)Source

Defrosting a FrozenPod to a Pod causes the value in stasis to be de-referenced. Once this happens the value can be GC'd as would normally happen. Unless another function has frozen the same version, in which case that function has to unfreeze the value as well