Skip to content

Commit 44cbe87

Browse files
committed
CP-49158: Use exponential backoff for delay between recursive calls
This delay was right after we waited for a new event, delaying all event responses by 50ms (including task completions). Eliminate the first delay, so that if we find the event we're looking after the DB update, then we can return immediately. On spurious wakeups (e.g. not the event we subscribed for) the delay is still useful, so keep it for recursive calls after the first one, and exponentially increase it up to the configured maximum. No feature flag, this is a relatively small change, and we use exponential backoffs elsewhere in XAPI already. Signed-off-by: Edwin Török <[email protected]>
1 parent a971b4a commit 44cbe87

File tree

3 files changed

+37
-16
lines changed

3 files changed

+37
-16
lines changed

ocaml/xapi-aux/throttle.ml

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,20 @@ module Make (Size : SIZE) = struct
4141
end
4242

4343
module Batching = struct
44-
type t = {delay_before: Mtime.span; delay_between: Mtime.span}
44+
type t = {
45+
delay_initial: Mtime.span
46+
; delay_before: Mtime.span
47+
; delay_between: Mtime.span
48+
}
4549

46-
let make ~delay_before ~delay_between = {delay_before; delay_between}
50+
let make ~delay_before ~delay_between =
51+
(* we are dividing, cannot overflow *)
52+
let delay_initial =
53+
Mtime.Span.to_float_ns delay_between /. 16.
54+
|> Mtime.Span.of_float_ns
55+
|> Option.get
56+
in
57+
{delay_initial; delay_before; delay_between}
4758

4859
(** [perform_delay delay] calls {!val:Thread.delay} when [delay] is non-zero.
4960
@@ -55,11 +66,15 @@ module Batching = struct
5566
if Mtime.Span.is_longer delay ~than:Mtime.Span.min_span then
5667
Thread.delay (Clock.Timer.span_to_s delay)
5768

58-
let with_recursive_loop config f arg =
59-
let rec self arg =
60-
perform_delay config.delay_between ;
61-
(f [@tailcall]) self arg
69+
let span_min a b = if Mtime.Span.is_shorter a ~than:b then a else b
70+
71+
let with_recursive_loop config f =
72+
let rec self arg input =
73+
let arg = span_min config.delay_between Mtime.Span.(2 * arg) in
74+
perform_delay arg ;
75+
(f [@tailcall]) (self arg) input
6276
in
77+
let self0 arg input = (f [@tailcall]) (self arg) input in
6378
perform_delay config.delay_before ;
64-
f self arg
79+
f (self0 config.delay_initial)
6580
end

ocaml/xapi-aux/throttle.mli

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,27 @@ module Batching : sig
3434
*)
3535

3636
val with_recursive_loop : t -> (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b
37-
(** [with_recursive_loop config f arg] calls [f self arg], where [self] can be used
37+
(** [with_recursive config f arg] calls [f self arg], where [self] can be used
3838
for recursive calls.
3939
40-
A [delay_before] amount of seconds is inserted once, and [delay_between] is inserted between recursive calls:
40+
[arg] is an argument that the implementation of [f] can change between recursive calls for its own purposes,
41+
otherwise [()] can be used.
42+
43+
A [delay_before] amount of seconds is inserted once, and [delay_between/8] is inserted between recursive calls,
44+
except the first one, and delays increase exponentially until [delay_between] is reached
4145
{v
4246
delay_before
4347
f ...
4448
(self[@tailcall]) ...
45-
delay_between
4649
f ...
4750
(self[@tailcall]) ...
48-
delay_between
51+
delay_between/8
4952
f ...
53+
(self[@tailcall]) ...
54+
delay_between/4
55+
f ...
5056
v}
5157
52-
The delays are determined by [config]
58+
The delays are determined by [config], and [delay_between] uses an exponential backoff, up to [config.delay_between] delay.
5359
*)
5460
end

ocaml/xapi/xapi_event.ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,11 @@ let rec next ~__context =
497497
in
498498
(* Like grab_range () only guarantees to return a non-empty range by blocking if necessary *)
499499
let grab_nonempty_range =
500-
Throttle.Batching.with_recursive_loop batching @@ fun self () ->
500+
Throttle.Batching.with_recursive_loop batching @@ fun self arg ->
501501
let last_id, end_id = grab_range () in
502502
if last_id = end_id then
503503
let (_ : int64) = wait subscription end_id in
504-
(self [@tailcall]) ()
504+
(self [@tailcall]) arg
505505
else
506506
(last_id, end_id)
507507
in
@@ -608,7 +608,7 @@ let from_inner __context session subs from from_t timer batching =
608608
let msg_gen, messages, tableset, (creates, mods, deletes, last) =
609609
with_call session subs (fun sub ->
610610
let grab_nonempty_range =
611-
Throttle.Batching.with_recursive_loop batching @@ fun self () ->
611+
Throttle.Batching.with_recursive_loop batching @@ fun self arg ->
612612
let ( (msg_gen, messages, _tableset, (creates, mods, deletes, last))
613613
as result
614614
) =
@@ -627,7 +627,7 @@ let from_inner __context session subs from from_t timer batching =
627627
(* last id the client got is equivalent to the current one *)
628628
last_msg_gen := msg_gen ;
629629
wait2 sub last timer ;
630-
(self [@tailcall]) ()
630+
(self [@tailcall]) arg
631631
) else
632632
result
633633
in

0 commit comments

Comments
 (0)