{"id":19112136,"url":"https://github.com/ocaml-multicore/picos","last_synced_at":"2025-04-05T22:06:56.819Z","repository":{"id":197775568,"uuid":"695196901","full_name":"ocaml-multicore/picos","owner":"ocaml-multicore","description":"Interoperable effects based concurrency","archived":false,"fork":false,"pushed_at":"2025-02-27T10:16:35.000Z","size":21365,"stargazers_count":124,"open_issues_count":23,"forks_count":6,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-02T21:39:25.113Z","etag":null,"topics":["cancelation","concurrency","effects","interoperability","parallelism"],"latest_commit_sha":null,"homepage":"https://ocaml-multicore.github.io/picos/","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ocaml-multicore.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-09-22T15:07:28.000Z","updated_at":"2025-03-31T18:18:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"ae06d8e1-0a7c-4ae7-a64d-9f6744ef9c28","html_url":"https://github.com/ocaml-multicore/picos","commit_stats":{"total_commits":45,"total_committers":1,"mean_commits":45.0,"dds":0.0,"last_synced_commit":"7df26128a555edd9c69fba6199dc6eafd9e7b109"},"previous_names":["ocaml-multicore/picos"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocaml-multicore%2Fpicos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocaml-multicore%2Fpicos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocaml-multicore%2Fpicos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocaml-multicore%2Fpicos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ocaml-multicore","download_url":"https://codeload.github.com/ocaml-multicore/picos/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247406088,"owners_count":20933803,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cancelation","concurrency","effects","interoperability","parallelism"],"created_at":"2024-11-09T04:31:44.768Z","updated_at":"2025-04-05T22:06:56.796Z","avatar_url":"https://github.com/ocaml-multicore.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"[API reference](https://ocaml-multicore.github.io/picos/doc/index.html) \u0026middot;\n[Benchmarks](https://bench.ci.dev/ocaml-multicore/picos/branch/main?worker=pascal\u0026image=bench.Dockerfile)\n\u0026middot;\n[Stdlib Benchmarks](https://bench.ci.dev/ocaml-multicore/multicore-bench/branch/main?worker=pascal\u0026image=bench.Dockerfile)\n\n\u003cdiv align=\"center\"\u003e\n\n\u003ca href=\"https://ocaml-multicore.github.io/picos/\"\u003e\n  \u003cimg width=\"70%\" src=\"https://raw.githubusercontent.com/ocaml-multicore/picos/main/doc/picos-logo.svg\"\u003e\n\u003c/a\u003e\n\n# **Picos** \u0026mdash; Interoperable effects based concurrency\n\n\u003c/div\u003e\n\nPicos is a\n[systems programming](https://en.wikipedia.org/wiki/Systems_programming)\ninterface between effects based schedulers and concurrent abstractions.\n\n\u003cp align=\"center\"\u003e\u003ca href=\"https://ocaml-multicore.github.io/picos/doc/picos/index.html\"\u003e\u003cimg width=\"65%\" src=\"https://raw.githubusercontent.com/ocaml-multicore/picos/main/doc/picos-ecosystem.svg\"\u003e\u003c/a\u003e\u003c/p\u003e\n\nPicos is designed to enable an _open ecosystem_ of\n[interoperable](https://en.wikipedia.org/wiki/Interoperability) and\ninterchangeable elements of effects based cooperative concurrent programming\nmodels such as\n\n- [schedulers](\u003chttps://en.wikipedia.org/wiki/Scheduling_(computing)\u003e) that\n  multiplex large numbers of\n  [user level fibers](https://en.wikipedia.org/wiki/Green_thread) to run on a\n  small number of system level threads,\n- mechanisms for managing fibers and for\n  [structuring concurrency](https://en.wikipedia.org/wiki/Structured_concurrency),\n- communication and synchronization primitives, such as\n  [mutexes and condition variables](\u003chttps://en.wikipedia.org/wiki/Monitor_(synchronization)\u003e),\n  message queues,\n  [STM](https://en.wikipedia.org/wiki/Software_transactional_memory)s, and more,\n  and\n- integrations with low level\n  [asynchronous IO](https://en.wikipedia.org/wiki/Asynchronous_I/O) systems\n\nby decoupling such elements from each other.\n\nPicos comes with a\n[reference manual](https://ocaml-multicore.github.io/picos/doc/index.html) and\nmany sample libraries.\n\n⚠️ Please note that Picos is still considered experimental and unstable.\n\n## Introduction\n\nPicos addresses the incompatibility of effects based schedulers at a fundamental\nlevel by introducing\n[an _interface_ to decouple schedulers and other concurrent abstractions](https://ocaml-multicore.github.io/picos/doc/picos/Picos/index.html)\nthat need services from a scheduler.\n\nThe\n[core abstractions of Picos](https://ocaml-multicore.github.io/picos/doc/picos/Picos/index.html#the-architecture-of-picos)\nare\n\n- [`Trigger`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Trigger/index.html)\n  — the ability to await for a signal,\n- [`Computation`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Computation/index.html)\n  — a cancelable computation, and\n- [`Fiber`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Fiber/index.html)\n  — an independent thread of execution,\n\nthat are implemented partially by the Picos interface in terms of the effects\n\n- [`Trigger.Await`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Trigger/index.html#extension-Await)\n  — to suspend and resume a fiber,\n- [`Computation.Cancel_after`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Computation/index.html#extension-Cancel_after)\n  — to cancel a computation after given period of time,\n- [`Fiber.Current`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Fiber/index.html#extension-Current)\n  — to obtain the current fiber,\n- [`Fiber.Yield`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Fiber/index.html#extension-Yield)\n  — to request rescheduling, and\n- [`Fiber.Spawn`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Fiber/index.html#extension-Spawn)\n  — to start a new fiber.\n\nThe partial implementation of the abstractions and the effects define a contract\nbetween schedulers and other concurrent abstractions. By handling the Picos\neffects according to the contract a scheduler becomes _Picos compatible_, which\nallows any abstractions written against the Picos interface, i.e. _Implemented\nin Picos_, to be used with the scheduler.\n\n### Understanding cancelation\n\nA central idea or goal of Picos is to provide a collection of building blocks\nfor parallelism safe cancelation that allows the implementation of both blocking\nabstractions as well as the implementation of abstractions for structuring\nfibers for cancelation or managing the propagation and scope of cancelation.\n\nWhile cancelation, which is essentially a kind of asynchronous exception or\nsignal, is not necessarily recommended as a general control mechanism, the\nability to cancel fibers in case of errors is crucial for the implementation of\npractical concurrent programming models.\n\nConsider the following characteristic\n[example](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/index.html#understanding-cancelation):\n\n```ocaml skip\nMutex.protect mutex begin fun () -\u003e\n  while true do\n    Condition.wait condition mutex\n  done\nend\n```\n\nAssume that a fiber executing the above code might be canceled, at any point, by\nanother fiber running in parallel. This could be necessary, for example, due to\nan error that requires the application to be shut down. How could that be done\nwhile ensuring both\n[safety and liveness](https://en.wikipedia.org/wiki/Safety_and_liveness_properties)?\n\n- For safety, cancelation should not leave the program in an invalid state or\n  cause the program to leak memory. In this case, `Condition.wait` must exit\n  with the mutex locked, even in case of cancelation, and, as `Mutex.protect`\n  exits, the ownership of the mutex must be transferred to the next fiber, if\n  any, waiting in queue for the mutex. No references to unused objects may be\n  left in the mutex or the condition variable.\n\n- For liveness, cancelation should ensure that the fiber will eventually\n  continue after cancelation. In this case, cancelation could be triggered\n  during the `Mutex.lock` operation inside `Mutex.protect` or the\n  `Condition.wait` operation, when the fiber might be in a suspended state, and\n  cancelation should then allow the fiber to continue.\n\nThe set of abstractions, `Trigger`, `Computation`, and `Fiber`, work together\n[to support cancelation](https://ocaml-multicore.github.io/picos/doc/picos/Picos/index.html#cancelation-in-picos).\nBriefly, a fiber corresponds to an independent thread of execution and every\nfiber is associated with a computation at all times. When a fiber creates a\ntrigger in order to await for a signal, it ask the scheduler to suspend the\nfiber on the trigger. Assuming the fiber has not forbidden the propagation of\ncancelation, which is required, for example, in the implementation of\n`Condition.wait` to lock the mutex upon exit, the scheduler must also attach the\ntrigger to the computation associated with the fiber. If the computation is then\ncanceled before the trigger is otherwise signaled, the trigger will be signaled\nby the cancelation of the computation, and the fiber will be resumed by the\nscheduler as canceled.\n\nThis cancelable suspension protocol and its partial implementation designed\naround the first-order\n[`Trigger.Await`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Trigger/index.html#extension-Await)\neffect creates a clear separation between schedulers and user code running in\nfibers and is designed to handle the possibility of a trigger being signaled or\na computation being canceled at any point during the suspension of a fiber.\nSchedulers are given maximal freedom to decide which fiber to resume next. As an\nexample, a scheduler could give priority to canceled fibers \u0026mdash; going as far\nas moving a fiber already in the ready queue of the scheduler to the front of\nthe queue at the point of cancelation \u0026mdash; based on the assumption that user\ncode promptly cancels external requests and frees critical resources.\n\n### `Trigger`\n\nA trigger provides the ability to await for a signal and is perhaps the best\nestablished and least controversial element of the Picos interface.\n\nHere is an extract from the signature of the\n[`Trigger` module](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Trigger/index.html):\n\n\u003c!--\n```ocaml\n# open Picos\n# open Picos_std_finally\n# open Picos_std_structured\n# open Picos_std_sync\n```\n--\u003e\n\n```ocaml skip\ntype t\nval create : unit -\u003e t\nval await : t -\u003e (exn * Printexc.raw_backtrace) option\nval signal : t -\u003e unit\nval on_signal : (* for schedulers *)\n```\n\nThe idea is that a fiber may create a trigger, insert it into some shared data\nstructure, and then call `await` to ask the scheduler to suspend the fiber until\nsomething signals the trigger. When `await` returns an exception with a\nbacktrace it means that the fiber has been canceled.\n\nAs an example, let's consider the implementation of an `Ivar` or incremental or\nsingle-assignment variable:\n\n```ocaml skip\ntype 'a t\nval create : unit -\u003e 'a t\nval try_fill : 'a t -\u003e 'a -\u003e bool\nval read : 'a t -\u003e 'a\n```\n\nAn `Ivar` is created as empty and can be filled with a value once. An attempt to\nread an `Ivar` blocks until the `Ivar` is filled.\n\nUsing `Trigger` and `Atomic`, we can represent an `Ivar` as follows:\n\n```ocaml\ntype 'a state =\n  | Filled of 'a\n  | Empty of Trigger.t list\n\ntype 'a t = 'a state Atomic.t\n```\n\nThe `try_fill` operation is then fairly straightforward to implement:\n\n```ocaml\nlet rec try_fill t value =\n  match Atomic.get t with\n  | Filled _ -\u003e false\n  | Empty triggers as before -\u003e\n    let after = Filled value in\n    if Atomic.compare_and_set t before after then\n      begin\n        List.iter Trigger.signal triggers; (* ! *)\n        true\n      end\n    else\n      try_fill t value\n```\n\nThe interesting detail above is that after successfully filling an `Ivar`, the\ntriggers are signaled. This allows the `await` inside the `read` operation to\nreturn:\n\n\u003c!--\n```ocaml\nlet cleanup _t _trigger = ()\n```\n---\u003e\n\n```ocaml\nlet rec read t =\n  match Atomic.get t with\n  | Filled value -\u003e value\n  | Empty triggers as before -\u003e\n    let trigger = Trigger.create () in\n    let after = Empty (trigger :: triggers) in\n    if Atomic.compare_and_set t before after then\n      match Trigger.await trigger with\n      | None -\u003e read t\n      | Some (exn, bt) -\u003e\n        cleanup t trigger; (* ! *)\n        Printexc.raise_with_backtrace exn bt\n    else\n      read t\n```\n\nAn important detail above is that when `await` returns an exception with a\nbacktrace, meaning that the fiber has been canceled, the `cleanup` operation\n(which is omitted) is called to remove the `trigger` from the `Ivar` to avoid\npotentially accumulating unbounded numbers of triggers in an empty `Ivar`.\n\nAs simple as it is, the design of `Trigger` is far from arbitrary:\n\n- First of all, `Trigger` has single-assignment semantics. After being signaled,\n  a trigger takes a constant amount of space and does not point to any other\n  heap object. This makes it easier to reason about the behavior and can also\n  help to avoid leaks or optimize data structures containing triggers, because\n  it is safe to hold bounded amounts of signaled triggers.\n\n- The `Trigger` abstraction is essentially first-order, which provides a clear\n  separation between a scheduler and programs, or fibers, running on a\n  scheduler. The `await` operation performs the `Await` effect, which passes the\n  trigger to the scheduler. The scheduler then attaches its own callback to the\n  trigger using `on_signal`. This way a scheduler does not call arbitrary user\n  specified code in the `Await` effect handler.\n\n- Separating the creation of a trigger from the `await` operation allows one to\n  easily insert a trigger into any number of places and allows the trigger to be\n  potentially concurrently signaled before the `Await` effect is performed in\n  which case the effect can be skipped entirely.\n\n- No value is propagated with a trigger. This makes triggers simpler and makes\n  it less likely for one to e.g. accidentally drop such a value. In many cases,\n  like with the `Ivar`, there is already a data structure through which values\n  can be propagated.\n\n- The `signal` operation gives no indication of whether a fiber will then be\n  resumed as canceled or not. This gives maximal flexibility for the scheduler\n  and also makes it clear that cancelation must be handled based on the return\n  value of `await`.\n\n### `Computation`\n\nA `Computation` basically holds the status, i.e. _running_, _returned_, or\n_canceled_, of some sort of computation and allows anyone with access to the\ncomputation to attach triggers to it to be signaled in case the computation\nstops running.\n\nHere is an extract from the signature of the\n[`Computation` module](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Computation/index.html):\n\n```ocaml skip\ntype 'a t\n\nval create : unit -\u003e 'a t\n\nval try_attach : 'a t -\u003e Trigger.t -\u003e bool\nval detach : 'a t -\u003e Trigger.t -\u003e unit\n\nval try_return : 'a t -\u003e 'a -\u003e bool\nval try_cancel : 'a t -\u003e exn -\u003e Printexc.raw_backtrace -\u003e bool\n\nval check : 'a t -\u003e unit\nval await : 'a t -\u003e 'a\n```\n\nA `Computation` directly provides a superset of the functionality of the `Ivar`\nwe sketched in the previous section:\n\n```ocaml\ntype 'a t = 'a Computation.t\nlet create : unit -\u003e 'a t = Computation.create\nlet try_fill : 'a t -\u003e 'a -\u003e bool =\n  Computation.try_return\nlet read : 'a t -\u003e 'a = Computation.await\n```\n\nHowever, what really makes the `Computation` useful is the ability to\nmomentarily attach triggers to it. A `Computation` essentially implements a\nspecialized lock-free bag of triggers, which allows one to implement dynamic\ncompletion propagation networks.\n\nThe `Computation` abstraction is also designed with both simplicity and\nflexibility in mind:\n\n- Similarly to `Trigger`, `Computation` has single-assignment semantics, which\n  makes it easier to reason about.\n\n- Unlike a typical cancelation context of a structured concurrency model,\n  `Computation` is unopinionated in that it does not impose a specific\n  hierarchical structure.\n\n- Anyone may ask to be notified when a `Computation` is completed by attaching\n  triggers to it and anyone may complete a `Computation`. This makes\n  `Computation` an omnidirectional communication primitive.\n\nInterestingly, and unintentionally, it turns out that, given\n[the ability to complete two (or more) computations atomically](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Computation/Tx/index.html),\n`Computation` is essentially expressive enough to implement the\n[event](https://ocaml.org/manual/latest/api/Event.html) abstraction of\n[Concurrent ML](https://en.wikipedia.org/wiki/Concurrent_ML). The same features\nthat make `Computation` suitable for implementing more or less arbitrary dynamic\ncompletion propagation networks make it suitable for implementing Concurrent ML\nstyle abstractions.\n\n### `Fiber`\n\nA fiber corresponds to an independent thread of execution. Technically an\neffects based scheduler creates a fiber, effectively giving it an identity, as\nit runs some function under its handler. The `Fiber` abstraction provides a way\nto share a proxy identity, and a bit of state, between a scheduler and other\nconcurrent abstractions.\n\nHere is an extract from the signature of the\n[`Fiber` module](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Fiber/index.html):\n\n```ocaml skip\ntype t\n\nval current : unit -\u003e t\n\nval create : forbid:bool -\u003e 'a Computation.t -\u003e t\nval spawn : t -\u003e (t -\u003e unit) -\u003e unit\n\nval get_computation : t -\u003e Computation.packed\nval set_computation : t -\u003e Computation.packed -\u003e unit\n\nval has_forbidden : t -\u003e bool\nval exchange : t -\u003e forbid:bool -\u003e bool\n\nmodule FLS : sig (* ... *) end\n```\n\nFibers are where all of the low level bits and pieces of Picos come together,\nwhich makes it difficult to give both meaningful and concise examples, but let's\nimplement a slightly simplistic structured concurrency mechanism:\n\n```ocaml skip\ntype t (* represents a scope *)\nval run : (t -\u003e unit) -\u003e unit\nval fork : t -\u003e (unit -\u003e unit) -\u003e unit\n```\n\nThe idea here is that `run` creates a \"scope\" and waits until all of the fibers\nforked into the scope have finished. In case any fiber raises an unhandled\nexception, or the main fiber that created the scope is canceled, all of the\nfibers are canceled and an exception is raised. To keep things slightly simpler,\nonly the first exception is kept.\n\nA scope can be represented by a simple record type:\n\n```ocaml\ntype t = {\n  count : int Atomic.t;\n  inner : unit Computation.t;\n  ended : Trigger.t;\n}\n```\n\nThe idea is that after a fiber is finished, we decrement the count and if it\nbecomes zero, we finish the computation and signal the main fiber that the scope\nhas ended:\n\n```ocaml\nlet decr t =\n  let n = Atomic.fetch_and_add t.count (-1) in\n  if n = 1 then begin\n    Computation.finish t.inner;\n    Trigger.signal t.ended\n  end\n```\n\nWhen forking a fiber, we increment the count unless it already was zero, in\nwhich case we raise an error:\n\n```ocaml\nlet rec incr t =\n  let n = Atomic.get t.count in\n  if n = 0 then invalid_arg \"ended\";\n  if not (Atomic.compare_and_set t.count n (n + 1))\n  then incr t\n```\n\nThe fork operation is now relatively straightforward to implement:\n\n```ocaml\nlet fork t action =\n  incr t;\n  try\n    let main _ =\n      match action () with\n      | () -\u003e decr t\n      | exception exn -\u003e\n          let bt = Printexc.get_raw_backtrace () in\n          Computation.cancel t.inner exn bt;\n          decr t\n    in\n    let fiber =\n      Fiber.create ~forbid:false t.inner\n    in\n    Fiber.spawn fiber main\n  with canceled_exn -\u003e\n    decr t;\n    raise canceled_exn\n```\n\nThe above `fork` first increments the count and then tries to spawn a fiber. The\nPicos interface specifies that when `Fiber.spawn` returns normally, the action,\n`main`, must be called by the scheduler. This allows us to ensure that the\nincrement is always matched with a decrement.\n\nSetting up a scope is the most complex operation:\n\n\u003c!--\n```ocaml\nlet join _ _ _ _ = ()\n```\n--\u003e\n\n```ocaml\nlet run body =\n  let count = Atomic.make 1 in\n  let inner = Computation.create () in\n  let ended = Trigger.create () in\n  let t = { count; inner; ended } in\n  let fiber = Fiber.current () in\n  let (Packed outer) =\n    Fiber.get_computation fiber\n  in\n  let canceler =\n    Computation.attach_canceler\n      ~from:outer\n      ~into:t.inner\n  in\n  match\n    Fiber.set_computation fiber (Packed t.inner);\n    body t\n  with\n  | () -\u003e join t outer canceler fiber\n  | exception exn -\u003e\n      let bt = Printexc.get_raw_backtrace () in\n      Computation.cancel t.inner exn bt;\n      join t outer canceler fiber;\n      Printexc.raise_with_backtrace exn bt\n```\n\nThe `Computation.attach_canceler` operation attaches a special trigger to\npropagate cancelation from one computation into another. After the body exits,\n`join`\n\n```ocaml\nlet join t outer canceler fiber =\n  decr t;\n  Fiber.set_computation fiber (Packed outer);\n  let forbid = Fiber.exchange fiber ~forbid:true in\n  Trigger.await t.ended |\u003e ignore;\n  Fiber.set fiber ~forbid;\n  Computation.detach outer canceler;\n  Computation.check t.inner;\n  Fiber.check fiber\n```\n\nis called to wait for the scoped fibers and restore the state of the main fiber.\nAn important detail is that propagation of cancelation is forbidden by setting\nthe `forbid` flag to `true` before the call of `Trigger.await`. This is\nnecessary to ensure that `join` does not exit, due to the fiber being canceled,\nbefore all of the child fibers have actually finished. Finally, `join` checks\nthe inner computation and the fiber, which means that an exception will be\nraised in case either was canceled.\n\nThe design of `Fiber` includes several key features:\n\n- The low level design allows one to both avoid unnecessary overheads, such as\n  allocating a `Computation.t` for every fiber, when implementing simple\n  abstractions and also to implement more complex behaviors that might prove\n  difficult given e.g. a higher level design with a built-in notion of\n  hierarchy.\n\n- As `Fiber.t` stores the `forbid` flag and the `Computation.t` associated with\n  the fiber one need not pass those as arguments through the program. This\n  allows various concurrent abstractions to be given traditional interfaces,\n  which would otherwise need to be complicated.\n\n- Effects are relatively expensive. The cost of performing effects can be\n  amortized by obtaining the `Fiber.t` once and then manipulating it multiple\n  times.\n\n- A `Fiber.t` also provides an identity for the fiber. It has so far proven to\n  be sufficient for most purposes. Fiber local storage, which we do not cover\n  here, can be used to implement, for example, a unique integer id for fibers.\n\n### Assumptions\n\nNow, consider the `Ivar` abstraction presented earlier as an example of the use\nof the `Trigger` abstraction. That `Ivar` implementation, as well as the\n`Computation` based implementation, works exactly as desired inside the scope\nabstraction presented in the previous section. In particular, a blocked\n`Ivar.read` can be canceled, either when another fiber in a scope raises an\nunhandled exception or when the main fiber of the scope is canceled, which\nallows the fiber to continue by raising an exception after cleaning up. In fact,\nPicos comes with a number of libraries that all would work quite nicely with the\nexamples presented here.\n\nFor example, a library provides an operation to run a block with a timeout on\nthe current fiber. One could use it with `Ivar.read` to implement a read\noperation\n[with a timeout](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/Control/index.html#val-terminate_after):\n\n```ocaml\nlet read_in ~seconds ivar =\n  Control.terminate_after ~seconds @@ fun () -\u003e\n  Ivar.read ivar\n```\n\nThis interoperability is not accidental. For example, the scope abstraction\nbasically assumes that one does not use `Fiber.set_computation`, in an arbitrary\nunscoped manner inside the scoped fibers. An idea with the Picos interface\nactually is that it is not supposed to be used by applications at all and most\nhigher level libraries should be built on top of libraries that do not directly\nexpose elements of the Picos interface.\n\nPerhaps more interestingly, there are obviously limits to what can be achieved\nin an \"interoperable\" manner. Imagine an operation like\n\n```ocaml skip\nval at_exit : (unit -\u003e unit) -\u003e unit\n```\n\nthat would allow one to run an action just before a fiber exits. One could, of\ncourse, use a custom spawn function that would support such cleanup, but then\n`at_exit` could only be used on fibers spawned through that particular spawn\nfunction.\n\n### The effects\n\nAs mentioned previously, the Picos interface is implemented partially in terms\nof five effects:\n\n```ocaml version\u003e=5.0.0\ntype _ Effect.t +=\n  | Await : Trigger.t -\u003e (exn * Printexc.raw_backtrace) option Effect.t\n  | Cancel_after : {\n      seconds : float;\n      exn: exn;\n      bt : Printexc.raw_backtrace;\n      computation : 'a Computation.t;\n    }\n      -\u003e unit Effect.t\n  | Current : t Effect.t\n  | Yield : unit Effect.t\n  | Spawn : {\n      fiber : Fiber.t;\n      main : (Fiber.t -\u003e unit);\n    }\n      -\u003e unit Effect.t\n```\n\nA scheduler must handle those effects as specified in the Picos documentation.\n\nThe Picos interface does not, in particular, dictate which ready fibers a\nscheduler must run next and on which domains. Picos also does not require that a\nfiber should stay on the domain on which it was spawned. Abstractions\nimplemented against the Picos interface should not assume any particular\nscheduling.\n\nPicos actually comes with\n[a randomized multithreaded scheduler](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_randos/index.html),\nthat, after handling any of the effects, picks the next ready fiber randomly. It\nhas proven to be useful for testing that abstractions implemented in Picos do\nnot make invalid scheduling assumptions.\n\nWhen a concurrent abstraction requires a particular scheduling, it should\nprimarily be achieved through the use of synchronization abstractions like when\nprogramming with traditional threads. Application programs may, of course, pick\nspecific schedulers.\n\n## Status and results\n\nWe have an experimental design and implementation of the core Picos interface as\nillustrated in the previous section. We have also created several _Picos\ncompatible_\n[sample schedulers](https://ocaml-multicore.github.io/picos/doc/picos_mux/index.html).\nA scheduler, in this context, just multiplexes fibers to run on one or more\nsystem level threads. We have also created some sample higher-level\n[scheduler agnostic libraries](https://ocaml-multicore.github.io/picos/doc/picos_std/index.html)\n_Implemented in Picos_. These libraries include\n[a library for resource management](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html),\n[a library for structured concurrency](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/index.html),\n[a library of synchronization primitives](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_sync/index.html),\nand\n[an asynchronous I/O library](https://ocaml-multicore.github.io/picos/doc/picos_io/Picos_io/index.html).\nThe synchronization library and the I/O library intentionally mimic libraries\nthat come with the OCaml distribution. All of the libraries work with all of the\nschedulers and all of these _elements_ are interoperable and entirely opt-in.\n\nWhat is worth explicitly noting is that all of these schedulers and libraries\nare small, independent, and highly modular pieces of code. They all crucially\ndepend on and are decoupled from each other via the core Picos interface\nlibrary. A basic single threaded scheduler implementation requires only about\n100 lines of code (LOC). A more complex parallel scheduler might require a\ncouple of hundred LOC. The scheduler agnostic libraries are similarly small.\n\nHere is an\n[example](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/index.html#a-simple-echo-server-and-clients)\nof a concurrent echo server using the scheduler agnostic libraries provided as\nsamples:\n\n```ocaml\nlet run_server server_fd =\n  Unix.listen server_fd 8;\n  Flock.join_after begin fun () -\u003e\n    while true do\n      let@ client_fd = instantiate Unix.close @@ fun () -\u003e\n        Unix.accept ~cloexec:true server_fd |\u003e fst\n      in\n      Flock.fork begin fun () -\u003e\n        let@ client_fd = move client_fd in\n        Unix.set_nonblock client_fd;\n        let bs = Bytes.create 100 in\n        let n =\n          Unix.read client_fd bs 0 (Bytes.length bs)\n        in\n        Unix.write client_fd bs 0 n |\u003e ignore\n      end\n    done\n  end\n```\n\nThe\n[`Unix`](https://ocaml-multicore.github.io/picos/doc/picos_io/Picos_io/Unix/index.html)\nmodule is provided by the I/O library. The operations on file descriptors on\nthat module, such as `accept`, `read`, and `write`, use the Picos interface to\nsuspend fibers allowing other fibers to run while waiting for I/O. The\n[`Flock`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/Flock/index.html)\nmodule comes from the structured concurrency library. A call of\n[`join_after`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/Flock/index.html#val-join_after)\nreturns only after all the fibers\n[`fork`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/Flock/index.html#val-fork)ed\ninto the flock have terminated. If the main fiber of the flock is canceled, or\nany fiber within the flock raises an unhandled exception, all the fibers within\nthe flock will be canceled and an exception will be raised on the main fiber of\nthe flock. The\n[`let@`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html#val-let@),\n[`finally`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html#val-instantiate),\nand\n[`move`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html#val-move)\noperations come from the resource management library and allow dealing with\nresources in a leak-free manner. The responsibility to close the `client_fd`\nsocket is\n[`move`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html#val-move)d\nfrom the main server fiber to a fiber forked to handle that client.\n\nWe should emphasize that the above is just an example. The Picos interface\nshould be both expressive and efficient enough to support practical\nimplementations of many different kinds of concurrent programming models. Also,\nas described previously, the Picos interface does not, for example, internally\nimplement structured concurrency. However, the abstractions provided by Picos\nare designed to allow structured and unstructured concurrency to be _Implemented\nin Picos_ as libraries that will then work with any _Picos compatible_ scheduler\nand with other concurrent abstractions.\n\nFinally, an interesting demonstration that Picos really fundamentally is an\ninterface is\n[a prototype _Picos compatible_ direct style interface to Lwt](https://ocaml-multicore.github.io/picos/doc/picos_lwt/Picos_lwt/index.html).\nThe implementation uses shallow effect handlers and defers all scheduling\ndecisions to Lwt. Running a program with the scheduler returns a Lwt promise.\n\n## Future work\n\nAs mentioned previously, Picos is still an ongoing project and the design is\nconsidered experimental. We hope that Picos soon matures to serve the needs of\nboth the commercial users of OCaml and the community at large.\n\nPrevious sections already touched a couple of updates currently in development,\nsuch as the support for finalizing resources stored in\n[`FLS`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Fiber/FLS/index.html)\nand the development of Concurrent ML style abstractions. We also have ongoing\nwork to formalize aspects of the Picos interface.\n\nOne potential change we will be investigating is whether the\n[`Computation`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Computation/index.html)\nabstraction should be simplified to only support cancelation.\n\nThe implementation of some operations, such as\n[`Fiber.current`](https://ocaml-multicore.github.io/picos/doc/picos/Picos/Fiber/index.html#val-current)\nto retrieve the current fiber proxy identity, do not strictly need to be\neffects. Performing an effect is relatively expensive and we will likely design\na mechanism to store a reference to the current fiber in some sort of local\nstorage, which could significantly improve the performance of certain\nabstractions, such as checked mutexes, that need to access the current fiber.\n\nWe also plan to develop a minimalist library for spawning threads over domains,\nmuch like Moonpool, in a cooperative manner for schedulers and other libraries.\n\nWe also plan to make Domainslib Picos compatible, which will require developing\na more efficient non-effects based interface for spawning fibers, and\ninvestigate making Eio Picos compatible.\n\nWe also plan to design and implement asynchronous IO libraries for Picos using\nvarious system call interface for asynchronous IO such as io_uring.\n\nFinally, Picos is supposed to be an _open ecosystem_. If you have feedback or\nwould like to work on something mentioned above, let us know.\n\n## Motivation\n\nThere are already several concrete effects-based concurrent programming\nlibraries and models being developed. Here is a list of some such publicly\navailable projects:\u003csup\u003e[\\*](https://xkcd.com/927/)\u003c/sup\u003e\n\n1. [Affect](https://github.com/dbuenzli/affect) — \"Composable concurrency\n   primitives with OCaml effects handlers (unreleased)\",\n2. [Domainslib](https://github.com/ocaml-multicore/domainslib) —\n   \"Nested-parallel programming\",\n3. [Eio](https://github.com/ocaml-multicore/eio) — \"Effects-Based Parallel IO\n   for OCaml\",\n4. [Fuseau](https://github.com/c-cube/fuseau) — \"Lightweight fiber library for\n   OCaml 5\",\n5. [Miou](https://github.com/robur-coop/miou) — \"A simple scheduler for OCaml\n   5\",\n6. [Moonpool](https://github.com/c-cube/moonpool) — \"Commodity thread pools for\n   OCaml 5\", and\n7. [Riot](https://github.com/leostera/riot) — \"An actor-model multi-core\n   scheduler for OCaml 5\".\n\nAll of the above libraries are mutually incompatible with each other with the\nexception that Domainslib, Eio, and Moonpool implement an earlier\ninteroperability proposal called\n[domain-local-await](https://github.com/ocaml-multicore/domain-local-await/) or\nDLA, which allows a concurrent programming library like\n[Kcas](https://github.com/ocaml-multicore/kcas/)[\\*](https://github.com/ocaml-multicore/kcas/pull/136)\nto work on all of those. Unfortunately, DLA, by itself, is known to be\ninsufficient and the design has not been universally accepted.\n\nBy introducing a scheduler interface and key libraries, such as an IO library,\nimplemented on top of the interface, we hope that the scarce resources of the\nOCaml community are not further divided into mutually incompatible ecosystems\nbuilt on top of such mutually incompatible concurrent programming libraries,\nwhile, simultaneously, making it possible to experiment with many kinds of\nconcurrent programming models.\n\nIt should be\ntechnically\u003csup\u003e[\\*](https://www.youtube.com/watch?v=hou0lU8WMgo)\u003c/sup\u003e possible\nfor all the previously mentioned libraries, except\n[Miou](https://github.com/robur-coop/miou), to\n\n1. be made\n   [Picos compatible](https://ocaml-multicore.github.io/picos/doc/picos/index.html#picos-compatible),\n   i.e. to handle the Picos effects, and\n2. have their elements\n   [implemented in Picos](https://ocaml-multicore.github.io/picos/doc/picos/index.html#implemented-in-picos),\n   i.e. to make them usable on other Picos-compatible schedulers.\n\nPlease read\n[the reference manual](https://ocaml-multicore.github.io/picos/doc/index.html)\nfor further information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Focaml-multicore%2Fpicos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Focaml-multicore%2Fpicos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Focaml-multicore%2Fpicos/lists"}