From 33a0ee69baa8fca8358e24d6de86de6f96527b0b Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Sun, 15 Feb 2026 15:21:29 -0500 Subject: [PATCH] refactor: extract `should_retry` --- src/client/generic_http_consumer.ml | 56 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/client/generic_http_consumer.ml b/src/client/generic_http_consumer.ml index 4768cfe7..35273945 100644 --- a/src/client/generic_http_consumer.ml +++ b/src/client/generic_http_consumer.ml @@ -60,15 +60,33 @@ end = struct let cleanup self = Httpc.cleanup self.http + (** Should we retry, based on the HTTP response code? *) + let should_retry = function + | `Failure _ -> true (* Network errors, connection issues *) + | `Status (code, _) -> + (* Retry on server errors, rate limits, timeouts *) + code >= 500 || code = 429 || code = 408 + | `Sysbreak -> false (* User interrupt, don't retry *) + + (** Retry loop over [f()] with exponential backoff *) + let rec retry_loop_ (self : t) attempt delay_ms ~f = + let open IO in + let* result = f () in + match result with + | Ok x -> return (Ok x) + | Error err + when should_retry err && attempt < self.config.retry_max_attempts -> + let delay_s = delay_ms /. 1000. in + let* () = sleep_s delay_s in + let next_delay = + min self.config.retry_max_delay_ms + (delay_ms *. self.config.retry_backoff_multiplier) + in + retry_loop_ self (attempt + 1) next_delay ~f + | Error _ as err -> return err + let send (self : t) (sigs : OTEL.Any_signal_l.t) : (unit, error) result IO.t = - let should_retry = function - | `Failure _ -> true (* Network errors, connection issues *) - | `Status (code, _) -> - (* Retry on server errors, rate limits, timeouts *) - code >= 500 || code = 429 || code = 408 - | `Sysbreak -> false (* User interrupt, don't retry *) - in let res = Resource_signal.of_signal_l sigs in let url, signal_headers = match res with @@ -98,30 +116,14 @@ end = struct ~protocol:self.config.protocol res in - (* Retry loop with exponential backoff *) - let rec retry_send attempt delay_ms = - let open IO in - let* result = - Httpc.send self.http ~url ~headers ~decode:(`Ret ()) data - in - match result with - | Ok x -> return (Ok x) - | Error err - when should_retry err && attempt < self.config.retry_max_attempts -> - let delay_s = delay_ms /. 1000. in - let* () = sleep_s delay_s in - let next_delay = - min self.config.retry_max_delay_ms - (delay_ms *. self.config.retry_backoff_multiplier) - in - retry_send (attempt + 1) next_delay - | Error _ as err -> return err + let do_once () = + Httpc.send self.http ~url ~headers ~decode:(`Ret ()) data in if self.config.retry_max_attempts > 0 then - retry_send 0 self.config.retry_initial_delay_ms + retry_loop_ self 0 self.config.retry_initial_delay_ms ~f:do_once else - Httpc.send self.http ~url ~headers ~decode:(`Ret ()) data + do_once () end module C = Generic_consumer.Make (IO) (Notifier) (Sender)