use std::process::ExitCode;
use std::time::{Duration, Instant};
use ytsaurus_client::{Client, ClientError};
const BASE: &str = "//tmp/ytsaurus_rs_detach";
const SHORT: Duration = Duration::from_secs(3);
const HELD: Duration = Duration::from_secs(7);
fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("\ndetach failed: {e}");
ExitCode::FAILURE
}
}
}
fn run() -> Result<(), ClientError> {
let first = Client::from_env()?;
let second = Client::from_env()?;
step("Preparing Cypress");
first.remove_tree(BASE)?;
first.create("map_node", BASE)?;
let staging = format!("{BASE}/staging");
done("clean slate");
step(&format!(
"Starting a {}s transaction and detaching",
SHORT.as_secs()
));
let started = Instant::now();
let tx = first.start_transaction_with(SHORT)?;
tx.create("table", &staging)?;
check("the transaction sees its table", tx.exists(&staging)?)?;
let id = tx.detach(); println!(" detached {id}");
check(
"the first client no longer sees the table",
!first.exists(&staging)?,
)?;
step("Attaching from the second client");
let attached = second.attach_transaction(&id)?;
check(
"the attached handle sees the table",
attached.exists(&staging)?,
)?;
step(&format!(
"Holding it for {}s — past its own timeout",
HELD.as_secs()
));
std::thread::sleep(HELD);
attached.commit()?;
check(
&format!(
"committed by the second client, {:.0}s after the start",
started.elapsed().as_secs_f64()
),
first.exists(&staging)?,
)?;
step("An attached handle dropped mid-work leaves the transaction alive");
let orphan = {
let tx = first.start_transaction()?;
tx.detach()
};
{
let attached = second.attach_transaction(&orphan)?;
attached.ping()?;
} second.ping_transaction(&orphan)?;
done("still answers a ping after the attached handle dropped");
step("A bare id is enough to finish it");
second.abort_transaction(&orphan)?;
match second.ping_transaction(&orphan) {
Ok(()) => {
return Err(ClientError::Config(
"a ping succeeded after the abort, which means the abort did not happen".to_owned(),
));
}
Err(e) => done(&format!("aborted by id; as expected: {e}")),
}
step("A started handle dropped mid-work still aborts — unchanged");
let watched = {
let tx = first.start_transaction()?;
tx.id().to_owned()
}; match second.ping_transaction(&watched) {
Ok(()) => {
return Err(ClientError::Config(
"a started handle's drop no longer aborts its transaction".to_owned(),
));
}
Err(e) => done(&format!("as expected: {e}")),
}
println!("\nA transaction outlived its handle and finished in other hands.");
println!("Tables left at {BASE}");
Ok(())
}
fn step(what: &str) {
println!("\n== {what}");
}
fn done(what: &str) {
println!(" ok {what}");
}
fn check(what: &str, passed: bool) -> Result<(), ClientError> {
if passed {
done(what);
return Ok(());
}
eprintln!(" FAIL {what}");
Err(ClientError::Config(format!("check failed: {what}")))
}