Skip to content

Commit fea2f27

Browse files
mystenmarkebmifa
andauthored
[Cherry Pick] Fix race condition for transaction cache (#20875) (#20882)
Bug was (likely) as follows: Reader thread | Writer thread (state sync) invalidate tickets get ticket miss cache read database insert to cache insert to cache (racing) ticket is valid panic write to database The writer thread must insert to db first so that the db read cannot find an old value while holding a valid ticket ## Description Describe the changes or additions included in this PR. ## Test plan How did you test the new or updated feature? --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] gRPC: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: Co-authored-by: Eugene Boguslavsky <[email protected]>
1 parent e8141a7 commit fea2f27

File tree

3 files changed

+70
-10
lines changed

3 files changed

+70
-10
lines changed

crates/sui-core/src/execution_cache/cache_types.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::sync::Arc;
88
use std::{cmp::Ordering, hash::DefaultHasher};
99

1010
use moka::sync::Cache as MokaCache;
11+
use mysten_common::debug_fatal;
1112
use parking_lot::Mutex;
1213
use sui_types::base_types::SequenceNumber;
1314

@@ -292,10 +293,12 @@ where
292293
let mut entry = entry.value().lock();
293294
check_ticket()?;
294295

295-
// Ticket expiry makes this assert impossible.
296-
// TODO: relax to debug_assert?
297-
assert!(!entry.is_newer_than(&value), "entry is newer than value");
298-
*entry = value;
296+
// Ticket expiry should make this assert impossible.
297+
if entry.is_newer_than(&value) {
298+
debug_fatal!("entry is newer than value");
299+
} else {
300+
*entry = value;
301+
}
299302
}
300303

301304
Ok(())

crates/sui-core/src/execution_cache/unit_tests/writeback_cache_tests.rs

+57
Original file line numberDiff line numberDiff line change
@@ -1339,3 +1339,60 @@ async fn latest_object_cache_race_test() {
13391339
checker.join().unwrap();
13401340
invalidator.join().unwrap();
13411341
}
1342+
1343+
#[tokio::test]
1344+
async fn test_transaction_cache_race() {
1345+
telemetry_subscribers::init_for_testing();
1346+
1347+
let mut s = Scenario::new(None, Arc::new(AtomicU32::new(0))).await;
1348+
let cache = s.cache.clone();
1349+
let mut txns = Vec::new();
1350+
1351+
for i in 0..1000 {
1352+
let a = i * 4;
1353+
s.with_created(&[a]);
1354+
s.do_tx().await;
1355+
1356+
let outputs = s.take_outputs();
1357+
let tx = (*outputs.transaction).clone();
1358+
let effects = outputs.effects.clone();
1359+
1360+
txns.push((tx, effects));
1361+
}
1362+
1363+
let barrier = Arc::new(std::sync::Barrier::new(2));
1364+
1365+
let t1 = {
1366+
let txns = txns.clone();
1367+
let cache = cache.clone();
1368+
let barrier = barrier.clone();
1369+
std::thread::spawn(move || {
1370+
for (i, (tx, effects)) in txns.into_iter().enumerate() {
1371+
barrier.wait();
1372+
// test both single and multi insert
1373+
if i % 2 == 0 {
1374+
cache.insert_transaction_and_effects(&tx, &effects);
1375+
} else {
1376+
cache.multi_insert_transaction_and_effects(&[VerifiedExecutionData::new(
1377+
tx, effects,
1378+
)]);
1379+
}
1380+
}
1381+
})
1382+
};
1383+
1384+
let t2 = {
1385+
let txns = txns.clone();
1386+
let cache = cache.clone();
1387+
let barrier = barrier.clone();
1388+
std::thread::spawn(move || {
1389+
for (tx, _) in txns {
1390+
barrier.wait();
1391+
cache.get_transaction_block(tx.digest());
1392+
}
1393+
})
1394+
};
1395+
1396+
t1.join().unwrap();
1397+
t2.join().unwrap();
1398+
}

crates/sui-core/src/execution_cache/writeback_cache.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -2268,6 +2268,9 @@ impl StateSyncAPI for WritebackCache {
22682268
transaction: &VerifiedTransaction,
22692269
transaction_effects: &TransactionEffects,
22702270
) {
2271+
self.store
2272+
.insert_transaction_and_effects(transaction, transaction_effects)
2273+
.expect("db error");
22712274
self.cached
22722275
.transactions
22732276
.insert(
@@ -2284,15 +2287,15 @@ impl StateSyncAPI for WritebackCache {
22842287
Ticket::Write,
22852288
)
22862289
.ok();
2287-
self.store
2288-
.insert_transaction_and_effects(transaction, transaction_effects)
2289-
.expect("db error");
22902290
}
22912291

22922292
fn multi_insert_transaction_and_effects(
22932293
&self,
22942294
transactions_and_effects: &[VerifiedExecutionData],
22952295
) {
2296+
self.store
2297+
.multi_insert_transaction_and_effects(transactions_and_effects.iter())
2298+
.expect("db error");
22962299
for VerifiedExecutionData {
22972300
transaction,
22982301
effects,
@@ -2315,8 +2318,5 @@ impl StateSyncAPI for WritebackCache {
23152318
)
23162319
.ok();
23172320
}
2318-
self.store
2319-
.multi_insert_transaction_and_effects(transactions_and_effects.iter())
2320-
.expect("db error");
23212321
}
23222322
}

0 commit comments

Comments
 (0)