Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions src/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,14 @@ impl Order {

/// Poll the order with the given [`RetryPolicy`]
///
/// Yields the [`OrderStatus`] immediately if `Ready` or `Invalid`, or after `tries` attempts.
/// Yields the [`OrderStatus`] immediately if `Ready` or `Invalid`, or yields an
/// [`Error::Timeout`] if the [`RetryPolicy::timeout`] has been reached.
pub async fn poll(&mut self, retries: &RetryPolicy) -> Result<OrderStatus, Error> {
let mut retrying = retries.state();
self.retry_after = None;
loop {
if let ControlFlow::Break(()) = retrying.wait(self.retry_after.take()).await {
return Ok(self.state.status);
if let ControlFlow::Break(err) = retrying.wait(self.retry_after.take()).await {
return Err(err);
}

let state = self.refresh().await?;
Expand Down Expand Up @@ -546,8 +547,7 @@ impl RetryPolicy {
RetryState {
delay: self.delay,
backoff: self.backoff,
timeout: self.timeout,
start: Instant::now(),
deadline: Instant::now() + self.timeout,
}
}
}
Expand All @@ -561,26 +561,30 @@ impl Default for RetryPolicy {
struct RetryState {
delay: Duration,
backoff: f32,
timeout: Duration,
start: Instant,
deadline: Instant,
}

impl RetryState {
async fn wait(&mut self, after: Option<SystemTime>) -> ControlFlow<(), ()> {
if self.start.elapsed() > self.timeout {
return ControlFlow::Break(());
}

async fn wait(&mut self, after: Option<SystemTime>) -> ControlFlow<Error, ()> {
if let Some(after) = after {
if let Ok(delay) = after.duration_since(SystemTime::now()) {
sleep(delay).await;
return ControlFlow::Continue(());
let now = SystemTime::now();
if let Ok(delay) = after.duration_since(now) {
let next = Instant::now() + delay;
if next > self.deadline {
return ControlFlow::Break(Error::Timeout(Some(next)));
} else {
sleep(delay).await;
return ControlFlow::Continue(());
}
}
}

sleep(self.delay).await;
self.delay = self.delay.mul_f32(self.backoff);
ControlFlow::Continue(())
match Instant::now() + self.delay > self.deadline {
true => ControlFlow::Break(Error::Timeout(None)),
false => ControlFlow::Continue(()),
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::{self, Write};
use std::net::IpAddr;
use std::time::Instant;

use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine};
use bytes::Bytes;
Expand Down Expand Up @@ -51,6 +52,11 @@ pub enum Error {
/// Failed to (de)serialize a JSON object
#[error("failed to (de)serialize JSON: {0}")]
Json(#[from] serde_json::Error),
/// Timed out while waiting for the server to update [`OrderStatus`]
///
/// If `Some`, the nested `Instant` indicates when the server suggests to poll next.
#[error("timed out waiting for an order update")]
Timeout(Option<Instant>),
/// ACME server does not support a requested feature
#[error("ACME server does not support: {0}")]
Unsupported(&'static str),
Expand Down