package picos_std

  1. Overview
  2. Docs

Source file picos_std_finally.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
open Picos

type ('a, _) tdt =
  | Transferred : ('a, [> `Transferred ]) tdt
  | Borrowed : ('a, [> `Borrowed ]) tdt
  | Dropped : ('a, [> `Dropped ]) tdt
  | Resource : {
      mutable resource : 'a;
      release : 'a -> unit;
      transferred_or_dropped : Trigger.t;
    }
      -> ('a, [> `Resource ]) tdt

type 'a instance =
  ('a, [ `Transferred | `Borrowed | `Dropped | `Resource ]) tdt Atomic.t

(* *)

let[@inline never] error
    (case : (_, [< `Transferred | `Borrowed | `Dropped ]) tdt) =
  invalid_arg
    (match case with
    | Transferred -> "transferred"
    | Dropped -> "dropped"
    | Borrowed -> "borrowed")

let[@inline never] check_released () =
  (* In case of cancelation we do not consider being released an error as the
     resource was released by (the |an)other party involved in the [move]. *)
  Fiber.check (Fiber.current ());
  error Dropped

(* *)

let forbidden release x =
  match Fiber.current () with
  | fiber -> begin
      if Fiber.exchange fiber ~forbid:true then release x
      else
        match release x with
        | () -> Fiber.set fiber ~forbid:false
        | exception exn ->
            Fiber.set fiber ~forbid:false;
            raise exn
    end
  | exception _exn ->
      (* This should only happen when not running under a scheduler.  However,
         we don't match on a specific exception, because it depends on the OCaml
         version.

         We also do not reraise the exception! *)
      release x

let[@inline never] release_and_reraise exn x release =
  let bt = Printexc.get_raw_backtrace () in
  forbidden release x;
  Printexc.raise_with_backtrace exn bt

let[@inline never] release_and_return value x release =
  forbidden release x;
  value

(* *)

let rec drop instance =
  match Atomic.get instance with
  | Transferred | Dropped -> ()
  | Borrowed as case -> error case
  | Resource r as before ->
      if Atomic.compare_and_set instance before Dropped then begin
        forbidden r.release r.resource;
        Trigger.signal r.transferred_or_dropped
      end
      else drop instance

let[@inline never] drop_and_reraise_as bt instance exn =
  drop instance;
  Printexc.raise_with_backtrace exn bt

let[@inline never] drop_and_reraise exn instance =
  let bt = Printexc.get_raw_backtrace () in
  drop_and_reraise_as bt instance exn

(* *)

let await_transferred_or_dropped instance result =
  match Atomic.get instance with
  | Transferred | Dropped -> result
  | Borrowed as case ->
      (* This should be impossible as [let@ _ = borrow _ in _] should have
         restored the state. *)
      error case
  | Resource r -> begin
      match Trigger.await r.transferred_or_dropped with
      | None ->
          (* We release in case we could not wait. *)
          drop instance;
          result
      | Some (exn, bt) ->
          (* We have been canceled, so we release. *)
          drop_and_reraise_as bt instance exn
    end

let[@inline never] instantiate instance scope =
  match scope instance with
  | result -> await_transferred_or_dropped instance result
  | exception exn -> drop_and_reraise exn instance

let[@inline never] instantiate release acquire scope =
  let instance =
    Sys.opaque_identity
      begin
        let transferred_or_dropped = Trigger.create () in
        let state =
          Resource { resource = Obj.magic (); release; transferred_or_dropped }
        in
        Atomic.make state
      end
  in
  (* After this point there must be no allocations before [acquire ()]. *)
  let (Resource r : (_, [ `Resource ]) tdt) = Obj.magic (Atomic.get instance) in
  r.resource <- acquire ();
  instantiate instance scope

(* *)

let[@inline never] rec transfer from scope =
  match Atomic.get from with
  | (Transferred | Borrowed) as case -> error case
  | Dropped -> check_released ()
  | Resource r as before ->
      let into = Atomic.make Transferred in
      if Atomic.compare_and_set from before Transferred then begin
        Atomic.set into before;
        match
          Trigger.signal r.transferred_or_dropped;
          scope into
        with
        | result -> await_transferred_or_dropped into result
        | exception exn -> drop_and_reraise exn into
      end
      else transfer from scope

(* *)

let[@inline never] rec borrow instance scope =
  match Atomic.get instance with
  | (Transferred | Dropped | Borrowed) as case -> error case
  | Resource r as before ->
      if Atomic.compare_and_set instance before Borrowed then begin
        match scope r.resource with
        | result ->
            Atomic.set instance before;
            result
        | exception exn ->
            (* [Atomic.set] should not disturb the stack trace. *)
            Atomic.set instance before;
            raise exn
      end
      else borrow instance scope

(* *)

let[@inline never] rec move from scope =
  match Atomic.get from with
  | (Transferred | Borrowed) as case -> error case
  | Dropped -> check_released ()
  | Resource r as before ->
      if Atomic.compare_and_set from before Transferred then begin
        match
          Trigger.signal r.transferred_or_dropped;
          scope r.resource
        with
        | result ->
            forbidden r.release r.resource;
            result
        | exception exn -> release_and_reraise exn r.resource r.release
      end
      else move from scope

(* *)

let[@inline never] finally x scope release =
  match scope x with
  | y -> release_and_return y x release
  | exception exn -> release_and_reraise exn x release

let[@inline never] finally release acquire scope =
  let x = acquire () in
  finally x scope release

let[@inline never] lastly action scope =
  match scope () with
  | value -> release_and_return value () action
  | exception exn -> release_and_reraise exn () action

external ( let@ ) : ('a -> 'b) -> 'a -> 'b = "%apply"
OCaml

Innovation. Community. Security.