Skip to content

Commit

Permalink
txn: make prewrite idempotent after a new lock is written (#12369) (#…
Browse files Browse the repository at this point in the history
…12377)

ref #12369, close pingcap/tidb#34066

If a retried prewrite encounters a newer lock, the result may be not
idempotent.

This happens when the first prewrite succeeds and the transaction
is committed by resolving async-commit locks. Then, TiKV returns
KeyIsLocked in the retry while the first prewrite succeeds.

If the client does not handle it carefully, the client may report a false
commit failure to the user.

This commit fixes the bug, making prewrite idempotent in this case.

Signed-off-by: ti-srebot <ti-srebot@pingcap.com>
Signed-off-by: Yilin Chen <sticnarf@gmail.com>

Co-authored-by: Yilin Chen <sticnarf@gmail.com>
Co-authored-by: Ti Chi Robot <ti-community-prow-bot@tidb.io>
  • Loading branch information
3 people committed Jun 14, 2022
1 parent acc800e commit 43456c6
Showing 1 changed file with 55 additions and 8 deletions.
63 changes: 55 additions & 8 deletions src/storage/txn/commands/prewrite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,12 +510,11 @@ impl<K: PrewriteKind> Prewriter<K> {
txn.guards = Vec::new();
final_min_commit_ts = TimeStamp::zero();
}
e @ Err(MvccError(box MvccErrorInner::KeyIsLocked { .. })) => {
locks.push(
e.map(|_| ())
.map_err(Error::from)
.map_err(StorageError::from),
);
Err(MvccError(box MvccErrorInner::KeyIsLocked { .. })) => {
match check_committed_record_on_err(prewrite_result, txn, reader, &key) {
Ok(res) => return Ok(res),
Err(e) => locks.push(Err(e.into())),
}
}
Err(e) => return Err(Error::from(e)),
}
Expand Down Expand Up @@ -814,7 +813,7 @@ mod tests {
)
.err()
.unwrap();
assert_eq!(2, statistic.write.seek);
assert_eq!(3, statistic.write.seek);
match e {
Error(box ErrorInner::Mvcc(MvccError(box MvccErrorInner::KeyIsLocked(_)))) => (),
_ => panic!("error type not match"),
Expand All @@ -827,7 +826,7 @@ mod tests {
102,
)
.unwrap();
assert_eq!(2, statistic.write.seek);
assert_eq!(3, statistic.write.seek);
let e = prewrite(
&engine,
&mut statistic,
Expand Down Expand Up @@ -1905,4 +1904,52 @@ mod tests {
let snap = engine.snapshot(Default::default()).unwrap();
assert!(prewrite_cmd.cmd.process_write(snap, context).is_err());
}

#[test]
fn test_prewrite_committed_encounter_newer_lock() {
let engine = TestEngineBuilder::new().build().unwrap();
let mut statistics = Statistics::default();

let k1 = b"k1";
let v1 = b"v1";
let v2 = b"v2";

must_prewrite_put_async_commit(&engine, k1, v1, k1, &Some(vec![]), 5, 10);
// This commit may actually come from a ResolveLock command
must_commit(&engine, k1, 5, 15);

// Another transaction prewrites
must_prewrite_put(&engine, k1, v2, k1, 20);

// A retried prewrite of the first transaction should be idempotent.
let prewrite_cmd = Prewrite::new(
vec![Mutation::Put((Key::from_raw(k1), v1.to_vec()))],
k1.to_vec(),
5.into(),
2000,
false,
1,
5.into(),
1000.into(),
Some(vec![]),
false,
Context::default(),
);
let context = WriteContext {
lock_mgr: &DummyLockManager {},
concurrency_manager: ConcurrencyManager::new(20.into()),
extra_op: ExtraOp::Noop,
statistics: &mut statistics,
async_apply_prewrite: false,
};
let snap = engine.snapshot(Default::default()).unwrap();
let res = prewrite_cmd.cmd.process_write(snap, context).unwrap();
match res.pr {
ProcessResult::PrewriteResult { result } => {
assert!(result.locks.is_empty(), "{:?}", result);
assert_eq!(result.min_commit_ts, 15.into(), "{:?}", result);
}
_ => panic!("unexpected result {:?}", res.pr),
}
}
}

0 comments on commit 43456c6

Please sign in to comment.