Library
Module
Module type
Parameter
Class
Class type
Syntax for avoiding resource leaks for Picos
.
A resource is something that is acquired and must be released after it is no longer needed.
We open both this library and a few other libraries
open Picos_io
open Picos_std_finally
open Picos_std_structured
open Picos_std_sync
for the examples.
let@ resource = template in scope
is equivalent to template (fun resource -> scope)
.
ℹ️ You can use this binding operator with any template
function that has a type of the form ('r -> 'a) -> 'a
.
finally release acquire scope
calls acquire ()
to obtain a resource
, calls scope resource
, and then calls release resource
after the scope exits.
Either contains a resource or is empty as the resource has been transferred, dropped, or has been borrowed temporarily.
val instantiate : ('r -> unit) -> (unit -> 'r) -> ('r instance -> 'a) -> 'a
val drop : 'r instance -> unit
drop instance
releases the resource, if any, contained by the instance
.
val borrow : 'r instance -> ('r -> 'a) -> 'a
borrow instance scope
borrows the resource
stored in the instance
, calls scope resource
, and then returns the resource
to the instance
after scope exits.
transfer source
transfers the resource
stored in the source
instance into a new target
instance, calls scope target
. Then, if scope
returns normally, awaits until the target
instance becomes empty. In case scope
raises an exception or the fiber is canceled, the target
instance will be dropped.
val move : 'r instance -> ('r -> 'a) -> 'a
move instance scope
is equivalent to transfer instance (fun instance -> borrow instance scope)
.
Here is a sketch of a server that recursively forks a fiber to accept and handle a client:
let recursive_server server_fd =
Flock.join_after @@ fun () ->
(* recursive server *)
let rec accept () =
let@ client_fd =
finally Unix.close @@ fun () ->
Unix.accept ~cloexec:true server_fd
|> fst
in
(* fork to accept other clients *)
Flock.fork accept;
(* handle this client... omitted *)
()
in
Flock.fork accept
There is also a way to move instantiated resources to allow forking fibers to handle clients without leaks.
Here is a sketch of a server that accepts in a loop and forks fibers to handle clients:
let looping_server server_fd =
Flock.join_after @@ fun () ->
(* loop to accept clients *)
while true do
let@ client_fd =
instantiate Unix.close @@ fun () ->
Unix.accept ~cloexec:true server_fd
|> fst
in
(* fork to handle this client *)
Flock.fork @@ fun () ->
let@ client_fd = move client_fd in
(* handle client... omitted *)
()
done
You can move an instantiated resource between any two fibers and borrow it before moving it. For example, you can create a resource in a child fiber, use it there, and then move it to the parent fiber:
let move_from_child_to_parent () =
Flock.join_after @@ fun () ->
(* for communicating a resource *)
let shared_ivar = Ivar.create () in
(* fork a child that creates a resource *)
Flock.fork begin fun () ->
let pretend_release () = ()
and pretend_acquire () = () in
(* allocate a resource *)
let@ instance =
instantiate pretend_release pretend_acquire
in
begin
(* borrow the resource *)
let@ resource = borrow instance in
(* use the resource... omitted *)
()
end;
(* send the resource to the parent *)
Ivar.fill shared_ivar instance
end;
(* await for a resource from the child and own it *)
let@ resource = Ivar.read shared_ivar |> move in
(* use the resource... omitted *)
()
The above uses an Ivar
to communicate the movable resource from the child fiber to the parent fiber. Any concurrency safe mechanism could be used.