Skip to content

Commit 7cf277e

Browse files
committed
Make Picos_select explicitly configurable
1 parent e876a87 commit 7cf277e

File tree

11 files changed

+119
-33
lines changed

11 files changed

+119
-33
lines changed

CHANGES.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
## Next version
22

3+
- Added `Picos_select.configure` to allow, and sometimes require, configuring
4+
`Picos_select` for co-operation with libraries that also deal with signals
5+
(@polytypic)
36
- Moved `Picos_tls` into `Picos_thread.TLS` (@polytypic)
47
- Enhanced `sleep` and `sleepf` in `Picos_stdio.Unix` to block in a scheduler
5-
friendly manner (@polytypic).
8+
friendly manner (@polytypic)
69

710
## 0.1.0
811

lib/picos_fifos/picos_fifos.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ let rec next t =
104104
end
105105

106106
let run ~forbid main =
107+
Select.check_configured ();
107108
let ready = Queue.create ()
108109
and needs_wakeup = Atomic.make false
109110
and num_alive_fibers = Atomic.make 1

lib/picos_fifos/select.none.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
let cancel_after _ ~seconds:_ _ =
22
raise (Sys_error "Computation: cancel_after unavailable")
3+
4+
let check_configured = Fun.id

lib/picos_fifos/select.some.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
let cancel_after = Picos_select.cancel_after
2+
let check_configured = Picos_select.check_configured

lib/picos_select/picos_select.ml

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
open Picos
22

3-
let intr_sig = Sys.sigusr2
4-
let is_intr_sig s = s == intr_sig
5-
let intr_sigs = [ intr_sig ]
3+
type config = { mutable intr_sig : int; mutable intr_sigs : int list }
4+
5+
let config = { intr_sig = 0; intr_sigs = [] }
6+
7+
(* *)
68

