package miou

  1. Overview
  2. Docs

An introduction about Miou's internals.

This module proposes a re-interpretation of the effects management offered by OCaml 5 centered on the idea necessary in the implementation of a round-robin scheduler. For simplicity, as soon as we talk about scheduler, we will talk about round-robin scheduler. This one focuses on:

  • simplicity of understanding and ease of implementation
  • solving the starvation problem
  • the communist idea of being able to fairly share the CPU time to our tasks

In this sense, the scheduler needs basic operations in the manipulation of tasks. Tasks are the functions (in the OCaml sense) to be executed. The scheduler must be able to:

  1. launch a task / apply a function
  2. stop a task at a specific point according to a metric
  3. have a representation of this suspended task (a state) and be able to store it
  4. restart a task from its state

Usually, the metric used to "stop" a task is time. That is to say that we could stop a task after 100ms has elapsed for example. Unfortunately, it seems difficult to translate this into OCaml. In this sense, the choice of our metric is: effect. That is to say that a task can only emit quanta effects (which we will of course have to manage). The quanta is the number of effects the task can produce.

Note about the Effect module.

If you follow us well and you are already familiar with the effects module in OCaml, you suspect that our implementation currently uses the Effect.Shallow module which allows you to manage 1 effect (and only one).

It is allowed to use the Effect.Deep module for the implementation of a scheduler - the latter makes it possible to manage several effects until a certain state of the function is obtained. In this case, when using the Effect.Deep module, we spontaneously obtain 2 states:

  • a suspended state because we have obtained a blocking effect
  • or a task termination state.

Note, however, the subtlety of the first in relation to what we want to "observe" of a task for our scheduler. This case explains the fact that the task stopped on a blocking event. We consider that this subtlety discriminates the tasks (which is in opposition to our communist ideal) between those which block and those which do not block.

In this sense, in our ideal and according to what is required by a round-robin scheduler, the suspension mechanism (stop a task) intervenes systematically for each effect. Discrimination must be radically combated.

The goal of this interface.

This module therefore allows us to "drive" our development on these principles described above - indeed, the Effect module is perhaps a little too permissive. Thus, if you have to modify this module and specifically this interface, beware of a "balkanization" effect which could betray our ideals.

type ('a, 'b) continuation

The type of continuations. ('a, 'b) continuation is the state of a function _ -> 'b. 'a is the type of the value to continue the continuation. The user can also discontinue with an exception the continuation.

type 'a t = private
  1. | Finished of ('a, exn) result
  2. | Suspended : ('a, 'b) continuation * 'a Effect.t -> 'b t

The type of function states.

The state of a function is its execution state. A function can finish with its return value or an exception, or it can suspend on an Effect.t. In the case of a suspension, the user can "continue" the execution via what is expected by the function depending on the effect and the function once. Note that once can only be used once on a given value (otherwise, an exception Continuation_already_resumed is raised by OCaml).

val make : ('a -> 'b) -> 'a -> 'b t

make fn value makes a new function state by executing the function with the given argument.

type 'a step =
  1. | Send of 'a
  2. | Fail of exn
  3. | Intr
  4. | Cont : 'a Effect.t -> 'a step
  5. | Yield : unit step

Type of a step.

A step is how we want to continue (or not) a function state (see t). The user continue the execution of the given function state (see Send), discontinue it with an exception (see Fail), do nothing (see Intr) or replace the current effect by an another one (see Cont as long as it expects the same type as the previous effect).

Yield actually continue the execution of the given function state but then stops, even if there is still a number of quanta available when run is used. It behaves in the same way as Send if you use once.

type ('a, 'b) k = ('a step -> 'b t) -> 'a Effect.t -> 'b t
type perform = {
  1. perform : 'a 'b. ('a, 'b) k;
}

Type of the effect handler.

perform is a function which should handle incoming effects and give a step to the given k according to the effect received. It's the effect handler.

val once : perform:perform -> 'a t -> 'a t

once ~perform state applies perform once on the given state if the latter emits an effect.

val fail : exn:exn -> 'a t -> 'a t

fail ~exn state discontinue the given state with the given exception. It always return Finished (Error exn).

val pure : ('a, exn) result -> 'a t

pure value returns Finished value.

val run : quanta:int -> perform:perform -> 'a t -> 'a t

run ~quanta ~perform state applies once quanta times. If perform responds with Intr (and therefore does nothing), even though there may be a few quanta left, the function returns the last state obtained.

The same applies to Yield, except that the continuation has burnt itself out. In other words, Yield is equivalent to Send (); Intr but costs only one quanta.

OCaml

Innovation. Community. Security.