Skip to content

Introduce trait abstraction over select() to enable unit-testing timeout enforcement #473

@ChristianPavilonis

Description

@ChristianPavilonis

Problem

The auction orchestrator's three timeout enforcement paths are untestable in unit tests:

  1. Deadline check in select() loop — drops remaining requests when auction_start.elapsed() >= deadline
  2. Mediator skip — skips mediator when remaining_ms == 0 after bidding phase
  3. Provider skip — skips provider launch when effective_timeout == 0

The root cause is tight coupling to Fastly's concrete PendingRequest and select():

  • PendingRequest::new() is pub(super) — cannot be constructed outside the fastly crate
  • select() is a free function backed by Wasm host ABI calls
  • The AuctionProvider trait's request_bids() returns PendingRequest directly

This means the orchestrator's select loop can only run on Fastly Compute (or Viceroy), never in a plain cargo test.

Suggested approach

Introduce a trait that abstracts over "a set of in-flight requests you can wait on":

/// Result from waiting on a set of pending provider requests.
pub struct ProviderResult {
    pub backend_name: String,
    pub result: Result<fastly::Response, fastly::http::request::SendError>,
}

/// Abstracts over the mechanism for waiting on concurrent provider requests.
///
/// Production uses Fastly's `select()`; tests inject a mock.
pub trait PendingRequestSet {
    /// True when there are no more requests to wait on.
    fn is_empty(&self) -> bool;

    /// Block until the next request completes and return it.
    fn select_next(&mut self) -> ProviderResult;
}

The orchestrator's select loop changes from:

while !remaining.is_empty() {
    let (result, rest) = select(remaining);
    remaining = rest;
    // ...
}

to:

while !pending.is_empty() {
    let result = pending.select_next();
    // ...
}

A production implementation wraps Vec<PendingRequest> and delegates to fastly::http::request::select(). A test implementation can return pre-canned responses with configurable delays (using std::thread::sleep or instant completion).

This unlocks tests like:

  • Provider skip: set timeout_ms: 0 on context, verify no requests are launched
  • Deadline enforcement: mock 3 slow providers, verify the loop breaks after the deadline
  • Mediator skip: inject provider responses that consume the full budget, verify mediator is skipped

Scope

  • No behavior change — pure structural refactor
  • Affected files: orchestrator.rs, provider.rs (trait may need adjustment), new test mock
  • The AuctionProvider::request_bids() return type may also need abstracting, or the trait boundary can be drawn at the select loop level only (simpler)

Context

Flagged during review of #469 (auction timeout enforcement). See the TODO at crates/common/src/auction/orchestrator.rs:711.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions