Exponential backoff retry logic
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

retry: libraries should not log at warn/err

All retry-attempt notifications are dropped to debug level. A retry
library emits nothing at or above info by default: the caller already
receives the outcome back and decides what to surface.

Users who want visibility into retry behaviour still can -- the [retry]
log source is kept so it can be enabled explicitly at debug.

+15 -29
+15 -29
lib/retry.ml
··· 19 19 20 20 let config ?(max_retries = 3) ?(backoff_factor = 0.3) ?(backoff_max = 120.0) 21 21 ?(jitter = true) () = 22 - Log.debug (fun m -> 23 - m "Creating retry config: max_retries=%d backoff_factor=%.2f jitter=%b" 24 - max_retries backoff_factor jitter); 25 22 { max_retries; backoff_factor; backoff_max; jitter } 26 23 27 24 let calculate_backoff ~config ~attempt = ··· 29 26 let delay = 30 27 if config.jitter then base_delay +. Random.float base_delay else base_delay 31 28 in 32 - let final_delay = min delay config.backoff_max in 33 - Log.debug (fun m -> 34 - m "Backoff: attempt=%d base=%.2f jitter=%b -> %.2fs" attempt base_delay 35 - config.jitter final_delay); 36 - final_delay 29 + min delay config.backoff_max 30 + 31 + (* All retry events are logged at [debug]: a library should not inject 32 + warnings or errors into its caller's output. Callers wanting visibility 33 + into retry behaviour enable the [retry] log source at debug. *) 37 34 38 35 let with_retry ~clock ~config ~should_retry f = 39 36 let rec attempt n = 40 - Log.info (fun m -> m "Attempt %d/%d" n (config.max_retries + 1)); 37 + Log.debug (fun m -> m "attempt %d/%d" n (config.max_retries + 1)); 41 38 match f () with 42 39 | result -> 43 - if n > 1 then Log.info (fun m -> m "Succeeded after %d attempts" n); 40 + if n > 1 then Log.debug (fun m -> m "succeeded after %d attempts" n); 44 41 result 45 42 | exception exn when n <= config.max_retries && should_retry exn -> 46 43 let delay = calculate_backoff ~config ~attempt:n in 47 - Log.warn (fun m -> 48 - m "Attempt %d/%d failed: %s. Retrying in %.2fs..." n 44 + Log.debug (fun m -> 45 + m "attempt %d/%d failed: %s; sleeping %.2fs" n 49 46 (config.max_retries + 1) (Printexc.to_string exn) delay); 50 47 Eio.Time.sleep clock delay; 51 48 attempt (n + 1) 52 - | exception exn -> 53 - if n > config.max_retries then 54 - Log.err (fun m -> 55 - m "Failed after %d attempts: %s" n (Printexc.to_string exn)) 56 - else 57 - Log.err (fun m -> 58 - m "Failed and won't retry: %s" (Printexc.to_string exn)); 59 - raise exn 49 + | exception exn -> raise exn 60 50 in 61 51 attempt 1 62 52 63 53 let with_retry_result ~clock ~config ~should_retry f = 64 54 let rec attempt n = 65 - Log.info (fun m -> m "Attempt %d/%d" n (config.max_retries + 1)); 55 + Log.debug (fun m -> m "attempt %d/%d" n (config.max_retries + 1)); 66 56 match f () with 67 57 | Ok _ as result -> 68 - if n > 1 then Log.info (fun m -> m "Succeeded after %d attempts" n); 58 + if n > 1 then Log.debug (fun m -> m "succeeded after %d attempts" n); 69 59 result 70 60 | Error e when n <= config.max_retries && should_retry e -> 71 61 let delay = calculate_backoff ~config ~attempt:n in 72 - Log.warn (fun m -> 73 - m "Attempt %d/%d returned error. Retrying in %.2fs..." n 62 + Log.debug (fun m -> 63 + m "attempt %d/%d returned error; sleeping %.2fs" n 74 64 (config.max_retries + 1) delay); 75 65 Eio.Time.sleep clock delay; 76 66 attempt (n + 1) 77 - | Error _ as result -> 78 - if n > config.max_retries then 79 - Log.err (fun m -> m "Failed after %d attempts" n) 80 - else Log.err (fun m -> m "Failed and won't retry"); 81 - result 67 + | Error _ as result -> result 82 68 in 83 69 attempt 1 84 70