fix: mutex usage and inline bugs

This commit is contained in:
ajbt200128 2025-11-12 14:27:36 -08:00
parent 507bf25dcf
commit 3fbac32822
3 changed files with 43 additions and 29 deletions

View file

@ -7,6 +7,19 @@ type 'a t = {
exception Closed exception Closed
(* Mutex.protect was added in OCaml 5.1, but we want support back to 4.08 *)
(* cannot inline, otherwise flambda might move code around. (as per Stdlib) *)
let[@inline never] protect m f =
Mutex.lock m;
match f () with
| x ->
Mutex.unlock m;
x
| exception e ->
(* NOTE: [unlock] does not poll for asynchronous exceptions *)
Mutex.unlock m;
Printexc.raise_with_backtrace e (Printexc.get_raw_backtrace ())
let create () : _ t = let create () : _ t =
{ {
mutex = Mutex.create (); mutex = Mutex.create ();
@ -16,54 +29,42 @@ let create () : _ t =
} }
let close (self : _ t) = let close (self : _ t) =
Mutex.lock self.mutex; protect self.mutex @@ fun () ->
if not self.closed then ( if not self.closed then (
self.closed <- true; self.closed <- true;
Condition.broadcast self.cond (* awake waiters so they fail *) Condition.broadcast self.cond (* awake waiters so they fail *)
); )
Mutex.unlock self.mutex
let push (self : _ t) x : unit = let push (self : _ t) x : unit =
Mutex.lock self.mutex; protect self.mutex @@ fun () ->
if self.closed then ( if self.closed then
Mutex.unlock self.mutex;
raise Closed raise Closed
) else ( else (
Queue.push x self.q; Queue.push x self.q;
Condition.signal self.cond; Condition.signal self.cond
Mutex.unlock self.mutex
) )
let pop (self : 'a t) : 'a = let pop (self : 'a t) : 'a =
Mutex.lock self.mutex;
let rec loop () = let rec loop () =
if self.closed then ( if self.closed then
Mutex.unlock self.mutex;
raise Closed raise Closed
) else if Queue.is_empty self.q then ( else if Queue.is_empty self.q then (
Condition.wait self.cond self.mutex; Condition.wait self.cond self.mutex;
(loop [@tailcall]) () (loop [@tailcall]) ()
) else ( ) else (
let x = Queue.pop self.q in let x = Queue.pop self.q in
Mutex.unlock self.mutex;
x x
) )
in in
loop () protect self.mutex loop
let pop_all (self : 'a t) into : unit = let pop_all (self : 'a t) into : unit =
Mutex.lock self.mutex;
let rec loop () = let rec loop () =
if Queue.is_empty self.q then ( if Queue.is_empty self.q then (
if self.closed then ( if self.closed then raise Closed;
Mutex.unlock self.mutex;
raise Closed
);
Condition.wait self.cond self.mutex; Condition.wait self.cond self.mutex;
(loop [@tailcall]) () (loop [@tailcall]) ()
) else ( ) else
Queue.transfer self.q into; Queue.transfer self.q into
Mutex.unlock self.mutex
)
in in
loop () protect self.mutex loop

View file

@ -9,11 +9,18 @@ type 'a t = {
mutex: Mutex.t; mutex: Mutex.t;
} }
(* Mutex.protect was added in OCaml 5.1, but we want support back to 4.08. (* Mutex.protect was added in OCaml 5.1, but we want support back to 4.08 *)
cannot inline, otherwise flambda might move code around. (as per Stdlib) *) (* cannot inline, otherwise flambda might move code around. (as per Stdlib) *)
let[@inline never] protect_mutex m f = let[@inline never] protect_mutex m f =
Mutex.lock m; Mutex.lock m;
Fun.protect f ~finally:(fun () -> Mutex.unlock m) match f () with
| x ->
Mutex.unlock m;
x
| exception e ->
(* NOTE: [unlock] does not poll for asynchronous exceptions *)
Mutex.unlock m;
Printexc.raise_with_backtrace e (Printexc.get_raw_backtrace ())
let default_high_watermark batch_size = let default_high_watermark batch_size =
if batch_size = 1 then if batch_size = 1 then

View file

@ -8,4 +8,10 @@ let set_mutex ~lock ~unlock : unit =
let[@inline] with_lock f = let[@inline] with_lock f =
!lock_ (); !lock_ ();
Fun.protect ~finally:!unlock_ f match f () with
| x ->
!unlock_ ();
x
| exception e ->
!unlock_ ();
Printexc.raise_with_backtrace e (Printexc.get_raw_backtrace ())