# module Fiber::ExecutionContext

##
Overview

An execution context creates and manages a dedicated pool of one or more schedulers where fibers will be running in. Each context manages the rules to run, suspend and swap fibers internally.

EXPERIMENTAL Execution contexts are an experimental feature, implementing RFC 2. It's opt-in and requires the compiler flags `-Dpreview_mt -Dexecution_context`.

An execution context groups fibers together. Instead of associating a fiber to a specific system thread, we associate a fiber to an execution context, abstracting which system thread(s) the fibers will run on.

Applications can create any number of execution contexts in parallel. Fibers running in any context can communicate and synchronize with any other fiber running in any context through the usual synchronization primitives such as `Channel`, `WaitGroup` or `Sync`.

When spawning a fiber with `::spawn`, it spawns into the execution context of the current fiber, so child fibers execute in the same context as their parent, unless told otherwise (see `ExecutionContext#spawn`).

Fibers are scoped to the execution context they are spawned into. Once spawned, a fiber cannot move to another execution context, and is always resumed in the same execution context.

##
Context types

The standard library provides a number of execution context implementations for common use cases.

- `ExecutionContext::Concurrent`: Fully concurrent with limited parallelism.

  Fibers run concurrently to each other, never in parallel (only one fiber at a time). They can use simpler and faster synchronization primitives internally (no atomics, limited thread safety), however communication with fibers in other contexts must be safe (e.g. `Channel,` Sync`, ...). A blocking fiber blocks the entire thread and all other fibers in the context.

- `ExecutionContext::Parallel`: Fully concurrent, fully parallel.

  Fibers running in this context can be resumed by multiple system threads in this context. They run concurrently and in parallel to each other (multiple fibers at a time), in addition to running in parallel to any fibers in other contexts. Schedulers steal work from each other. The parallelism can grow and shrink dynamically.

- `ExecutionContext::Isolated`: Single fiber in a single system thread without concurrency.

  This is useful for tasks that can block thread execution for a long time (e.g. CPU heavy computation) or must be reactive (e.g. a GUI or game loop). The event-loop works normally and so does communication and synchronization with fibers in other contexts (`Channel`, `WaitGroup`, `Sync`, ...). When the fiber needs to wait, it pauses the thread.

Again, any number of execution contexts can be created (as far as the computer can physically allow). An advantage of starting execution contexts is that it creates execution boundaries, the OS thread scheduler can for example preempt a system thread, allowing fibers in other system threads to run.

##
The default execution context

The Crystal runtime starts a default execution context exposed as `Fiber::ExecutionContext.default`. This is where the main fiber is running.

Its parallelism is set to 1 for backwards compatibility reasons; Crystal used to be single-threaded and concurrent only. You can increase the parallelism at any time using `Parallel#resize`, for example:

    count = Fiber::ExecutionContext.default_workers_count
    Fiber::ExecutionContext.default.resize(count)

##
Relationship with system threads

Execution contexts control when and how fibers run, and on which system thread they execute. The term parallelism is the maximum number of fibers that can run in parallel (maximum number of schedulers) but there can be less or more system threads running in practice, for example when a fiber is blocked on a syscall.

There are no guarantees on how a fiber will run on system threads. A fiber can start in thread A, then be resumed and terminated on thread A, B or C. This is true for both the `Parallel` and `Concurrent` contexts.

Notable exception: `Isolated` guarantees that its fiber will always run on the same system thread. During its lifetime, the fiber owns the thread, but only for the fiber's lifetime.

Threads are kept in a thread pool: threads can be started, attached and detached from any context at any time. Threads can be detached from a context and reattached to the same execution context or to another one (`Concurrent`, `Parallel` or `Isolated`).

EXPERIMENTAL

##
Direct including types

- Fiber::ExecutionContext::Isolated
- Fiber::ExecutionContext::Parallel

##
Defined in:

fiber/execution_context.cr
fiber/execution_context/concurrent.cr
fiber/execution_context/global_queue.cr
fiber/execution_context/isolated.cr
fiber/execution_context/monitor.cr
fiber/execution_context/parallel.cr
fiber/execution_context/parallel/scheduler.cr
fiber/execution_context/runnables.cr
fiber/execution_context/scheduler.cr
fiber/execution_context/thread_pool.cr

##
Constructors

- .current : ExecutionContext

  Returns the `ExecutionContext` the current fiber is running in.

##
Class Method Summary

- .current? : ExecutionContext | Nil
- .default : ExecutionContext::Parallel

  Returns the default `ExecutionContext` for the process, automatically started when the program started.

- .default_workers_count : Int32

  Returns the default maximum parallelism.

- .each(&) : Nil

  Iterates all execution contexts.

- .thread_keepalive : Time::Span

  How long a parked thread will be kept waiting in the thread pool.

- .thread_keepalive=(thread_keepalive : Time::Span)

  How long a parked thread will be kept waiting in the thread pool.

##
Instance Method Summary

- #spawn(*, name : String | Nil = nil, &block : -> ) : Fiber

  Creates a new fiber then enqueues it to the execution context.

##
Constructor Detail

def self.current : ExecutionContext #

Returns the `ExecutionContext` the current fiber is running in.

##
Class Method Detail

def self.current? : ExecutionContext | Nil #
def self.default : ExecutionContext::Parallel #

Returns the default `ExecutionContext` for the process, automatically started when the program started.

The default execution context is currently `Parallel` but only starts with parallelism set to 1. The parallelism can be changed using `Parallel#resize`.

def self.default_workers_count : Int32 #

Returns the default maximum parallelism. Can be used to resize the default parallel execution context for example.

Respects the `CRYSTAL_WORKERS` environment variable if present and valid, and otherwise defaults to the number of logical CPUs available to the process or on the computer.

def self.each(&) : Nil #

Iterates all execution contexts.

def self.thread_keepalive : Time::Span #

How long a parked thread will be kept waiting in the thread pool. Defaults to 5 minutes.

def self.thread_keepalive=(thread_keepalive : Time::Span) #

How long a parked thread will be kept waiting in the thread pool. Defaults to 5 minutes.

##
Instance Method Detail

def spawn(*, name : String | Nil = nil, &block : -> ) : Fiber #

Creates a new fiber then enqueues it to the execution context.

May be called from any `ExecutionContext` (i.e. must be thread-safe).