use super::*;
use gnitz_core::{PkColumn, RelDescriptor, RMW_MAX_ATTEMPTS};
use gnitz_wire::{WireFault, WireStatus};
struct Fixture {
_srv: ServerHandle,
schema_name: String,
a: GnitzClient,
b: GnitzClient,
target: Arc<RelDescriptor>,
}
fn boot() -> Fixture {
let srv = ServerHandle::start_n(2);
let mut a = GnitzClient::connect(srv.sock_path()).unwrap();
let b = GnitzClient::connect(srv.sock_path()).unwrap();
let (schema_name, ..) = create_table(&mut a, schema_of(&[("pk", TypeCode::I64), ("val", TypeCode::I64)]));
let target = a.resolve_relation(&schema_name, "t").unwrap();
commit(&mut a, &target, 1, 0);
Fixture { _srv: srv, schema_name, a, b, target }
}
fn commit(client: &mut GnitzClient, target: &RelDescriptor, pk: u128, val: i64) {
let s = &target.schema;
let mut batch = ZSetBatch::new(s);
BatchAppender::new(&mut batch).add_row(pk, 1).i64_val(val);
client.push(target.tid, s, &batch, WireConflictMode::Update).unwrap();
}
fn run_increment(
f: &mut Fixture,
mut side: impl FnMut(&mut GnitzClient, &RelDescriptor, usize),
) -> (Result<usize, ClientError>, usize) {
let s = Arc::clone(&f.target.schema);
let keys = PkColumn::from_natives(&s, [1]).keys();
let mut attempts = 0;
let Fixture { a, b, target, .. } = f;
let result = a.read_modify_write(target, ReadBound::PkSet(keys), Vec::new(), false, |mut rows| {
side(&mut *b, target, attempts);
attempts += 1;
for r in 0..rows.len() {
let v = read_i64_le(&rows.payload[0].bytes, r * 8);
rows.set_u64_cell(r, 0, (v + 1) as u64);
}
Ok::<_, ClientError>(rows)
});
(result, attempts)
}
fn row_1(client: &mut GnitzClient, target: &RelDescriptor) -> Vec<(i64, i64)> {
let b = scan_all(client, target.tid, &target.schema);
weighted_rows(&b)
.into_iter()
.filter(|&(pk, ..)| pk == 1)
.map(|(_, cells, w)| (cells[0], w))
.collect()
}
#[test]
fn sustained_contention_surfaces_a_conflict_naming_the_table() {
let mut f = boot();
let (result, attempts) = run_increment(&mut f, |b, t, i| commit(b, t, 2, i as i64));
let err = result.expect_err("every attempt conflicts");
assert!(
matches!(
&err,
ClientError::Refused(WireFault { status: WireStatus::TxnConflict, .. })
),
"{err:?}"
);
assert!(err.to_string().contains(&format!("'{}.t'", f.schema_name)), "{err}");
assert_eq!(attempts, RMW_MAX_ATTEMPTS);
assert_eq!(row_1(&mut f.a, &f.target), [(0, 1)]);
}
#[test]
fn a_conflict_retries_over_a_fresh_read() {
let mut f = boot();
let (result, attempts) = run_increment(&mut f, |b, t, i| {
if i == 0 {
commit(b, t, 1, 100);
}
});
assert_eq!(result.unwrap(), 1);
assert_eq!(attempts, 2);
assert_eq!(row_1(&mut f.a, &f.target), [(101, 1)]);
}
#[test]
fn a_write_the_read_saw_is_no_conflict() {
let mut f = boot();
let target = Arc::clone(&f.target);
commit(&mut f.b, &target, 1, 100);
let before = f.a.requests_sent();
let (result, attempts) = run_increment(&mut f, |_, _, _| {});
assert_eq!(result.unwrap(), 1);
assert_eq!(attempts, 1);
assert_eq!(f.a.requests_sent() - before, 2, "SCAN_SPEC and PUSH_TXN");
assert_eq!(row_1(&mut f.a, &target), [(101, 1)]);
}