Compute the next refresh delay: 80 % of the time remaining until the current token's `exp`, plus up to 10 % jitter, with a small lower bound for already-expired tokens and capped at 12 h. If the token can't be parsed (legacy/non-JWT bearer) or carries the `exp = 0` non-expiring sentinel, default to 6 h.
(slot: &TokenSlot)
| 404 | /// (legacy/non-JWT bearer) or carries the `exp = 0` non-expiring sentinel, |
| 405 | /// default to 6 h. |
| 406 | fn compute_refresh_delay(slot: &TokenSlot) -> Duration { |
| 407 | let token = slot |
| 408 | .read() |
| 409 | .ok() |
| 410 | .and_then(|v| v.to_str().ok().map(str::to_string)) |
| 411 | .unwrap_or_default(); |
| 412 | let bearer = token.strip_prefix("Bearer ").unwrap_or(&token); |
| 413 | let now_ms = i64::try_from( |
| 414 | SystemTime::now() |
| 415 | .duration_since(UNIX_EPOCH) |
| 416 | .map_or(0, |d| d.as_millis()), |
| 417 | ) |
| 418 | .unwrap_or(i64::MAX); |
| 419 | let mut delay_ms = match parse_jwt_exp_ms(bearer) { |
| 420 | Some(0) | None => 21_600_000, |
| 421 | Some(exp) => { |
| 422 | let remaining_ms = exp - now_ms; |
| 423 | if remaining_ms <= 0 { |
| 424 | 1_000 |
| 425 | } else { |
| 426 | (remaining_ms * 8 / 10).clamp(1_000, 43_200_000) |
| 427 | } |
| 428 | } |
| 429 | }; |
| 430 | // Up to 10 % jitter, derived deterministically from token bytes so |
| 431 | // unit tests are reproducible without injecting an RNG. |
| 432 | let jitter_pct = (token.len() % 10) as u64; |
| 433 | let jitter_ms = (u64::try_from(delay_ms).unwrap_or(0) * jitter_pct) / 100; |
| 434 | delay_ms = delay_ms.saturating_add(i64::try_from(jitter_ms).unwrap_or(0)); |
| 435 | Duration::from_millis(u64::try_from(delay_ms).unwrap_or(0)) |
| 436 | } |
| 437 | |
| 438 | /// Decode the `exp` claim from a JWT without verifying its signature. |
| 439 | /// Returns the expiry in milliseconds since the Unix epoch, or `None` if |