use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::fmt;
use std::net::SocketAddr;
use std::path::Path;
use std::sync::{Arc, Mutex, PoisonError};
use std::task::{Poll, Waker};
use std::time::Duration;
use asupersync::Cx;
use asupersync::http::h1::{
Http1Client, Http1Config, Http1Server, Method as H1Method, Request as H1Request,
Response as H1Response, Version, server::HostPolicy,
};
use asupersync::http::{
ClientError as HttpClientError, Method as HttpMethod, ParsedUrl, Request as HttpRequest,
Response as HttpResponse,
};
use asupersync::lab::{DporExplorer, ExplorationReport, ExplorerConfig, LabRuntime};
use asupersync::net::tcp::VirtualTcpStream;
use asupersync::record::{ObligationKind, ObligationState};
use asupersync::types::{Budget, CancelReason};
use franken_snowflake_core::outcome::SnowflakeOutcome;
use franken_snowflake_http::{
AuthorizationDescriptor, RawHttp, SnowflakeAuthTokenType, SnowflakeEndpoint,
SnowflakeHttpClient, TransportConfig,
};
use franken_snowflake_sqlapi::driver::{
DriverEvent, DriverObserver, StatementHooks, run_statement_hooked,
};
use franken_snowflake_sqlapi::lifecycle::{PollPlan, Progress, StatementMachine};
use franken_snowflake_sqlapi::request::{SubmitQueryParams, SubmitStatementRequest};
use franken_snowflake_sqlapi::status::ResponseClass;
use serde::{Deserialize, Serialize};
use crate::harness::clock::{BackoffPolicy, Clock, ManualClock, backoff_schedule};
use crate::harness::logger::{LogError, RunLogger, RunSummary, StepOutcome};
use crate::mock::http::{Method as MockMethod, MockHttpRequest, MockHttpResponse, reason_phrase};
use crate::mock::{scenarios, server::MockSqlApi};
pub const RACE_SUITE_SCHEMA_VERSION: u32 = 1;
const DEFAULT_BASE_SEED: u64 = 0xF5_00_00_04;
const DEFAULT_DPOR_RUNS: usize = 4;
const DEFAULT_MAX_STEPS: u64 = 50_000;
const DEFAULT_RETRY_LIMIT: u32 = 2;
const CLIENT_HOST: &str = "snowflake.test";
const COMMAND_ID: &str = "fsnow-native-snowflake-connector-w0i.4";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RaceSuiteConfig {
pub base_seed: u64,
pub dpor_runs: usize,
pub max_steps_per_run: u64,
pub retry_limit: u32,
}
impl Default for RaceSuiteConfig {
fn default() -> Self {
default_race_suite_config()
}
}
#[must_use]
pub const fn default_race_suite_config() -> RaceSuiteConfig {
RaceSuiteConfig {
base_seed: DEFAULT_BASE_SEED,
dpor_runs: DEFAULT_DPOR_RUNS,
max_steps_per_run: DEFAULT_MAX_STEPS,
retry_limit: DEFAULT_RETRY_LIMIT,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RaceCaseKind {
CancelDuringSubmit,
CancelDuringPoll,
CancelDuringPartitionFetch,
RateLimitStorm,
PartialPartitionFailure,
UnsafeSubmitRetryRefusal,
CancelRacesPollExchange,
DriverCancelInFlight,
DriverResubmitAfterLostAnswer,
DriverCancelDuringPartitions,
}
impl RaceCaseKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::CancelDuringSubmit => "cancel_during_submit",
Self::CancelDuringPoll => "cancel_during_poll",
Self::CancelDuringPartitionFetch => "cancel_during_partition_fetch",
Self::RateLimitStorm => "rate_limit_storm",
Self::PartialPartitionFailure => "partial_partition_failure",
Self::UnsafeSubmitRetryRefusal => "unsafe_submit_retry_refusal",
Self::CancelRacesPollExchange => "cancel_races_poll_exchange",
Self::DriverCancelInFlight => "driver_cancel_in_flight",
Self::DriverResubmitAfterLostAnswer => "driver_resubmit_after_lost_answer",
Self::DriverCancelDuringPartitions => "driver_cancel_during_partitions",
}
}
fn all() -> [Self; 10] {
[
Self::CancelDuringSubmit,
Self::CancelDuringPoll,
Self::CancelDuringPartitionFetch,
Self::RateLimitStorm,
Self::PartialPartitionFailure,
Self::UnsafeSubmitRetryRefusal,
Self::CancelRacesPollExchange,
Self::DriverCancelInFlight,
Self::DriverResubmitAfterLostAnswer,
Self::DriverCancelDuringPartitions,
]
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaceCaseReport {
pub schema_version: u32,
pub case: RaceCaseKind,
pub seed: u64,
pub virtual_tcp_exchanges: u32,
pub plain_submits: u32,
pub retry_submits: u32,
pub async_submits: u32,
pub polls: u32,
pub partitions: u32,
pub cancels: u32,
pub retry_delays_ms: Vec<u64>,
pub manual_clock_ms: u64,
pub completed: bool,
pub cancelled: bool,
pub retry_budget_exhausted: bool,
pub unsafe_submit_retry_refused: bool,
pub no_double_submit: bool,
pub cancel_propagated: bool,
pub bounded_retries: bool,
pub lab_invariants_clean: bool,
pub step_capped: bool,
pub certificate_hash: u64,
pub trace_fingerprint: u64,
pub replay_command: String,
pub client_aborted_mid_exchange: bool,
pub no_orphan_statements: bool,
pub no_obligation_leaks: bool,
pub crashpack_manifest: String,
#[serde(default)]
pub driver_events: Vec<String>,
}
impl RaceCaseReport {
#[must_use]
pub const fn ok(&self) -> bool {
self.step_capped
|| (self.no_double_submit
&& self.cancel_propagated
&& self.bounded_retries
&& self.lab_invariants_clean
&& self.no_orphan_statements
&& self.no_obligation_leaks)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaceSuiteReport {
pub schema_version: u32,
pub suite_id: String,
pub config: RaceSuiteConfigReport,
pub schedules: Vec<RaceCaseReport>,
pub dpor: Vec<DporCaseReport>,
}
impl RaceSuiteReport {
#[must_use]
pub fn ok(&self) -> bool {
self.schedules.iter().all(RaceCaseReport::ok) && self.dpor.iter().all(DporCaseReport::ok)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaceSuiteConfigReport {
pub base_seed: u64,
pub dpor_runs: usize,
pub max_steps_per_run: u64,
pub retry_limit: u32,
}
impl From<&RaceSuiteConfig> for RaceSuiteConfigReport {
fn from(value: &RaceSuiteConfig) -> Self {
Self {
base_seed: value.base_seed,
dpor_runs: value.dpor_runs,
max_steps_per_run: value.max_steps_per_run,
retry_limit: value.retry_limit,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DporCaseReport {
pub case: RaceCaseKind,
pub total_runs: usize,
pub unique_classes: usize,
pub violation_count: usize,
pub step_capped_runs: usize,
pub total_races: usize,
pub total_backtrack_points: usize,
}
impl DporCaseReport {
#[must_use]
pub const fn ok(&self) -> bool {
self.violation_count == 0
}
}
#[derive(Debug)]
pub enum RaceError {
Lab(String),
Truncated,
Http(String),
Lifecycle(String),
Poisoned(&'static str),
Serialize(serde_json::Error),
Log(LogError),
}
impl fmt::Display for RaceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Lab(message) => write!(f, "lab race error: {message}"),
Self::Truncated => write!(
f,
"lab run truncated at the per-run step budget before quiescence (inconclusive)"
),
Self::Http(message) => write!(f, "virtual HTTP error: {message}"),
Self::Lifecycle(message) => write!(f, "lifecycle race error: {message}"),
Self::Poisoned(name) => write!(f, "shared race state poisoned: {name}"),
Self::Serialize(error) => write!(f, "race report serialization error: {error}"),
Self::Log(error) => write!(f, "race logger error: {error}"),
}
}
}
impl std::error::Error for RaceError {}
impl From<serde_json::Error> for RaceError {
fn from(error: serde_json::Error) -> Self {
Self::Serialize(error)
}
}
impl From<LogError> for RaceError {
fn from(error: LogError) -> Self {
Self::Log(error)
}
}
pub fn run_default_race_suite() -> Result<RaceSuiteReport, RaceError> {
run_race_suite(&default_race_suite_config())
}
pub fn run_race_suite(config: &RaceSuiteConfig) -> Result<RaceSuiteReport, RaceError> {
let schedules = Arc::new(Mutex::new(Vec::new()));
let mut dpor = Vec::new();
for (case_idx, case) in RaceCaseKind::all().into_iter().enumerate() {
let case_seed = config.base_seed.wrapping_add((case_idx as u64) << 16);
let mut explorer = DporExplorer::new(
ExplorerConfig::new(case_seed, config.dpor_runs)
.worker_count(2)
.max_steps(config.max_steps_per_run),
);
let schedules_for_case = Arc::clone(&schedules);
let retry_limit = config.retry_limit;
let report = explorer.explore(move |runtime| {
let schedule = run_case_under_lab(runtime, case, retry_limit);
match schedules_for_case.lock() {
Ok(mut reports) => reports.push(schedule),
Err(poisoned) => poisoned.into_inner().push(poisoned_report(
case,
runtime.config().seed,
"race schedule sink",
)),
}
});
let coverage = explorer.dpor_coverage();
dpor.push(dpor_case_report(
case,
&report,
&coverage,
config.max_steps_per_run,
));
}
let schedules = match Arc::try_unwrap(schedules) {
Ok(mutex) => mutex
.into_inner()
.map_err(|_| RaceError::Poisoned("race schedules"))?,
Err(shared) => shared
.lock()
.map_err(|_| RaceError::Poisoned("race schedules"))?
.clone(),
};
Ok(RaceSuiteReport {
schema_version: RACE_SUITE_SCHEMA_VERSION,
suite_id: COMMAND_ID.to_owned(),
config: RaceSuiteConfigReport::from(config),
schedules,
dpor,
})
}
pub fn race_suite_jsonl(report: &RaceSuiteReport) -> Result<String, RaceError> {
let mut out = String::new();
for schedule in &report.schedules {
out.push_str(&serde_json::to_string(schedule)?);
out.push('\n');
}
Ok(out)
}
pub fn write_race_suite_artifacts(
report: &RaceSuiteReport,
artifacts_root: impl AsRef<Path>,
) -> Result<RunSummary, RaceError> {
let mut logger = RunLogger::new(artifacts_root, &report.suite_id)?;
for schedule in &report.schedules {
let detail = serde_json::to_string(schedule)?;
logger.emit(
COMMAND_ID,
schedule.case.as_str(),
if schedule.ok() {
StepOutcome::Pass
} else {
StepOutcome::Fail
},
Some(detail),
schedule.ok().then_some("all invariants true".to_owned()),
(!schedule.ok()).then_some(format!(
"no_double_submit={} cancel_propagated={} bounded_retries={} lab_invariants_clean={}",
schedule.no_double_submit,
schedule.cancel_propagated,
schedule.bounded_retries,
schedule.lab_invariants_clean
)),
)?;
}
Ok(logger.finish()?)
}
fn dpor_case_report(
case: RaceCaseKind,
report: &ExplorationReport,
coverage: &asupersync::lab::DporCoverageMetrics,
max_steps_per_run: u64,
) -> DporCaseReport {
let mut violation_count = 0;
let mut step_capped_runs = 0;
for violation in &report.violations {
if violation.steps >= max_steps_per_run {
step_capped_runs += 1;
} else {
violation_count += 1;
}
}
DporCaseReport {
case,
total_runs: report.total_runs,
unique_classes: report.unique_classes,
violation_count,
step_capped_runs,
total_races: coverage.total_races,
total_backtrack_points: coverage.total_backtrack_points,
}
}
fn run_case_under_lab(
runtime: &mut LabRuntime,
case: RaceCaseKind,
retry_limit: u32,
) -> RaceCaseReport {
let seed = runtime.config().seed;
match run_case_inner(runtime, case, retry_limit) {
Ok(mut report) => {
report.seed = seed;
report.certificate_hash = runtime.certificate().hash();
report.trace_fingerprint = trace_fingerprint(runtime);
let step_capped = !runtime.is_quiescent();
report.step_capped = step_capped;
report.lab_invariants_clean = step_capped || runtime.check_invariants().is_empty();
report
}
Err(RaceError::Truncated) => {
let mut report = failed_report(runtime, case, RaceError::Truncated);
report.step_capped = true;
report
}
Err(error) => failed_report(runtime, case, error),
}
}
fn run_case_inner(
runtime: &mut LabRuntime,
case: RaceCaseKind,
retry_limit: u32,
) -> Result<RaceCaseReport, RaceError> {
if matches!(
case,
RaceCaseKind::DriverCancelInFlight
| RaceCaseKind::DriverResubmitAfterLostAnswer
| RaceCaseKind::DriverCancelDuringPartitions
) {
return run_production_driver(runtime, case);
}
let mut driver = RaceDriver::new(runtime, case, retry_limit);
match case {
RaceCaseKind::CancelDuringSubmit => driver.cancel_during_submit()?,
RaceCaseKind::CancelDuringPoll => driver.cancel_during_poll()?,
RaceCaseKind::CancelDuringPartitionFetch => driver.cancel_during_partition_fetch()?,
RaceCaseKind::RateLimitStorm => driver.rate_limit_storm()?,
RaceCaseKind::PartialPartitionFailure => driver.partial_partition_failure()?,
RaceCaseKind::UnsafeSubmitRetryRefusal => driver.unsafe_submit_retry_refusal()?,
RaceCaseKind::CancelRacesPollExchange => driver.cancel_races_poll_exchange()?,
RaceCaseKind::DriverCancelInFlight
| RaceCaseKind::DriverResubmitAfterLostAnswer
| RaceCaseKind::DriverCancelDuringPartitions => {
return Err(RaceError::Lab(format!(
"{} runs the production driver, not the model",
case.as_str()
)));
}
}
Ok(driver.finish())
}
fn run_production_driver(
runtime: &mut LabRuntime,
case: RaceCaseKind,
) -> Result<RaceCaseReport, RaceError> {
let seed = runtime.config().seed;
let region = runtime.state.create_root_region(Budget::INFINITE);
let server = Arc::new(Mutex::new(RaceServerState::for_case(case)));
let ended: Arc<Mutex<Option<&'static str>>> = Arc::new(Mutex::new(None));
let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let mut recorder = EventLog(Arc::clone(&events));
let inbox = Arc::new(Mutex::new(Inbox::default()));
let http = VirtualRawHttp {
inbox: Arc::clone(&inbox),
};
let (server_task, _) = runtime
.state
.create_task(
region,
Budget::INFINITE,
serve_inbox(inbox, Arc::clone(&server)),
)
.map_err(|error| RaceError::Lab(format!("server task spawn failed: {error}")))?;
let endpoint = SnowflakeEndpoint::parse("https://race.us-east-1.snowflakecomputing.com")
.map_err(|error| RaceError::Lab(format!("race endpoint refused: {}", error.message)))?;
let slot = Arc::clone(&ended);
let (driver_task, driver_handle) = runtime
.state
.create_task(region, Budget::INFINITE, async move {
let Some(cx) = Cx::current() else {
return;
};
let client = SnowflakeHttpClient::new(TransportConfig::new(endpoint), http);
let mut auth = AuthorizationDescriptor::bearer(
SnowflakeAuthTokenType::ProgrammaticAccessToken,
"race-token",
"cred_race",
);
let params = SubmitQueryParams {
request_id: Some("01b2c3d4-0000-4000-8000-00000000race".to_owned()),
retry: true,
..SubmitQueryParams::default()
};
let (outcome, _) = Box::pin(run_statement_hooked(
&cx,
&client,
&mut auth,
SubmitStatementRequest::new("select 1"),
params,
PollPlan::default(),
StatementHooks {
sink: None,
observer: Some(&mut recorder),
},
))
.await;
let (label, ending) = match &outcome {
SnowflakeOutcome::Ok(_) => ("ok", "ended: ok".to_owned()),
SnowflakeOutcome::Cancelled(reason) => {
("cancelled", format!("ended: cancelled ({:?})", reason.kind))
}
SnowflakeOutcome::Err(error) => ("err", format!("ended: error {:?}", error.code)),
SnowflakeOutcome::Panicked(_) => ("panicked", "ended: panicked".to_owned()),
};
recorder
.0
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(ending);
*slot.lock().unwrap_or_else(PoisonError::into_inner) = Some(label);
})
.map_err(|error| RaceError::Lab(format!("driver task spawn failed: {error}")))?;
let mut scheduler = runtime.scheduler.lock();
scheduler.schedule(server_task, 0);
scheduler.schedule(driver_task, 0);
drop(scheduler);
if matches!(
case,
RaceCaseKind::DriverCancelInFlight | RaceCaseKind::DriverCancelDuringPartitions
) {
let wait_for_partitions = case == RaceCaseKind::DriverCancelDuringPartitions;
let watched = Arc::clone(&server);
let (canceller_task, _) = runtime
.state
.create_task(region, Budget::INFINITE, async move {
while wait_for_partitions
&& watched
.lock()
.unwrap_or_else(PoisonError::into_inner)
.counters
.partitions
== 0
&& !driver_handle.is_finished()
{
asupersync::runtime::yield_now().await;
}
driver_handle.abort_with_reason(CancelReason::user("the caller gives up"));
})
.map_err(|error| RaceError::Lab(format!("canceller task spawn failed: {error}")))?;
runtime.scheduler.lock().schedule(canceller_task, 0);
}
runtime.run_with_auto_advance();
if !runtime.is_quiescent() {
return Err(RaceError::Truncated);
}
let ended = ended
.lock()
.map_err(|_| RaceError::Poisoned("driver outcome"))?
.take();
let state = server
.lock()
.map_err(|_| RaceError::Poisoned("race server state"))?;
let counters = state.counters.clone();
let completed = ended == Some("ok");
let cancelled = ended == Some("cancelled");
let orphan_free = match &state.issued_handle {
None => true,
Some(handle) => completed || state.cancelled_handles.contains(handle),
};
let leases: Vec<ObligationState> = runtime
.state
.obligations_iter()
.filter(|(_, record)| record.kind == ObligationKind::Lease)
.map(|(_, record)| record.state)
.collect();
let leases_resolved = runtime.state.leak_count() == 0
&& (state.issued_handle.is_none() || !leases.is_empty())
&& leases
.iter()
.all(|lease| matches!(lease, ObligationState::Committed | ObligationState::Aborted));
Ok(RaceCaseReport {
schema_version: RACE_SUITE_SCHEMA_VERSION,
case,
seed,
virtual_tcp_exchanges: counters.virtual_tcp_exchanges,
plain_submits: counters.plain_submits,
retry_submits: counters.retry_submits,
async_submits: counters.async_submits,
polls: counters.polls,
partitions: counters.partitions,
cancels: counters.cancels,
retry_delays_ms: Vec::new(),
manual_clock_ms: 0,
completed,
cancelled,
retry_budget_exhausted: false,
unsafe_submit_retry_refused: false,
no_double_submit: state.mock.executions() <= 1,
cancel_propagated: orphan_free,
bounded_retries: true,
lab_invariants_clean: true,
client_aborted_mid_exchange: false,
no_orphan_statements: orphan_free,
no_obligation_leaks: leases_resolved,
step_capped: false,
certificate_hash: 0,
trace_fingerprint: 0,
replay_command: replay_command(case, seed),
crashpack_manifest: crashpack_manifest(case, seed),
driver_events: events
.lock()
.map_err(|_| RaceError::Poisoned("driver events"))?
.clone(),
})
}
struct EventLog(Arc<Mutex<Vec<String>>>);
impl DriverObserver for EventLog {
fn event(&mut self, event: DriverEvent) {
self.0
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(format!("{event:?}"));
}
}
#[derive(Default)]
struct Inbox {
connections: VecDeque<VirtualTcpStream>,
waker: Option<Waker>,
closed: bool,
}
struct VirtualRawHttp {
inbox: Arc<Mutex<Inbox>>,
}
impl VirtualRawHttp {
fn dial(&self) -> VirtualTcpStream {
let (client_io, server_io) =
VirtualTcpStream::pair(socket_addr(40_000), socket_addr(40_001));
let mut inbox = self.inbox.lock().unwrap_or_else(PoisonError::into_inner);
inbox.connections.push_back(server_io);
if let Some(waker) = inbox.waker.take() {
waker.wake();
}
client_io
}
}
impl Drop for VirtualRawHttp {
fn drop(&mut self) {
let mut inbox = self.inbox.lock().unwrap_or_else(PoisonError::into_inner);
inbox.closed = true;
if let Some(waker) = inbox.waker.take() {
waker.wake();
}
}
}
impl RawHttp for VirtualRawHttp {
async fn send(
&self,
_cx: &Cx,
method: HttpMethod,
url: String,
headers: Vec<(String, String)>,
body: Vec<u8>,
_timeout: Option<Duration>,
) -> Result<HttpResponse, HttpClientError> {
let parsed = ParsedUrl::parse(&url)?;
let request = HttpRequest::builder(method, parsed.path.clone())
.header("Host", parsed.authority())
.headers(
headers
.into_iter()
.filter(|(name, _)| !name.eq_ignore_ascii_case("host")),
)
.body(body)
.build();
let (response, _connection, _body_withheld) =
Box::pin(Http1Client::request_with_io(self.dial(), request)).await?;
Ok(response)
}
}
async fn serve_inbox(inbox: Arc<Mutex<Inbox>>, server_state: Arc<Mutex<RaceServerState>>) {
loop {
let next = std::future::poll_fn(|context| {
let mut inbox = inbox.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(connection) = inbox.connections.pop_front() {
Poll::Ready(Some(connection))
} else if inbox.closed {
Poll::Ready(None)
} else {
inbox.waker = Some(context.waker().clone());
Poll::Pending
}
})
.await;
let Some(connection) = next else {
break;
};
let state = Arc::clone(&server_state);
let server = Http1Server::with_config(
move |request| {
let state = Arc::clone(&state);
async move {
state
.lock()
.unwrap_or_else(PoisonError::into_inner)
.respond(request)
}
},
Http1Config::default()
.host_policy(HostPolicy::AllowAll)
.keep_alive(false)
.max_requests(Some(1)),
);
let _ = Box::pin(server.serve(connection)).await;
}
}
struct RaceDriver<'a> {
runtime: &'a mut LabRuntime,
case: RaceCaseKind,
retry_limit: u32,
server: Arc<Mutex<RaceServerState>>,
machine: StatementMachine,
clock: ManualClock,
retry_delays_ms: Vec<u64>,
completed: bool,
cancelled: bool,
retry_budget_exhausted: bool,
unsafe_submit_retry_refused: bool,
max_attempts_observed: u32,
region: asupersync::types::RegionId,
obligation_open: Option<String>,
obligation_resolved: bool,
client_aborted_mid_exchange: bool,
}
impl<'a> RaceDriver<'a> {
fn new(runtime: &'a mut LabRuntime, case: RaceCaseKind, retry_limit: u32) -> Self {
let region = runtime.state.create_root_region(Budget::INFINITE);
Self {
runtime,
case,
retry_limit,
server: Arc::new(Mutex::new(RaceServerState::for_case(case))),
machine: StatementMachine::new(PollPlan::with_max_polls(8)),
clock: ManualClock::new(),
retry_delays_ms: Vec::new(),
completed: false,
cancelled: false,
retry_budget_exhausted: false,
unsafe_submit_retry_refused: false,
max_attempts_observed: 0,
region,
obligation_open: None,
obligation_resolved: false,
client_aborted_mid_exchange: false,
}
}
fn cancel_races_poll_exchange(&mut self) -> Result<(), RaceError> {
let response = self.send_with_retry(RouteKind::SubmitPlain, submit_request(false))?;
let progress = self.on_submit(response)?;
let Progress::PollAgain(handle) = progress else {
return Err(RaceError::Lifecycle(
"expected poll handle before the raced poll exchange".to_owned(),
));
};
self.obligation_open = Some(handle.as_str().to_owned());
let polled = self.send_once_raced(poll_request(handle.as_str()))?;
match polled {
Some(poll) => {
let progress = self
.machine
.on_poll(ResponseClass::from_status(poll.status), &poll.body)
.map_err(|error| RaceError::Lifecycle(error.to_string()))?;
match progress {
Progress::Complete(_) => {
self.mark_progress(&progress);
self.obligation_open = None;
self.obligation_resolved = true;
}
Progress::PollAgain(next) => {
self.cancel_handle(next.as_str())?;
self.obligation_open = None;
self.obligation_resolved = true;
}
_ => {}
}
}
None => {
self.client_aborted_mid_exchange = true;
self.cancel_handle(handle.as_str())?;
self.obligation_open = None;
self.obligation_resolved = true;
}
}
Ok(())
}
fn cancel_during_submit(&mut self) -> Result<(), RaceError> {
let response = self.send_with_retry(RouteKind::SubmitPlain, submit_request(false))?;
let progress = self.on_submit(response)?;
if let Progress::PollAgain(handle) = progress {
self.cancel_handle(handle.as_str())?;
}
Ok(())
}
fn cancel_during_poll(&mut self) -> Result<(), RaceError> {
let response = self.send_with_retry(RouteKind::SubmitPlain, submit_request(false))?;
let progress = self.on_submit(response)?;
let Progress::PollAgain(handle) = progress else {
return Err(RaceError::Lifecycle(
"expected poll handle after async submit".to_owned(),
));
};
let poll = self.send_with_retry(RouteKind::Poll, poll_request(handle.as_str()))?;
let progress = self
.machine
.on_poll(ResponseClass::from_status(poll.status), &poll.body)
.map_err(|error| RaceError::Lifecycle(error.to_string()))?;
if let Progress::PollAgain(next) = progress {
self.cancel_handle(next.as_str())?;
}
Ok(())
}
fn cancel_during_partition_fetch(&mut self) -> Result<(), RaceError> {
let response = self.send_with_retry(RouteKind::SubmitPlain, submit_request(false))?;
let progress = self.on_submit(response)?;
let Progress::FetchPartition { handle, partition } = progress else {
return Err(RaceError::Lifecycle(
"expected first partition fetch after immediate multi-partition submit".to_owned(),
));
};
let fetched = self.send_with_retry(
RouteKind::Partition,
partition_request(handle.as_str(), partition),
)?;
let progress = self
.machine
.on_partition(
ResponseClass::from_status(fetched.status),
partition,
&fetched.body,
)
.map_err(|error| RaceError::Lifecycle(error.to_string()))?;
if let Progress::FetchPartition { handle, .. } = progress {
self.cancel_handle(handle.as_str())?;
}
Ok(())
}
fn rate_limit_storm(&mut self) -> Result<(), RaceError> {
let response = self.send_with_retry(RouteKind::SubmitPlain, submit_request(false))?;
let progress = self.on_submit(response)?;
let Progress::PollAgain(handle) = progress else {
return Err(RaceError::Lifecycle(
"expected poll handle after rate-limit submit".to_owned(),
));
};
let first_poll = self.send_with_retry(RouteKind::Poll, poll_request(handle.as_str()))?;
let progress = self
.machine
.on_poll(
ResponseClass::from_status(first_poll.status),
&first_poll.body,
)
.map_err(|error| RaceError::Lifecycle(error.to_string()))?;
let Progress::PollAgain(handle) = progress else {
return Err(RaceError::Lifecycle(
"expected running status after bounded 429 storm".to_owned(),
));
};
let terminal = self.send_with_retry(RouteKind::Poll, poll_request(handle.as_str()))?;
let terminal_progress = self
.machine
.on_poll(ResponseClass::from_status(terminal.status), &terminal.body);
self.mark_terminal(terminal_progress)?;
Ok(())
}
fn partial_partition_failure(&mut self) -> Result<(), RaceError> {
let response = self.send_with_retry(RouteKind::SubmitPlain, submit_request(false))?;
let progress = self.on_submit(response)?;
let Progress::FetchPartition { handle, partition } = progress else {
return Err(RaceError::Lifecycle(
"expected partition fetch after partial-failure submit".to_owned(),
));
};
let first = self.send_with_retry(
RouteKind::Partition,
partition_request(handle.as_str(), partition),
)?;
let progress = self
.machine
.on_partition(
ResponseClass::from_status(first.status),
partition,
&first.body,
)
.map_err(|error| RaceError::Lifecycle(error.to_string()))?;
let Progress::FetchPartition { handle, partition } = progress else {
return Err(RaceError::Lifecycle(
"expected second partition after partition 1".to_owned(),
));
};
let second = self.send_with_retry(
RouteKind::Partition,
partition_request(handle.as_str(), partition),
);
if matches!(second, Err(RaceError::Http(_))) {
self.retry_budget_exhausted = true;
self.cancel_handle(handle.as_str())?;
return Ok(());
}
let response = second?;
let terminal_progress = self.machine.on_partition(
ResponseClass::from_status(response.status),
partition,
&response.body,
);
self.mark_terminal(terminal_progress)?;
Ok(())
}
fn unsafe_submit_retry_refusal(&mut self) -> Result<(), RaceError> {
match self.send_with_retry(RouteKind::SubmitPlain, submit_request(false)) {
Err(RaceError::Truncated) => Err(RaceError::Truncated),
Err(RaceError::Http(_)) => {
self.unsafe_submit_retry_refused = true;
Ok(())
}
_ => Err(RaceError::Http(
"plain submit should refuse retryable response instead of resubmitting".to_owned(),
)),
}
}
fn on_submit(&mut self, response: MockHttpResponse) -> Result<Progress, RaceError> {
let progress = self
.machine
.on_submit(ResponseClass::from_status(response.status), &response.body)
.map_err(|error| RaceError::Lifecycle(error.to_string()))?;
self.mark_progress(&progress);
Ok(progress)
}
fn mark_terminal(
&mut self,
progress: Result<Progress, franken_snowflake_sqlapi::lifecycle::LifecycleError>,
) -> Result<(), RaceError> {
let progress = progress.map_err(|error| RaceError::Lifecycle(error.to_string()))?;
self.mark_progress(&progress);
Ok(())
}
fn mark_progress(&mut self, progress: &Progress) {
if matches!(progress, Progress::Complete(_)) {
self.completed = true;
}
}
fn cancel_handle(&mut self, handle: &str) -> Result<(), RaceError> {
let response = self.send_once(cancel_request(handle))?;
if response.status == 200 {
self.cancelled = true;
}
Ok(())
}
fn send_with_retry(
&mut self,
route: RouteKind,
request: H1Request,
) -> Result<MockHttpResponse, RaceError> {
let schedule = retry_schedule(self.retry_limit, self.runtime.config().seed);
let mut retries_spent = 0_u32;
loop {
let response = self.send_once(request.clone())?;
let attempts = retries_spent.saturating_add(1);
self.max_attempts_observed = self.max_attempts_observed.max(attempts);
if !is_retryable_status(response.status) {
return Ok(response);
}
if !route.allows_retry() {
self.unsafe_submit_retry_refused = route == RouteKind::SubmitPlain;
return Err(RaceError::Http(format!(
"{} returned retryable status {} but route is not idempotent",
route.as_str(),
response.status
)));
}
if retries_spent >= self.retry_limit {
self.retry_budget_exhausted = true;
return Err(RaceError::Http(format!(
"{} exhausted retry budget after {} attempts",
route.as_str(),
attempts
)));
}
if let Some(delay) = schedule.get(retries_spent as usize) {
self.clock.advance(*delay);
self.retry_delays_ms.push(duration_millis(*delay));
}
retries_spent = retries_spent.saturating_add(1);
}
}
fn send_once(&mut self, request: H1Request) -> Result<MockHttpResponse, RaceError> {
let exchange = perform_virtual_http_exchange(
self.runtime,
self.region,
Arc::clone(&self.server),
request,
)?;
Ok(exchange)
}
fn send_once_raced(
&mut self,
request: H1Request,
) -> Result<Option<MockHttpResponse>, RaceError> {
perform_virtual_http_exchange_raced(
self.runtime,
self.region,
Arc::clone(&self.server),
request,
)
}
fn finish(self) -> RaceCaseReport {
let counters = self
.server
.lock()
.map(|state| state.counters.clone())
.unwrap_or_default();
let cancel_required = matches!(
self.case,
RaceCaseKind::CancelDuringSubmit
| RaceCaseKind::CancelDuringPoll
| RaceCaseKind::CancelDuringPartitionFetch
| RaceCaseKind::PartialPartitionFailure
| RaceCaseKind::CancelRacesPollExchange
);
let no_double_submit = counters.plain_submits <= 1;
let bounded_retries = self.max_attempts_observed <= self.retry_limit.saturating_add(1);
RaceCaseReport {
schema_version: RACE_SUITE_SCHEMA_VERSION,
case: self.case,
seed: self.runtime.config().seed,
virtual_tcp_exchanges: counters.virtual_tcp_exchanges,
plain_submits: counters.plain_submits,
retry_submits: counters.retry_submits,
async_submits: counters.async_submits,
polls: counters.polls,
partitions: counters.partitions,
cancels: counters.cancels,
retry_delays_ms: self.retry_delays_ms,
no_orphan_statements: {
let issued = self
.server
.lock()
.ok()
.and_then(|state| state.issued_handle.clone());
let cancelled = self
.server
.lock()
.map(|state| state.cancelled_handles.clone())
.unwrap_or_default();
match issued {
None => true,
Some(handle) => cancelled.contains(&handle) || self.completed,
}
},
no_obligation_leaks: self.obligation_open.is_none() || self.obligation_resolved,
lab_invariants_clean: true,
manual_clock_ms: duration_millis(self.clock.now()),
completed: self.completed,
cancelled: self.cancelled,
retry_budget_exhausted: self.retry_budget_exhausted,
unsafe_submit_retry_refused: self.unsafe_submit_retry_refused,
no_double_submit,
cancel_propagated: !cancel_required || counters.cancels >= 1,
bounded_retries,
client_aborted_mid_exchange: self.client_aborted_mid_exchange,
step_capped: false,
certificate_hash: 0,
trace_fingerprint: 0,
replay_command: replay_command(self.case, self.runtime.config().seed),
crashpack_manifest: crashpack_manifest(self.case, self.runtime.config().seed),
driver_events: Vec::new(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum RouteKind {
SubmitPlain,
Poll,
Partition,
}
impl RouteKind {
const fn as_str(self) -> &'static str {
match self {
Self::SubmitPlain => "submit_plain",
Self::Poll => "poll",
Self::Partition => "partition",
}
}
const fn allows_retry(self) -> bool {
matches!(self, Self::Poll | Self::Partition)
}
}
#[derive(Clone, Debug, Default)]
struct RaceCounters {
virtual_tcp_exchanges: u32,
plain_submits: u32,
retry_submits: u32,
async_submits: u32,
polls: u32,
partitions: u32,
cancels: u32,
}
struct RaceServerState {
mock: MockSqlApi,
scripts: BTreeMap<String, VecDeque<MockHttpResponse>>,
counters: RaceCounters,
issued_handle: Option<String>,
cancelled_handles: BTreeSet<String>,
}
impl RaceServerState {
fn for_case(case: RaceCaseKind) -> Self {
let mut state = match case {
RaceCaseKind::CancelDuringPartitionFetch
| RaceCaseKind::PartialPartitionFailure
| RaceCaseKind::DriverCancelDuringPartitions => Self::multi_partition(case),
RaceCaseKind::UnsafeSubmitRetryRefusal => Self::unsafe_submit_refusal(case),
RaceCaseKind::RateLimitStorm => Self::rate_limit_storm(case),
RaceCaseKind::CancelDuringSubmit | RaceCaseKind::CancelDuringPoll => {
Self::default_async(case)
}
RaceCaseKind::CancelRacesPollExchange | RaceCaseKind::DriverCancelInFlight => {
Self::default_async(case)
}
RaceCaseKind::DriverResubmitAfterLostAnswer => Self::lost_submit_answer(),
};
state.install_common_scripts();
state
}
fn default_async(_case: RaceCaseKind) -> Self {
Self {
mock: scenarios::default_async_lifecycle(),
scripts: BTreeMap::new(),
counters: RaceCounters::default(),
issued_handle: None,
cancelled_handles: BTreeSet::new(),
}
}
fn lost_submit_answer() -> Self {
Self {
mock: scenarios::default_async_lifecycle()
.with_lost_submit_answer(MockHttpResponse::json(500, b"{}".to_vec())),
scripts: BTreeMap::new(),
counters: RaceCounters::default(),
issued_handle: None,
cancelled_handles: BTreeSet::new(),
}
}
fn rate_limit_storm(_case: RaceCaseKind) -> Self {
let handle = scenarios::DEFAULT_HANDLE;
let mut scripts = BTreeMap::new();
scripts.insert(
format!("GET /api/v2/statements/{handle}"),
VecDeque::from([
scenarios::rate_limited(),
scenarios::rate_limited(),
scenarios::running(),
scenarios::ok_single_partition(),
]),
);
Self {
mock: scenarios::default_async_lifecycle(),
scripts,
counters: RaceCounters::default(),
issued_handle: None,
cancelled_handles: BTreeSet::new(),
}
}
fn multi_partition(case: RaceCaseKind) -> Self {
let handle = "01b2c3d4-0000-0000-0000-000000000010";
let mut scripts = BTreeMap::new();
if case == RaceCaseKind::PartialPartitionFailure {
scripts.insert(
format!("GET /api/v2/statements/{handle}?partition=2"),
VecDeque::from([
retryable_failure(),
retryable_failure(),
retryable_failure(),
]),
);
}
Self {
mock: MockSqlApi::new(
handle,
scenarios::running(),
scenarios::ok_multi_partition(),
scenarios::cancel(),
)
.immediate()
.with_partition(
1,
MockHttpResponse::json(
200,
br#"[["18264","ENTITY125"],["18265","ENTITY126"]]"#.to_vec(),
),
)
.with_partition(
2,
MockHttpResponse::json(200, br#"[["18266","ENTITY127"]]"#.to_vec()),
),
scripts,
counters: RaceCounters::default(),
issued_handle: None,
cancelled_handles: BTreeSet::new(),
}
}
fn unsafe_submit_refusal(_case: RaceCaseKind) -> Self {
let mut scripts = BTreeMap::new();
scripts.insert(
"POST /api/v2/statements".to_owned(),
VecDeque::from([retryable_failure(), scenarios::running()]),
);
Self {
mock: scenarios::default_async_lifecycle(),
scripts,
counters: RaceCounters::default(),
issued_handle: None,
cancelled_handles: BTreeSet::new(),
}
}
fn install_common_scripts(&mut self) {}
fn respond(&mut self, request: H1Request) -> H1Response {
let mock_request = h1_to_mock_request(request);
self.counters.virtual_tcp_exchanges = self.counters.virtual_tcp_exchanges.saturating_add(1);
self.count(&mock_request);
let key = format!("{} {}", mock_request.method.as_str(), mock_request.path);
let response = self
.scripts
.get_mut(&key)
.and_then(VecDeque::pop_front)
.unwrap_or_else(|| self.mock.respond(&mock_request));
if mock_request.method == MockMethod::Post
&& mock_request.path.starts_with("/api/v2/statements")
&& response.status == 202
&& String::from_utf8_lossy(&response.body).contains(scenarios::DEFAULT_HANDLE)
{
self.issued_handle = Some(scenarios::DEFAULT_HANDLE.to_owned());
}
mock_to_h1_response(response)
}
fn count(&mut self, request: &MockHttpRequest) {
match (&request.method, request.path.as_str()) {
(MockMethod::Post, "/api/v2/statements") => {
self.counters.plain_submits = self.counters.plain_submits.saturating_add(1);
}
(MockMethod::Post, path) if path.starts_with("/api/v2/statements?") => {
self.counters.retry_submits = self.counters.retry_submits.saturating_add(1);
if path.contains("async=true") {
self.counters.async_submits = self.counters.async_submits.saturating_add(1);
}
}
(MockMethod::Get, path) if path.contains("?partition=") => {
self.counters.partitions = self.counters.partitions.saturating_add(1);
}
(MockMethod::Get, path) if path.starts_with("/api/v2/statements/") => {
self.counters.polls = self.counters.polls.saturating_add(1);
}
(MockMethod::Post, path) if path.ends_with("/cancel") => {
self.counters.cancels = self.counters.cancels.saturating_add(1);
if let Some(handle) = path
.strip_prefix("/api/v2/statements/")
.and_then(|rest| rest.strip_suffix("/cancel"))
{
self.cancelled_handles.insert(handle.to_owned());
}
}
_ => {}
}
}
}
fn perform_virtual_http_exchange(
runtime: &mut LabRuntime,
region: asupersync::types::RegionId,
server_state: Arc<Mutex<RaceServerState>>,
request: H1Request,
) -> Result<MockHttpResponse, RaceError> {
let seed_low = (runtime.config().seed & 0xffff) as u16;
let base_port = 30_000_u16.saturating_add(seed_low % 10_000);
let client_addr = socket_addr(base_port);
let server_addr = socket_addr(base_port.saturating_add(1));
let (client_io, server_io) = VirtualTcpStream::pair(client_addr, server_addr);
let client_result: Arc<Mutex<Option<Result<H1Response, String>>>> = Arc::new(Mutex::new(None));
let server = Http1Server::with_config(
move |request| {
let server_state = Arc::clone(&server_state);
async move {
match server_state.lock() {
Ok(mut state) => state.respond(request),
Err(poisoned) => {
let mut state = poisoned.into_inner();
state.respond(request)
}
}
}
},
Http1Config::default()
.host_policy(HostPolicy::AllowAll)
.keep_alive(false)
.max_requests(Some(1)),
);
let (server_task, _) = runtime
.state
.create_task(region, Budget::INFINITE, async move {
let _ = server.serve(server_io).await;
})
.map_err(|error| RaceError::Lab(format!("server task spawn failed: {error}")))?;
let client_slot = Arc::clone(&client_result);
let (client_task, _) = runtime
.state
.create_task(region, Budget::INFINITE, async move {
let result = Http1Client::request_with_io(client_io, request)
.await
.map(|(response, _, _)| response)
.map_err(|error| error.to_string());
match client_slot.lock() {
Ok(mut slot) => *slot = Some(result),
Err(poisoned) => {
*poisoned.into_inner() = Some(Err("client slot poisoned".to_owned()))
}
}
})
.map_err(|error| RaceError::Lab(format!("client task spawn failed: {error}")))?;
{
let mut scheduler = runtime.scheduler.lock();
scheduler.schedule(server_task, 0);
scheduler.schedule(client_task, 0);
}
runtime.run_until_quiescent();
if !runtime.is_quiescent() {
return Err(RaceError::Truncated);
}
let result = client_result
.lock()
.map_err(|_| RaceError::Poisoned("virtual HTTP client result"))?
.take()
.ok_or_else(|| RaceError::Http("client did not produce a response".to_owned()))?;
let response = result.map_err(RaceError::Http)?;
Ok(h1_to_mock_response(response))
}
fn perform_virtual_http_exchange_raced(
runtime: &mut LabRuntime,
region: asupersync::types::RegionId,
server_state: Arc<Mutex<RaceServerState>>,
request: H1Request,
) -> Result<Option<MockHttpResponse>, RaceError> {
let seed_low = (runtime.config().seed & 0xffff) as u16;
let base_port = 30_000_u16.saturating_add(seed_low % 10_000);
let client_addr = socket_addr(base_port);
let server_addr = socket_addr(base_port.saturating_add(1));
let (client_io, server_io) = VirtualTcpStream::pair(client_addr, server_addr);
let client_result: Arc<Mutex<Option<Result<H1Response, String>>>> = Arc::new(Mutex::new(None));
let server = Http1Server::with_config(
move |request| {
let server_state = Arc::clone(&server_state);
async move {
match server_state.lock() {
Ok(mut state) => state.respond(request),
Err(poisoned) => {
let mut state = poisoned.into_inner();
state.respond(request)
}
}
}
},
Http1Config::default()
.host_policy(HostPolicy::AllowAll)
.keep_alive(false)
.max_requests(Some(1)),
);
let (server_task, _) = runtime
.state
.create_task(region, Budget::INFINITE, async move {
let _ = server.serve(server_io).await;
})
.map_err(|error| RaceError::Lab(format!("server task spawn failed: {error}")))?;
let client_slot = Arc::clone(&client_result);
let (client_task, client_handle) = runtime
.state
.create_task(region, Budget::INFINITE, async move {
let result = Http1Client::request_with_io(client_io, request)
.await
.map(|(response, _, _)| response)
.map_err(|error| error.to_string());
match client_slot.lock() {
Ok(mut slot) => *slot = Some(result),
Err(poisoned) => {
*poisoned.into_inner() = Some(Err("client slot poisoned".to_owned()))
}
}
})
.map_err(|error| RaceError::Lab(format!("client task spawn failed: {error}")))?;
let (canceller_task, _) = runtime
.state
.create_task(region, Budget::INFINITE, async move {
client_handle.abort_with_reason(CancelReason::user(
"cancel races the poll exchange in flight",
));
})
.map_err(|error| RaceError::Lab(format!("canceller task spawn failed: {error}")))?;
{
let mut scheduler = runtime.scheduler.lock();
scheduler.schedule(server_task, 0);
scheduler.schedule(client_task, 0);
scheduler.schedule(canceller_task, 0);
}
runtime.run_until_quiescent();
if !runtime.is_quiescent() {
return Err(RaceError::Truncated);
}
let result = client_result
.lock()
.map_err(|_| RaceError::Poisoned("virtual HTTP client result"))?
.take();
Ok(result.and_then(|response| response.ok().map(h1_to_mock_response)))
}
fn h1_to_mock_request(request: H1Request) -> MockHttpRequest {
MockHttpRequest {
method: match request.method {
H1Method::Get => MockMethod::Get,
H1Method::Post => MockMethod::Post,
other => MockMethod::Other(other.as_str().to_owned()),
},
path: request.uri,
headers: request.headers,
body: request.body,
}
}
fn h1_to_mock_response(response: H1Response) -> MockHttpResponse {
MockHttpResponse {
status: response.status,
headers: response.headers,
body: response.body,
}
}
fn mock_to_h1_response(response: MockHttpResponse) -> H1Response {
H1Response {
version: Version::Http11,
status: response.status,
reason: reason_phrase(response.status).to_owned(),
headers: response.headers,
body: response.body,
trailers: Vec::new(),
}
}
fn submit_request(retry: bool) -> H1Request {
let path = if retry {
"/api/v2/statements?requestId=req-w0i4&retry=true"
} else {
"/api/v2/statements"
};
h1_request(
H1Method::Post,
path,
scenarios::SUBMIT_SELECT_REQUEST.to_vec(),
)
}
fn poll_request(handle: &str) -> H1Request {
h1_request(
H1Method::Get,
format!("/api/v2/statements/{handle}"),
Vec::new(),
)
}
fn partition_request(handle: &str, partition: u32) -> H1Request {
h1_request(
H1Method::Get,
format!("/api/v2/statements/{handle}?partition={partition}"),
Vec::new(),
)
}
fn cancel_request(handle: &str) -> H1Request {
h1_request(
H1Method::Post,
format!("/api/v2/statements/{handle}/cancel"),
Vec::new(),
)
}
fn h1_request(method: H1Method, uri: impl Into<String>, body: Vec<u8>) -> H1Request {
H1Request {
method,
uri: uri.into(),
version: Version::Http11,
headers: vec![("Host".to_owned(), CLIENT_HOST.to_owned())],
body,
trailers: Vec::new(),
peer_addr: None,
}
}
fn retry_schedule(retry_limit: u32, seed: u64) -> Vec<Duration> {
let policy = BackoffPolicy::exponential(
Duration::from_millis(25),
Duration::from_millis(100),
retry_limit,
);
backoff_schedule(&policy, seed)
}
fn is_retryable_status(status: u16) -> bool {
matches!(status, 429 | 500..=599)
}
fn retryable_failure() -> MockHttpResponse {
MockHttpResponse::json(
503,
br#"{"code":"390503","message":"transient overload"}"#.to_vec(),
)
}
fn socket_addr(port: u16) -> SocketAddr {
SocketAddr::from(([127, 0, 0, 1], port))
}
fn duration_millis(duration: Duration) -> u64 {
u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
}
fn trace_fingerprint(runtime: &LabRuntime) -> u64 {
asupersync::trace::trace_fingerprint(&runtime.trace().snapshot())
}
fn replay_command(case: RaceCaseKind, seed: u64) -> String {
format!(
"cargo test -p franken-snowflake-testkit race::{case} -- --exact seed={seed}",
case = case.as_str()
)
}
fn crashpack_manifest(case: RaceCaseKind, seed: u64) -> String {
format!("fsnow-w0i4-{}-{seed}.replay.json", case.as_str())
}
fn failed_report(runtime: &LabRuntime, case: RaceCaseKind, error: RaceError) -> RaceCaseReport {
let seed = runtime.config().seed;
RaceCaseReport {
schema_version: RACE_SUITE_SCHEMA_VERSION,
case,
seed,
virtual_tcp_exchanges: 0,
plain_submits: 0,
retry_submits: 0,
async_submits: 0,
polls: 0,
partitions: 0,
cancels: 0,
retry_delays_ms: Vec::new(),
manual_clock_ms: 0,
completed: false,
cancelled: false,
retry_budget_exhausted: false,
unsafe_submit_retry_refused: false,
no_double_submit: false,
cancel_propagated: false,
bounded_retries: false,
lab_invariants_clean: false,
client_aborted_mid_exchange: false,
no_orphan_statements: false,
no_obligation_leaks: false,
step_capped: false,
certificate_hash: runtime.certificate().hash(),
trace_fingerprint: trace_fingerprint(runtime),
replay_command: replay_command(case, seed),
crashpack_manifest: format!("{}; error={error}", crashpack_manifest(case, seed)),
driver_events: Vec::new(),
}
}
fn poisoned_report(case: RaceCaseKind, seed: u64, name: &'static str) -> RaceCaseReport {
RaceCaseReport {
schema_version: RACE_SUITE_SCHEMA_VERSION,
case,
seed,
virtual_tcp_exchanges: 0,
plain_submits: 0,
retry_submits: 0,
async_submits: 0,
polls: 0,
partitions: 0,
cancels: 0,
retry_delays_ms: Vec::new(),
manual_clock_ms: 0,
completed: false,
cancelled: false,
retry_budget_exhausted: false,
unsafe_submit_retry_refused: false,
no_double_submit: false,
cancel_propagated: false,
bounded_retries: false,
lab_invariants_clean: false,
client_aborted_mid_exchange: false,
no_orphan_statements: false,
no_obligation_leaks: false,
step_capped: false,
certificate_hash: 0,
trace_fingerprint: 0,
replay_command: replay_command(case, seed),
crashpack_manifest: format!("{}; poisoned={name}", crashpack_manifest(case, seed)),
driver_events: Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_race_suite_proves_cancel_retry_invariants() -> Result<(), Box<dyn std::error::Error>>
{
let report = run_default_race_suite()?;
assert!(report.ok(), "race suite report: {report:#?}");
assert_eq!(
report.schedules.len(),
RaceCaseKind::all().len() * DEFAULT_DPOR_RUNS
);
assert!(report.schedules.iter().any(|schedule| schedule.case
== RaceCaseKind::UnsafeSubmitRetryRefusal
&& schedule.unsafe_submit_retry_refused
&& schedule.plain_submits == 1));
assert!(
report
.schedules
.iter()
.filter(|schedule| matches!(
schedule.case,
RaceCaseKind::CancelDuringSubmit
| RaceCaseKind::CancelDuringPoll
| RaceCaseKind::CancelDuringPartitionFetch
| RaceCaseKind::PartialPartitionFailure
| RaceCaseKind::CancelRacesPollExchange
))
.all(|schedule| schedule.step_capped || schedule.cancels >= 1)
);
assert!(
report
.schedules
.iter()
.filter(|schedule| schedule.case == RaceCaseKind::CancelRacesPollExchange)
.all(|schedule| schedule.no_orphan_statements
&& schedule.no_obligation_leaks
&& schedule.cancels >= 1),
"raced-exchange schedules must resolve every statement obligation"
);
let driver: Vec<&RaceCaseReport> = report
.schedules
.iter()
.filter(|schedule| schedule.case == RaceCaseKind::DriverCancelInFlight)
.collect();
assert_eq!(driver.len(), DEFAULT_DPOR_RUNS);
assert!(
driver.iter().all(|schedule| schedule.step_capped
|| (schedule.no_orphan_statements
&& schedule.no_obligation_leaks
&& schedule.lab_invariants_clean)),
"{driver:#?}"
);
assert!(
driver.iter().all(|schedule| schedule.step_capped
|| schedule.completed
|| schedule.cancelled
|| schedule.driver_events.is_empty()),
"{driver:#?}"
);
let landings: BTreeSet<(u32, u32, u32, bool)> = driver
.iter()
.map(|schedule| {
(
schedule.plain_submits + schedule.retry_submits,
schedule.polls,
schedule.cancels,
schedule.completed,
)
})
.collect();
assert!(landings.len() >= 2, "{driver:#?}");
assert!(
driver
.iter()
.any(|schedule| schedule.cancelled && schedule.cancels >= 1),
"{driver:#?}"
);
assert!(
driver.iter().any(|schedule| schedule.async_submits >= 1
&& schedule.cancelled
&& schedule.cancels >= 1
&& schedule.no_double_submit),
"{driver:#?}"
);
let window: Vec<&RaceCaseReport> = report
.schedules
.iter()
.filter(|schedule| schedule.case == RaceCaseKind::DriverCancelDuringPartitions)
.collect();
assert_eq!(window.len(), DEFAULT_DPOR_RUNS);
assert!(
window.iter().all(|schedule| schedule.step_capped
|| (schedule.no_obligation_leaks
&& schedule.lab_invariants_clean
&& schedule.no_double_submit
&& (schedule.completed
|| schedule.cancelled
|| schedule.driver_events.is_empty()))),
"{window:#?}"
);
assert!(
window
.iter()
.any(|schedule| schedule.cancelled && schedule.partitions >= 1),
"{window:#?}"
);
let resubmit: Vec<&RaceCaseReport> = report
.schedules
.iter()
.filter(|schedule| schedule.case == RaceCaseKind::DriverResubmitAfterLostAnswer)
.collect();
assert_eq!(resubmit.len(), DEFAULT_DPOR_RUNS);
assert!(
resubmit.iter().all(|schedule| schedule.step_capped
|| (schedule.completed
&& schedule.no_double_submit
&& schedule.retry_submits == 2
&& schedule.plain_submits == 0
&& schedule.cancels == 0
&& schedule.no_obligation_leaks
&& schedule.lab_invariants_clean)),
"{resubmit:#?}"
);
let jsonl = race_suite_jsonl(&report)?;
assert_eq!(jsonl.lines().count(), report.schedules.len());
Ok(())
}
}