79
type cancel_at =
810
| Cancel_at : {
@@ -65,6 +67,15 @@ type counter_state = { value : int; req : req }
6567
let intr_pending = Atomic.make { value = 0; req = R Nothing }
6668
let exit_exn_bt = Exn_bt.get_callstack 0 Exit
6769

70+
let cleared =
71+
let computation = Computation.create () in
72+
Computation.return computation Cleared;
73+
computation
74+
75+
let intr_key =
76+
Picos_thread.TLS.new_key @@ fun () : [ `Req ] tdt ->
77+
invalid_arg "Picos_select has not been configured"
78+
6879
let key =
6980
Picos_domain.DLS.new_key @@ fun () ->
7081
{
@@ -198,15 +209,17 @@ and select_thread_continue s rd wr ex (rd_fds, wr_fds, ex_fds) =
198209
if state.value = 0 then tos
199210
else begin
200211
assert (0 < state.value);
201-
Unix.kill (Unix.getpid ()) intr_sig;
212+
Unix.kill (Unix.getpid ()) config.intr_sig;
202213
let idle = 0.000_001 (* 1μs *) in
203214
if tos < 0.0 || idle <= tos then idle else tos
204215
end
205216
in
206217
select_thread s tos rd wr ex
207218

208219
let select_thread s =
209-
if not Sys.win32 then Thread.sigmask SIG_BLOCK intr_sigs |> ignore;
220+
if not Sys.win32 then begin
221+
Thread.sigmask SIG_BLOCK config.intr_sigs |> ignore
222+
end;
210223
begin
211224
try
212225
let pipe_inn, pipe_out = Unix.pipe ~cloexec:true () in
@@ -220,7 +233,46 @@ let select_thread s =
220233
if s.pipe_inn != Unix.stdin then Unix.close s.pipe_inn;
221234
if s.pipe_out != Unix.stdin then Unix.close s.pipe_out
222235

236+
let[@poll error] [@inline never] try_configure ~intr_sig ~intr_sigs =
237+
config.intr_sigs == []
238+
&& begin
239+
config.intr_sig <- intr_sig;
240+
config.intr_sigs <- intr_sigs;
241+
true
242+
end
243+
244+
let is_intr_sig signum = signum = config.intr_sig
245+
246+
let rec configure ?(intr_sig = Sys.sigusr2) () =
247+
if not (Picos_thread.is_main_thread ()) then
248+
invalid_arg
249+
"Picos_select must be configured from the main thread on the main domain";
250+
assert (Sys.sigabrt = -1 && Sys.sigxfsz < Sys.sigabrt);
251+
if intr_sig < Sys.sigxfsz || 0 <= intr_sig || intr_sig = Sys.sigchld then
252+
invalid_arg "Invalid interrupt signal number";
253+
if not (try_configure ~intr_sig ~intr_sigs:[ intr_sig ]) then
254+
invalid_arg "Picos_select.configure already configured";
255+
256+
if not Sys.win32 then begin
257+
begin
258+
let previously_blocked = Thread.sigmask SIG_BLOCK config.intr_sigs in
259+
assert (not (List.exists is_intr_sig previously_blocked));
260+
let old_behavior =
261+
Sys.signal config.intr_sig (Sys.Signal_handle handle_signal)
262+
in
263+
assert (old_behavior == Signal_default)
264+
end
265+
end
266+
267+
and handle_signal signal =
268+
if signal = config.intr_sig then
269+
let (Req r) = Picos_thread.TLS.get intr_key in
270+
Computation.return r.computation Signaled
271+
272+
let check_configured () = if config.intr_sigs = [] then configure ()
273+
223274
let[@inline never] init s =
275+
check_configured ();
224276
if try_transition s `Initial `Starting then begin
225277
match Thread.create select_thread s with
226278
| thread ->
@@ -319,21 +371,8 @@ let await_on file_descr op =
319371
module Intr = struct
320372
type t = req
321373

322-
let cleared =
323-
let computation = Computation.create () in
324-
Computation.return computation Cleared;
325-
computation
326-
327-
let intr_key =
328-
Picos_thread.TLS.new_key @@ fun () : [ `Req ] tdt ->
329-
Req { state = get (); unused = false; computation = cleared }
330-
331374
let[@inline] use = function R Nothing -> () | R (Req r) -> r.unused <- false
332375

333-
let handle _ =
334-
let (Req r) = Picos_thread.TLS.get intr_key in
335-
Computation.return r.computation Signaled
336-
337376
(** This is used to ensure that the [intr_pending] counter is incremented
338377
exactly once before the counter is decremented. *)
339378
let rec incr_once (Req r as req : [ `Req ] tdt) backoff =
@@ -366,14 +405,6 @@ module Intr = struct
366405
let _ : bool = incr_once req Backoff.default in
367406
()
368407

369-
let () =
370-
if not Sys.win32 then begin
371-
let previously_blocked = Thread.sigmask SIG_BLOCK intr_sigs in
372-
assert (not (List.exists is_intr_sig previously_blocked));
373-
let old_behavior = Sys.signal intr_sig (Sys.Signal_handle handle) in
374-
assert (old_behavior == Signal_default)
375-
end
376-
377408
let nothing = R Nothing
378409

379410
let[@alert "-handler"] req ~seconds =
@@ -392,8 +423,10 @@ module Intr = struct
392423
Picos_thread.TLS.set intr_key req;
393424
let entry = Cancel_at { time; exn_bt = exit_exn_bt; computation } in
394425
add_timeout state id entry;
395-
let _was_blocked : int list = Thread.sigmask SIG_UNBLOCK intr_sigs in
396-
(* assert (List.exists is_intr_sig was_blocked); *)
426+
let was_blocked : int list =
427+
Thread.sigmask SIG_UNBLOCK config.intr_sigs
428+
in
429+
assert (List.exists is_intr_sig was_blocked);
397430
R req
398431
end
399432

@@ -408,8 +441,10 @@ module Intr = struct
408441
let clr = function
409442
| R Nothing -> ()
410443
| R (Req r as req) ->
411-
let _was_blocked : int list = Thread.sigmask SIG_BLOCK intr_sigs in
412-
(* assert (not (List.exists is_intr_sig was_blocked)); *)
444+
let was_blocked : int list =
445+
Thread.sigmask SIG_BLOCK config.intr_sigs
446+
in
447+
assert (not (List.exists is_intr_sig was_blocked));
413448
if not (Computation.try_return r.computation Cleared) then begin
414449
let _ : bool = incr_once req Backoff.default in
415450
(* We ensure that the associated increment has been done before we

lib/picos_select/picos_select.mli

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
The operations in this module automatically manage a {!Thread} per domain
44
that runs a {!Unix.select} loop to support the operations.
55
6+
⚠️ Signal handlers are unfortunately fundamentally non-compositional. The
7+
use of signal handlers in this module has been designed to be {{!configure}
8+
configurable}, which should allow co-operating with other libraries using
9+
signals as long as care is taken at application startup to {!configure}
10+
things.
11+
612
⚠️ All the usual limitations of the {!Unix} module apply. *)
713

814
open Picos
@@ -35,8 +41,8 @@ val await_on : Picos_fd.t -> [ `R | `W | `E ] -> Picos_fd.t
3541
module Intr : sig
3642
(** A mechanism to interrupt blocking {!Unix} IO operations.
3743
38-
⚠️ The mechanism uses the {!Sys.sigusr2} signal which should not be used
39-
for other purposes at the same time.
44+
⚠️ The mechanism uses {{!configure} a signal} which should not be used for
45+
other purposes.
4046
4147
⚠️ Beware that signal handling in OCaml 5.0.0 is known to be broken and
4248
several fixes were included in OCaml {{:https://ocaml.org/releases/5.1.0}
@@ -63,3 +69,32 @@ module Intr : sig
6369
(** [clr req] either cancels or acknowledges the interrupt request. Every
6470
{!req} must be cleared exactly once! *)
6571
end
72+
73+
(** {1 Configuration} *)
74+
75+
val configure : ?intr_sig:int -> unit -> unit
76+
(** [configure ~intr_sig ()] can, and sometimes must, be called by an
77+
application to configure the use of signals by this module.
78+
79+
The optional [intr_sig] argument can be used to specify the signal used by
80+
the {{!Intr} interrupt} mechanism. The default is to use {!Sys.sigusr2}.
81+
82+
⚠️ This module must always be configured before use. Unless this module has
83+
been explicitly configured, calling a method of this module from the main
84+
thread on the main domain will automatically configure this module with
85+
default options. In case the application uses multiple threads or multiple
86+
domains, the application should arrange to call [configure] from the main
87+
thread on the main domain before any threads or domains besides the main are
88+
created or spawned. *)
89+
90+
val handle_signal : int -> unit
91+
(** [handle_signal signum] should be called to notify this module of a signal
92+
when {{!configure} configured} to not handle said signals. *)
93+
94+
val check_configured : unit -> unit
95+
(** [check_configured ()] checks whether this module has already been
96+
{{!configure} configured} or not and, if not, calls {!configure} with
97+
default arguments.
98+
99+
ℹ️ The intended use case for [check_configure ()] is at the point of
100+
entry of schedulers and other facilities that use this module. *)

lib/picos_stdio/picos_stdio.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ module Unix : sig
2929
3030
also block in a scheduler friendly manner.
3131
32+
⚠️ This module uses {!Picos_select} and you may need to
33+
{{!Picos_select.configure} configure} it at start of your application.
34+
3235
Please consult the documentation of the {{!Deps.Unix} [Unix]} module that
3336
comes with OCaml. *)
3437

lib/picos_threaded/picos_threaded.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,5 @@ and spawn : type a. _ -> forbid:bool -> a Computation.t -> _ =
8383
and handler = Handler.{ current; spawn; yield; cancel_after; await }
8484

8585
let run ~forbid main =
86+
Select.check_configured ();
8687
Handler.using handler (create ~forbid (Computation.create ())) main

lib/picos_threaded/select.none.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
let cancel_after _ ~seconds:_ _ =
22
raise (Sys_error "Computation: cancel_after unavailable")
3+
4+
let check_configured = Fun.id

lib/picos_threaded/select.some.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
let cancel_after = Picos_select.cancel_after
2+
let check_configured = Picos_select.check_configured

0 commit comments

Comments
 (0)