package opentelemetry-client

  1. Overview
  2. Docs

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