moonpool/picos/Picos/Trigger/index.html
2024-12-04 16:11:59 +00:00

18 lines
21 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Trigger (picos.Picos.Trigger)</title><meta charset="utf-8"/><link rel="stylesheet" href="../../../_odoc-theme/odoc.css"/><meta name="generator" content="odoc 2.4.3"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><script src="../../../highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script></head><body class="odoc"><nav class="odoc-nav"><a href="../index.html">Up</a> <a href="../../index.html">picos</a> &#x00BB; <a href="../index.html">Picos</a> &#x00BB; Trigger</nav><header class="odoc-preamble"><h1>Module <code><span>Picos.Trigger</span></code></h1><p>Ability to await for a signal.</p><p>To suspend and later resume the current thread of execution, one can <a href="#val-create"><code>create</code></a> a trigger, arrange <a href="#val-signal"><code>signal</code></a> to be called on it, and <a href="#val-await"><code>await</code></a> for the call.</p><p>Here is a simple example:</p><pre class="language-ocaml"><code>run begin fun () -&gt;
Flock.join_after @@ fun () -&gt;
let trigger = Trigger.create () in
Flock.fork begin fun () -&gt;
Trigger.signal trigger
end;
match Trigger.await trigger with
| None -&gt;
(* We were resumed normally. *)
()
| Some (exn, bt) -&gt;
(* We were canceled. *)
Printexc.raise_with_backtrace exn bt
end</code></pre><p>⚠️ Typically we need to cleanup after <a href="#val-await"><code>await</code></a>, but in the above example we didn't insert the trigger into any data structure nor did we <a href="../Computation/index.html#val-try_attach" title="Computation.try_attach">attach</a> the trigger to any computation.</p><p>All operations on triggers are wait-free, with the obvious exception of <a href="#val-await"><code>await</code></a>. The <a href="#val-signal"><code>signal</code></a> operation inherits the properties of the action attached with <a href="#val-on_signal"><code>on_signal</code></a> to the trigger.</p></header><nav class="odoc-toc"><ul><li><a href="#interface-for-suspending">Interface for suspending</a></li><li><a href="#interface-for-resuming">Interface for resuming</a></li><li><a href="#interface-for-schedulers">Interface for schedulers</a></li><li><a href="#design-rationale">Design rationale</a></li></ul></nav><div class="odoc-content"><h3 id="interface-for-suspending"><a href="#interface-for-suspending" class="anchor"></a>Interface for suspending</h3><div class="odoc-spec"><div class="spec type anchored" id="type-t"><a href="#type-t" class="anchor"></a><code><span><span class="keyword">type</span> t</span></code></div><div class="spec-doc"><p>Represents a trigger. A trigger can be in one of three states: <i>initial</i>, <i>awaiting</i>, or <i>signaled</i>.</p><p> Once a trigger becomes signaled it no longer changes state.</p><p>🏎️ A trigger in the initial and signaled states is a tiny object that does not hold onto any other objects.</p></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-create"><a href="#val-create" class="anchor"></a><code><span><span class="keyword">val</span> create : <span>unit <span class="arrow">&#45;&gt;</span></span> <a href="#type-t">t</a></span></code></div><div class="spec-doc"><p><code>create ()</code> allocates a new trigger in the initial state.</p></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-is_signaled"><a href="#val-is_signaled" class="anchor"></a><code><span><span class="keyword">val</span> is_signaled : <span><a href="#type-t">t</a> <span class="arrow">&#45;&gt;</span></span> bool</span></code></div><div class="spec-doc"><p><code>is_signaled trigger</code> determines whether the trigger is in the signaled state.</p><p>This can be useful, for example, when a <code>trigger</code> is being inserted to multiple locations and might be signaled concurrently while doing so. In such a case one can periodically check with <code>is_signaled trigger</code> whether it makes sense to continue.</p><p> <a href="../Computation/index.html#val-try_attach"><code>Computation.try_attach</code></a> already checks that the trigger being inserted has not been signaled so when attaching a trigger to multiple computations there is no need to separately check with <code>is_signaled</code>.</p></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-await"><a href="#val-await" class="anchor"></a><code><span><span class="keyword">val</span> await : <span><a href="#type-t">t</a> <span class="arrow">&#45;&gt;</span></span> <span><span>(exn * <a href="../../../ocaml/Stdlib/Printexc/index.html#type-raw_backtrace">Stdlib.Printexc.raw_backtrace</a>)</span> option</span></span></code></div><div class="spec-doc"><p><code>await trigger</code> waits for the trigger to be <a href="#val-signal"><code>signal</code></a>ed.</p><p>The return value is <code>None</code> in case the trigger has been signaled and the <a href="../Fiber/index.html" title="Fiber">fiber</a> was resumed normally. Otherwise the return value is <code>Some (exn, bt)</code>, which indicates that the fiber has been canceled and the caller should raise the exception. In either case the caller is responsible for cleaning up. Usually this means making sure that no references to the trigger remain to avoid space leaks.</p><p>⚠️ As a rule of thumb, if you inserted the trigger to some data structure or <a href="../Computation/index.html#val-try_attach" title="Computation.try_attach">attached</a> it to some computation, then you are responsible for removing and <a href="../Computation/index.html#val-detach" title="Computation.detach">detaching</a> the trigger after <code>await</code>.</p><p> A trigger in the signaled state only takes a small constant amount of memory. Make sure that it is not possible for a program to accumulate unbounded numbers of signaled triggers under any circumstance.</p><p>⚠️ Only the owner or creator of a trigger may call <code>await</code>. It is considered an error to make multiple calls to <code>await</code>.</p><p> The behavior is that, <i>unless <code>await</code> can return immediately</i>,</p><ul><li>on OCaml 5, <code>await</code> will perform the <a href="#extension-Await"><code>Await</code></a> effect, and</li><li>on OCaml 4, <code>await</code> will call the <code>await</code> operation of the <a href="../Handler/index.html" title="Handler">current handler</a>.</li></ul><ul class="at-tags"><li class="raises"><span class="at-tag">raises</span> <code>Invalid_argument</code> <p>if the trigger was in the awaiting state, which means that multiple concurrent calls of <code>await</code> are being made.</p></li></ul></div></div><h3 id="interface-for-resuming"><a href="#interface-for-resuming" class="anchor"></a>Interface for resuming</h3><div class="odoc-spec"><div class="spec value anchored" id="val-signal"><a href="#val-signal" class="anchor"></a><code><span><span class="keyword">val</span> signal : <span><a href="#type-t">t</a> <span class="arrow">&#45;&gt;</span></span> unit</span></code></div><div class="spec-doc"><p><code>signal trigger</code> puts the <code>trigger</code> into the signaled state and calls the resume action, if any, attached using <a href="#val-on_signal"><code>on_signal</code></a>.</p><p>The intention is that calling <code>signal trigger</code> guarantees that any fiber <a href="#val-await" title="await">awaiting</a> the <code>trigger</code> will be resumed. However, when and whether a fiber having called <a href="#val-await"><code>await</code></a> will be resumed normally or as canceled is determined by the scheduler that handles the <a href="#extension-Await"><code>Await</code></a> effect.</p><p> Note that under normal circumstances, <code>signal</code> should never raise an exception. If an exception is raised by <code>signal</code>, it means that the handler of <a href="#extension-Await"><code>Await</code></a> has a bug or some catastrophic failure has occurred.</p><p>⚠️ Do not call <code>signal</code> from an effect handler in a scheduler.</p></div></div><h3 id="interface-for-schedulers"><a href="#interface-for-schedulers" class="anchor"></a>Interface for schedulers</h3><p>⚠️ The operations in this section are for more advanced use cases and their use requires a deeper understanding of how schedulers work.</p><div class="odoc-spec"><div class="spec value anchored" id="val-is_initial"><a href="#val-is_initial" class="anchor"></a><code><span><span class="keyword">val</span> is_initial : <span><a href="#type-t">t</a> <span class="arrow">&#45;&gt;</span></span> bool</span></code></div><div class="spec-doc"><p><code>is_initial trigger</code> determines whether the trigger is in the initial or in the signaled state.</p><p> Consider using <a href="#val-is_signaled"><code>is_signaled</code></a> instead of <code>is_initial</code> as in some contexts a trigger might reasonably be either in the initial or the awaiting state depending on the order in which things are being done.</p><ul class="at-tags"><li class="raises"><span class="at-tag">raises</span> <code>Invalid_argument</code> <p>if the trigger was in the awaiting state.</p></li></ul></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-on_signal"><a href="#val-on_signal" class="anchor"></a><code><span><span class="keyword">val</span> on_signal : <span><a href="#type-t">t</a> <span class="arrow">&#45;&gt;</span></span> <span><span class="type-var">'x</span> <span class="arrow">&#45;&gt;</span></span> <span><span class="type-var">'y</span> <span class="arrow">&#45;&gt;</span></span> <span><span>(<span><a href="#type-t">t</a> <span class="arrow">&#45;&gt;</span></span> <span><span class="type-var">'x</span> <span class="arrow">&#45;&gt;</span></span> <span><span class="type-var">'y</span> <span class="arrow">&#45;&gt;</span></span> unit)</span> <span class="arrow">&#45;&gt;</span></span> bool</span></code></div><div class="spec-doc"><p><code>on_signal trigger x y resume</code> attempts to attach the <code>resume</code> action to the <code>trigger</code> and transition the trigger to the awaiting state.</p><p>The return value is <code>true</code> in case the action was attached successfully. Otherwise the return value is <code>false</code>, which means that the trigger was already in the signaled state.</p><p>⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through <a href="../Computation/index.html#val-canceler" title="Computation.canceler">propagation</a>. Unless you know, then you should assume that the <code>resume</code> action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.</p><p>⚠️ It is considered an error to make multiple calls to <code>on_signal</code> with a specific <code>trigger</code>.</p><ul class="at-tags"><li class="raises"><span class="at-tag">raises</span> <code>Invalid_argument</code> <p>if the trigger was in the awaiting state, which means that either the owner or creator of the trigger made concurrent calls to <a href="#val-await"><code>await</code></a> or the handler called <code>on_signal</code> more than once.</p></li></ul><ul class="at-tags"><li class="alert"><span class="at-tag">alert</span> handler Only a scheduler should call this in the handler of the Await effect to attach the scheduler specific resume action to the trigger. Annotate your effect handling function with [@alert &quot;-handler&quot;].</li></ul></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-from_action"><a href="#val-from_action" class="anchor"></a><code><span><span class="keyword">val</span> from_action : <span><span class="type-var">'x</span> <span class="arrow">&#45;&gt;</span></span> <span><span class="type-var">'y</span> <span class="arrow">&#45;&gt;</span></span> <span><span>(<span><a href="#type-t">t</a> <span class="arrow">&#45;&gt;</span></span> <span><span class="type-var">'x</span> <span class="arrow">&#45;&gt;</span></span> <span><span class="type-var">'y</span> <span class="arrow">&#45;&gt;</span></span> unit)</span> <span class="arrow">&#45;&gt;</span></span> <a href="#type-t">t</a></span></code></div><div class="spec-doc"><p><code>from_action x y resume</code> is equivalent to <a href="#val-on_signal" title="on_signal"><code>let t = create () in assert (on_signal t x y resume); t</code></a>.</p><p> This can useful when you just want to have an arbitrary callback executed when a trigger you attach to a <a href="../Computation/index.html" title="Computation">computation</a> is signaled.</p><p>⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through <a href="../Computation/index.html#val-canceler" title="Computation.canceler">propagation</a>. Unless you know, then you should assume that the <code>resume</code> action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.</p><p>⚠️ The returned trigger will be in the awaiting state, which means that it is an error to call <a href="#val-await"><code>await</code></a>, <a href="#val-on_signal"><code>on_signal</code></a>, or <a href="#val-dispose"><code>dispose</code></a> on it.</p><ul class="at-tags"><li class="alert"><span class="at-tag">alert</span> handler This is an escape hatch for experts implementing schedulers or structured concurrency mechanisms. If you know what you are doing, use [@alert &quot;-handler&quot;].</li></ul></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-dispose"><a href="#val-dispose" class="anchor"></a><code><span><span class="keyword">val</span> dispose : <span><a href="#type-t">t</a> <span class="arrow">&#45;&gt;</span></span> unit</span></code></div><div class="spec-doc"><p><code>dispose trigger</code> transition the <code>trigger</code> from the initial state to the signaled state.</p><p>🚦 The intended use case of <code>dispose</code> is for use from the handler of <a href="#extension-Await"><code>Await</code></a> to ensure that the trigger has been put to the signaled state after <a href="#val-await"><code>await</code></a> returns.</p><ul class="at-tags"><li class="raises"><span class="at-tag">raises</span> <code>Invalid_argument</code> <p>if the trigger was in the awaiting state.</p></li></ul></div></div><div class="odoc-spec"><div class="spec type extension anchored" id="extension-decl-Await"><a href="#extension-decl-Await" class="anchor"></a><code><span><span class="keyword">type</span> <a href="../../../ocaml/Stdlib/Effect/index.html#type-t">Stdlib.Effect.t</a> += <span class="keyword">private</span> </span></code><ol><li id="extension-Await" class="def variant extension anchored"><a href="#extension-Await" class="anchor"></a><code><span>| </span><span><span class="extension">Await</span> : <a href="#type-t">t</a> <span class="arrow">&#45;&gt;</span> <span><span><span>(exn * <a href="../../../ocaml/Stdlib/Printexc/index.html#type-raw_backtrace">Stdlib.Printexc.raw_backtrace</a>)</span> option</span> <a href="../../../ocaml/Stdlib/Effect/index.html#type-t">Stdlib.Effect.t</a></span></span></code></li></ol></div><div class="spec-doc"><p>Schedulers must handle the <a href="#extension-Await"><code>Await</code></a> effect to implement the behavior of <a href="#val-await"><code>await</code></a>.</p><p>In case the fiber permits propagation of cancelation, the trigger must be attached to the computation of the fiber for the duration of suspending the fiber by the scheduler.</p><p>Typically the scheduler calls <a href="../Fiber/index.html#val-try_suspend" title="Fiber.try_suspend"><code>try_suspend</code></a>, which in turn calls <a href="#val-on_signal"><code>on_signal</code></a>, to attach a scheduler specific <code>resume</code> action to the <code>trigger</code>. The scheduler must guarantee that the fiber will be resumed after <a href="#val-signal"><code>signal</code></a> has been called on the <code>trigger</code>.</p><p>Whether being resumed due to cancelation or not, the trigger must be either <a href="#val-signal" title="signal">signaled</a> outside of the effect handler, or <a href="#val-dispose" title="dispose">disposed</a> by the effect handler, before resuming the fiber.</p><p>In case the fiber permits propagation of cancelation and the computation associated with the fiber has been canceled the scheduler is free to continue the fiber immediately with the cancelation exception after <a href="#val-dispose" title="dispose">disposing</a> the trigger.</p><p>⚠️ A scheduler must not discontinue, i.e. raise an exception to, the fiber as a response to <a href="#extension-Await"><code>Await</code></a>.</p><p>The scheduler is free to choose which ready fiber to resume next.</p></div></div><h3 id="design-rationale"><a href="#design-rationale" class="anchor"></a>Design rationale</h3><p>A key idea behind this design is that the handler for <a href="#extension-Await"><code>Await</code></a> does not need to run arbitrary user defined code while suspending a fiber: the handler calls <a href="#val-on_signal"><code>on_signal</code></a> by itself. This should make it easier to get both the handler and the user code correct.</p><p>Another key idea is that the <a href="#val-signal"><code>signal</code></a> operation provides no feedback as to the outcome regarding cancelation. Calling <a href="#val-signal"><code>signal</code></a> merely guarantees that the caller of <a href="#val-await"><code>await</code></a> will return. This means that the point at which cancelation must be determined can be as late as possible. A scheduler can check the cancelation status just before calling <code>continue</code> and it is, of course, possible to check the cancelation status earlier. This allows maximal flexibility for the handler of <a href="#extension-Await"><code>Await</code></a>.</p><p>The consequence of this is that the only place to handle cancelation is at the point of <a href="#val-await"><code>await</code></a>. This makes the design simpler and should make it easier for the user to get the handling of cancelation right. A minor detail is that <a href="#val-await"><code>await</code></a> returns an option instead of raising an exception. The reason for this is that matching against an option is slightly faster than setting up an exception handler. Returning an option also clearly communicates the two different cases to handle.</p><p>On the other hand, the trigger mechanism does not have a way to specify a user-defined callback to perform cancelation immediately before the fiber is resumed. Such an immediately called callback could be useful for e.g. canceling an underlying IO request. One justification for not having such a callback is that cancelation is allowed to take place from outside of the scheduler, i.e. from another system level thread, and, in such a case, the callback could not be called immediately. Instead, the scheduler is free to choose how to schedule canceled and continued fibers and, assuming that fibers can be trusted, a scheduler may give priority to canceled fibers.</p><p>This design also separates the allocation of the atomic state for the trigger, or <a href="#val-create"><code>create</code></a>, from <a href="#val-await"><code>await</code></a>, and allows the state to be polled using <a href="#val-is_signaled"><code>is_signaled</code></a> before calling <a href="#val-await"><code>await</code></a>. This is particularly useful when the trigger might need to be inserted to multiple places and be <a href="#val-signal"><code>signal</code></a>ed in parallel before the call of <a href="#val-await"><code>await</code></a>.</p><p>No mechanism is provided to communicate any result with the signal. That can be done outside of the mechanism and is often not needed. This simplifies the design.</p><p>Once <a href="#val-signal"><code>signal</code></a> has been called, a trigger no longer refers to any other object and takes just two words of memory. This e.g. allows lazy removal of triggers, assuming the number of attached triggers can be bounded, because nothing except the trigger itself would be leaked.</p><p>To further understand the problem domain, in this design, in a suspend-resume scenario, there are three distinct pieces of state:</p><ol><li>The state of shared data structure(s) used for communication and / or synchronization.</li><li>The state of the trigger.</li><li>The cancelation status of the fiber.</li></ol><p>The trigger and cancelation status are both updated independently and atomically through code in this interface. The key requirement left for the user is to make sure that the state of the shared data structure is updated correctly independently of what <a href="#val-await"><code>await</code></a> returns. So, for example, a mutex implementation must check, after getting <code>Some (exn, bt)</code>, what the state of the mutex is and how it should be updated.</p></div></body></html>