fix: reject stale tile rollback generations

This commit is contained in:
torlando-agent[bot]
2026-08-07 01:36:50 +00:00
parent 0ebf5968d9
commit e59c249295
3 changed files with 17 additions and 3 deletions
+9 -1
View File
@@ -597,6 +597,15 @@ TileStoreResult MapTileStore::finishPut() {
return result;
}
const bool transactional_eviction = victim_count != 0U;
if (duplicate) {
// A stale backup must not become the rollback generation recorded by a
// new transaction. Verify its removal before publishing the manifest.
result = storage_.remove(put_backup_);
if ((result != TileStoreResult::OK) && (result != TileStoreResult::MISS)) {
storage_.remove(put_temp_);
return result;
}
}
if (transactional_eviction) {
result = writeEvictionTransaction(put_key_, duplicate, victims, victim_count);
if (result != TileStoreResult::OK) {
@@ -605,7 +614,6 @@ TileStoreResult MapTileStore::finishPut() {
}
}
if (duplicate) {
storage_.remove(put_backup_);
result = storage_.rename(put_live_, put_backup_);
if (result != TileStoreResult::OK) {
if (transactional_eviction) recoverEvictionTransaction();
+7 -1
View File
@@ -231,6 +231,12 @@ void testDuplicateEvictionPowerCutsRestoreOldCandidateAndVictim() { beginTest();
drain(recovered,a,40U); drain(recovered,b,40U);
}
}
void testStaleDuplicateBackupMustClearBeforeManifest() { beginTest(); FakeStorage fs; MapTileStore s(fs,config(2U,80U,80U)); CHECK(s.initialize()==TileStoreResult::OK);
const TileKey a={1U,0U,0U}, b={1U,1U,0U}; CHECK(put(s,a,png())==TileStoreResult::OK); CHECK(put(s,b,png())==TileStoreResult::OK);
fs.add("/pyxis-map/tiles/1/0/0.png.bak",png(60U)); fs.fail_remove_path="/pyxis-map/tiles/1/0/0.png.bak";
CHECK(put(s,a,png(80U))==TileStoreResult::IO_ERROR); fs.fail_remove_path.clear();
CHECK(s.entryCount()==2U); CHECK(s.totalBytes()==80U); drain(s,a,40U); drain(s,b,40U);
}
void testMalformedEvictionManifestFailsClosed() { beginTest(); FakeStorage fs;
fs.add("/pyxis-map/tiles/.evict.txn",std::vector<std::uint8_t>(21U,0U));
MapTileStore s(fs,config()); CHECK(s.initialize()==TileStoreResult::INDEX_MISMATCH);
@@ -249,4 +255,4 @@ void testDeterministicStress() { beginTest(); FakeStorage fs; MapTileStore s(fs,
std::uint32_t size=0U; for(std::uint32_t i=0U;i<100000U;++i) { const TileKey k={2U,i&3U,(i>>2)&3U}; TileStoreResult r=s.beginGet(k,size); CHECK(r==TileStoreResult::OK||r==TileStoreResult::MISS); if(r==TileStoreResult::OK)s.endGet(); }
}
}
int main() { testKeyAndCanonicalPath(); testMissHitAndRemoval(); testMalformedPngs(); testShortWriteAbortsTemp(); testExactQuotaAndLruEviction(); testDuplicateAtomicReplacement(); testInterruptedFilesRecover(); testLiveWinsRecovery(); testCorruptLiveRecoversValidBackup(); testCorruptLiveWithoutBackupIsRemoved(); testStaleTempRemovalFailureAbortsPut(); testRecoveryRejectsMalformedAndExhaustion(); testRecoveryQuotaFailsClosed(); testRenameFailureRestoresDuplicate(); testPromotionFailureDoesNotEvictVictims(); testEvictionPreflightFailurePreservesAllVictims(); testEvictionStageFailureRollsBackAllVictims(); testEvictionPowerCutsRestoreWholeOldGeneration(); testDuplicateEvictionPowerCutsRestoreOldCandidateAndVictim(); testMalformedEvictionManifestFailsClosed(); testPostCommitCleanupResidueKeepsWholeNewGeneration(); testDeterministicStress(); std::cout<<"map tile store: "<<tests_run<<" tests passed\n"; }
int main() { testKeyAndCanonicalPath(); testMissHitAndRemoval(); testMalformedPngs(); testShortWriteAbortsTemp(); testExactQuotaAndLruEviction(); testDuplicateAtomicReplacement(); testInterruptedFilesRecover(); testLiveWinsRecovery(); testCorruptLiveRecoversValidBackup(); testCorruptLiveWithoutBackupIsRemoved(); testStaleTempRemovalFailureAbortsPut(); testRecoveryRejectsMalformedAndExhaustion(); testRecoveryQuotaFailsClosed(); testRenameFailureRestoresDuplicate(); testPromotionFailureDoesNotEvictVictims(); testEvictionPreflightFailurePreservesAllVictims(); testEvictionStageFailureRollsBackAllVictims(); testEvictionPowerCutsRestoreWholeOldGeneration(); testDuplicateEvictionPowerCutsRestoreOldCandidateAndVictim(); testStaleDuplicateBackupMustClearBeforeManifest(); testMalformedEvictionManifestFailsClosed(); testPostCommitCleanupResidueKeepsWholeNewGeneration(); testDeterministicStress(); std::cout<<"map tile store: "<<tests_run<<" tests passed\n"; }
+1 -1
View File
@@ -29,4 +29,4 @@ def test_bounded_map_tile_store(tmp_path: Path, sanitize: bool) -> None:
env["UBSAN_OPTIONS"] = "halt_on_error=1:print_stacktrace=1"
ran = subprocess.run([str(binary)], capture_output=True, text=True, timeout=60, env=env)
assert ran.returncode == 0, ran.stdout + ran.stderr
assert ran.stdout == "map tile store: 22 tests passed\n"
assert ran.stdout == "map tile store: 23 tests passed\n"