ExecutionContext

First stab at implementing RFC 0002 for Crystal.

Basics

The ExecutionContext::SingleThreaded context starts a scheduler on a single thread. Every fiber spawned into that context will never run in parallel to each other (but they will run in parallel to fibers running in other contexts).

The ExecutionContext::MultiThreaded context starts multiple threads and a scheduler on each of them. Fibers spawned into that context will run in parallel to each other (and other fibers in other contexts). Pending runnable fibers may be stolen and resumed by any scheduler inside the context (but never be stolen and resumed by another context)

The ExecutionContext::Isolated context starts a single thread to run one exclusive fiber. Trying to spawn inside this context will actually enqueue the fibers into another context (the default context by default).

You can spawn a fiber into an explicit execution context using context.spawn while spawn will spawn into the current context.

Default context

The shard creates a default execution context that is ExecutionContext::SingleThreaded by default unless you specify the -Dmt compilation flag in addition to the -Dpreview_mt flag (required to make stdlib object thread safe) in which case it will be ExecutionContext::MultiThreaded.

Requirements

The shard replaces the regular Crystal::Scheduler implementation in stdlib with new ones.

The shard will monkey-patch many things into Crystal's stdlib as much as possible, yet there are a couple changes that requires to patch the stdlib directly.

The patches are in the /patches folder of the shard and must be applied on top of Crystal HEAD. I recommend to clone the Git repository on your local, and to keep it up to date:

$ git clone https://github.com/crystal-lang/crystal.git
$ cd crystal
$ for f in /path/to/execution_context/patches/*; do patch -p1 $f; done

Said patches have been merged and shall be released with Crystal 1.13.0. Maybe further changes will need patches again, though.

Usage

Add the shard to your project:

dependencies:
  execution_context:
    github: ysbaddaden/execution_context
    branch: main

The we can start creating execution contexts:

require "execution_context"

st = ExecutionContext::SingleThreaded.new("ST")
mt = ExecutionContext::MultiThreaded.new("MT", size: 4)

100.times do |i|
  st.spawn do
    print "Hello from ST (iteration #{i})\n"
  end

  mt.spawn do
    print "Hello from MT (iteration #{i})\n"
  end
end

sleep 1

Remember that you need a patched Crystal and we still need the preview_mt flag to enable thread safety in Crystal stdlib (e.g. GC, Channel, Mutex).

$ CRYSTAL=/path/to/patched-crystal/bin/crystal
$ $CRYSTAL run -Dpreview_mt test.cr
Hello from MT (iteration 0)
Hello from MT (iteration 1)
Hello from ST (iteration 2)
Hello from MT (iteration 2)
Hello from ST (iteration 3)
Hello from ST (iteration 0)
Hello from ST (iteration 1)
Hello from MT (iteration 3)
Hello from ST (iteration 4)
Hello from MT (iteration 4)
Hello from ST (iteration 5)
Hello from MT (iteration 5)
...

The messages should be in any order, mixing ST and MT.

License

Distributed under the Apache-2.0 license.