package opentelemetry-client
Source file bounded_queue.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(** Interface for a thread-safe, bounded queue. After the high watermark is reached, pushing items into the queue will instead discard them. *) exception Closed (** Raised when pushing into a closed queue *) type 'a pop_result = [ `Empty | `Closed | `Item of 'a ] module Common = struct type t = { closed: unit -> bool; (** Is the queue closed {b for writing}. Consumers should only use [try_pop] because a queue that's closed-for-writing might still contain straggler items that need to be consumed. This should be as fast and cheap as possible. *) num_discarded: unit -> int; (** How many items were discarded? *) size: unit -> int; (** Snapshot of how many items are currently in the queue *) high_watermark: unit -> int; (** Maximum size of the queue *) } let[@inline] num_discarded self = self.num_discarded () let[@inline] closed (self : t) : bool = self.closed () let[@inline] size (self : t) : int = self.size () let[@inline] high_watermark self = self.high_watermark () end (** Receiving side *) module Recv = struct type 'a t = { on_non_empty: (unit -> unit) -> unit; (** [on_non_empty f] registers [f] to be called whenever the queue transitions from empty to non-empty. *) try_pop: unit -> 'a pop_result; (** Try to pop an item right now. *) common: Common.t; } let[@inline] try_pop (self : _ t) : _ pop_result = self.try_pop () let[@inline] on_non_empty (self : _ t) f = self.on_non_empty f let[@inline] closed (self : _ t) : bool = self.common.closed () let[@inline] num_discarded self = self.common.num_discarded () let[@inline] size self = self.common.size () let[@inline] high_watermark self = self.common.high_watermark () let map (type a b) (f : a -> b) (self : a t) : b t = { self with try_pop = (fun () -> match self.try_pop () with | (`Closed | `Empty) as r -> r | `Item x -> `Item (f x)); } end (** Sending side *) module Send = struct type 'a t = { push: 'a list -> unit; (** Push items. This might discard some of them. @raise Closed if the queue is closed. *) close: unit -> unit; (** Close the queue. Items currently in the queue will still be accessible to consumers until the queue is emptied out. Idempotent. *) common: Common.t; } let[@inline] push (self : _ t) x : unit = self.push x let[@inline] close (self : _ t) : unit = self.close () let[@inline] closed (self : _ t) : bool = self.common.closed () let[@inline] num_discarded self = self.common.num_discarded () let[@inline] size self = self.common.size () let[@inline] high_watermark self = self.common.high_watermark () let map (type a b) (f : a list -> b list) (self : b t) : a t = { self with push = (fun xs -> match f xs with | [] -> () | ys -> self.push ys); } (** Turn the writing end of the queue into an emitter. @param close_queue_on_close if true, closing the emitter will close the queue *) let to_emitter ~signal_name ~close_queue_on_close (self : 'a t) : 'a Opentelemetry_emitter.Emitter.t = let closed () = closed self in let enabled () = not (closed ()) in let emit x = if x <> [] then push self x in let tick ~mtime:_ = () in (* the exporter will emit these, the queue is shared *) let self_metrics ~now:_ () = [] in (* NOTE: we cannot actually flush, only close. Emptying the queue is fundamentally asynchronous because it's done by consumers *) let flush_and_close () = if close_queue_on_close then close self in { signal_name; closed; enabled; emit; tick; flush_and_close; self_metrics } end type 'a t = { send: 'a Send.t; recv: 'a Recv.t; } (** A bounded queue, with multiple producers and potentially multiple consumers. All functions must be thread-safe except for [try_pop] which might not have to be depending on the context (e.g. a Lwt-specific queue implementation will consume only from the Lwt thread). *) module Defaults = struct (** The default high watermark *) let high_watermark : int = 2048 end