Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3040,6 +3040,13 @@ fn main() {
if let Ok(case) = std::env::var("DRAGONOS_EXT4_RECOVERY_CASE") {
let persistence = PersistenceModel::WriteBack;
match case.as_str() {
"orphan-index" => {
for model in [PersistenceModel::WriteBack, PersistenceModel::WriteThrough] {
run_journal_reclaim_matrix(model);
run_journal_linked_tail_final_unlink_matrix(model);
run_journal_linked_tail_rename_matrix(model);
}
}
"reserved-payload-retry" => run_reserved_delalloc_payload_retry_test(persistence),
"production-append" => run_production_delalloc_append_block_test(persistence),
"production-split" => run_production_delalloc_extent_split_test(persistence),
Expand Down
6 changes: 3 additions & 3 deletions kernel/crates/another_ext4/src/ext4/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2051,9 +2051,9 @@ impl Ext4 {
}
// Each iteration starts from the checkpointed inode-table entry. The
// on-disk extent root is therefore the restart cursor after any crash.
// Chain membership was fully validated once above. The metadata write
// barrier keeps the chain stable, avoiding O(extents * orphan_count)
// repeated walks; final orphan_del performs its own bounded walk.
// Membership comes from the validated, transaction-maintained index.
// The metadata write barrier keeps the chain stable throughout reclaim;
// final orphan_del rechecks the target and indexed predecessor images.
loop {
let mut inode = self.validate_reclaim_inode(inode_id, generation)?;
if !inode.inode.uses_extents() {
Expand Down
4 changes: 4 additions & 0 deletions kernel/crates/another_ext4/src/ext4/extent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2385,6 +2385,8 @@ mod tests {
Ext4 {
block_device,
metadata_cache: crate::ext4::MetadataBlockCache::new(16),
orphan_index: spin::Mutex::new(crate::ext4::orphan::LegacyOrphanIndex::default()),
orphan_index_valid: Arc::new(core::sync::atomic::AtomicBool::new(false)),
cached_super_block: spin::Mutex::new(sb),
cached_block_groups: Vec::new(),
system_metadata_ranges: Vec::new(),
Expand Down Expand Up @@ -2434,6 +2436,8 @@ mod tests {
Ext4 {
block_device,
metadata_cache: crate::ext4::MetadataBlockCache::new(16),
orphan_index: spin::Mutex::new(crate::ext4::orphan::LegacyOrphanIndex::default()),
orphan_index_valid: Arc::new(core::sync::atomic::AtomicBool::new(false)),
cached_super_block: spin::Mutex::new(sb),
cached_block_groups: Vec::new(),
system_metadata_ranges: Vec::new(),
Expand Down
2 changes: 2 additions & 0 deletions kernel/crates/another_ext4/src/ext4/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ impl Ext4 {
{
return Err(Ext4Error::new(ErrCode::EIO));
}
self.orphan_index_valid
.store(false, core::sync::atomic::Ordering::Release);
ext4_sb = recovered_sb;
*self.cached_super_block.lock() = recovered_sb;
self.cached_block_groups = recovered_groups;
Expand Down
94 changes: 94 additions & 0 deletions kernel/crates/another_ext4/src/ext4/journal_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ pub struct Transaction<'a> {
retired: Vec<RetiredRange>,
preserve_originals: bool,
owns_writer: bool,
orphan_index: Option<Arc<AtomicBool>>,
}

impl Transaction<'_> {
Expand All @@ -358,8 +359,19 @@ impl Transaction<'_> {
retired: Vec::new(),
preserve_originals,
owns_writer: true,
orphan_index: None,
}
}
/// The caller owns the exclusive metadata gate until this transaction is
/// consumed. Index changes are provisional until logical publication.
pub(super) fn track_orphan_index(&mut self, valid: Arc<AtomicBool>) {
if let Some(previous) = &self.orphan_index {
debug_assert!(Arc::ptr_eq(previous, &valid));
} else {
self.orphan_index = Some(valid);
}
}

/// Replace the final image for `home`. Re-staging the same home block does
/// not consume another credit and subsequent reads observe the replacement.
pub fn stage(&mut self, home: PBlockId, image: Box<[u8; BLOCK_SIZE]>) -> Result<()> {
Expand Down Expand Up @@ -514,6 +526,9 @@ impl Transaction<'_> {
return Err(Ext4Error::new(ErrCode::EINVAL));
};
let result = core.publish(&mut self.staged, &mut self.retired, publisher);
if result.is_ok() {
self.orphan_index = None;
}
self.release_writer();
result
}
Expand Down Expand Up @@ -620,6 +635,7 @@ impl Transaction<'_> {
return self.fail(error, CommitFailure::CommitUncertain, true);
}
publisher.publish_home_current(&self.staged, &self.retired);
self.orphan_index = None;
self.release_writer();
Ok(())
}
Expand Down Expand Up @@ -669,6 +685,7 @@ impl Transaction<'_> {
}
}
publisher.publish_home_current(&self.staged, &self.retired);
self.orphan_index = None;
self.release_writer();
Ok(())
}
Expand All @@ -693,6 +710,9 @@ impl Transaction<'_> {
let result = core.commit_images(device, &images, || {
publisher.publish_home_current(&self.staged, &self.retired)
});
if result.is_ok() {
self.orphan_index = None;
}
self.release_writer();
result
}
Expand Down Expand Up @@ -1013,6 +1033,9 @@ impl JournalTransactionCore {

impl Drop for Transaction<'_> {
fn drop(&mut self) {
if let Some(valid) = self.orphan_index.take() {
valid.store(false, Ordering::Release);
}
self.release_writer();
}
}
Expand Down Expand Up @@ -1399,6 +1422,77 @@ mod tests {
}
}

#[test]
fn orphan_index_is_invalidated_by_abort_drop_and_commit_failure() {
for action in 0..3 {
let valid = Arc::new(AtomicBool::new(true));
let device = MemoryDevice::new();
let publisher = Publisher(AtomicUsize::new(0));
let core = DirectTransactionCore::new(128).unwrap();
let mut operation = core.start(1).unwrap();
operation.track_orphan_index(valid.clone());
operation.stage(2, Box::new([2; BLOCK_SIZE])).unwrap();
match action {
0 => operation.abort(),
1 => drop(operation),
_ => {
device.fail_at.store(0, Ordering::SeqCst);
assert!(operation.commit(&device, &publisher).is_err());
}
}
assert!(!valid.load(Ordering::Acquire));
}
}

#[test]
fn orphan_index_survives_successful_logical_publication() {
let device = MemoryDevice::new();
let publisher = Publisher(AtomicUsize::new(0));
let direct = DirectTransactionCore::new(128).unwrap();
let valid = Arc::new(AtomicBool::new(true));
let mut operation = direct.start(1).unwrap();
operation.track_orphan_index(valid.clone());
operation.stage(2, Box::new([2; BLOCK_SIZE])).unwrap();
operation.commit(&device, &publisher).unwrap();
assert!(valid.load(Ordering::Acquire));

let journal = JournalTransactionCore::new(context_with_ring(64, 1)).unwrap();
let mut operation = staged_journal_transaction(&journal, 1);
operation.track_orphan_index(valid.clone());
operation.commit(&device, &publisher).unwrap();
assert!(valid.load(Ordering::Acquire));

let batch = JournalBatchCore::new(context_with_ring(64, 1), 32).unwrap();
let mut operation = batch.start(1).unwrap();
operation.track_orphan_index(valid.clone());
operation.stage(2, Box::new([3; BLOCK_SIZE])).unwrap();
operation.publish(&publisher).unwrap();
assert!(valid.load(Ordering::Acquire));
// A newer operation's abort invalidates the current logical index;
// checkpointing the older accepted batch must not revive it.
let mut aborted = batch.start(1).unwrap();
aborted.track_orphan_index(valid.clone());
aborted.abort();
batch.request_seal();
batch.commit_pending(&device).unwrap();
assert!(!valid.load(Ordering::Acquire));
}

#[test]
fn orphan_index_is_invalidated_at_every_journal_failure_boundary() {
for failure in 0..14 {
let device = MemoryDevice::new();
device.fail_at.store(failure, Ordering::SeqCst);
let publisher = Publisher(AtomicUsize::new(0));
let journal = JournalTransactionCore::new(context_with_ring(64, 1)).unwrap();
let valid = Arc::new(AtomicBool::new(true));
let mut operation = staged_journal_transaction(&journal, 1);
operation.track_orphan_index(valid.clone());
let result = operation.commit(&device, &publisher);
assert_eq!(valid.load(Ordering::Acquire), result.is_ok());
}
}

#[test]
fn direct_commit_writes_home_blocks_in_order_then_publishes() {
let device = MemoryDevice::new();
Expand Down
2 changes: 2 additions & 0 deletions kernel/crates/another_ext4/src/ext4/low_level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4605,6 +4605,8 @@ mod tests {
Ext4 {
block_device,
metadata_cache: crate::ext4::MetadataBlockCache::new(16),
orphan_index: spin::Mutex::new(crate::ext4::orphan::LegacyOrphanIndex::default()),
orphan_index_valid: Arc::new(core::sync::atomic::AtomicBool::new(false)),
cached_super_block: spin::Mutex::new(sb),
cached_block_groups: Vec::new(),
system_metadata_ranges: Vec::new(),
Expand Down
4 changes: 4 additions & 0 deletions kernel/crates/another_ext4/src/ext4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ pub struct Ext4 {
/// Bounded raw metadata acceleration. Journal overlays remain the
/// authoritative accepted view; this cache is always discardable.
metadata_cache: MetadataBlockCache,
orphan_index: spin::Mutex<orphan::LegacyOrphanIndex>,
orphan_index_valid: Arc<AtomicBool>,
/// Cached superblock to avoid repeated disk reads.
/// The superblock is loaded once at mount time and updated
/// in memory whenever it is written to disk.
Expand Down Expand Up @@ -818,6 +820,8 @@ impl Ext4 {
Ok(Self {
block_device,
metadata_cache: MetadataBlockCache::new(0),
orphan_index: spin::Mutex::new(orphan::LegacyOrphanIndex::default()),
orphan_index_valid: Arc::new(AtomicBool::new(false)),
cached_super_block: spin::Mutex::new(sb),
cached_block_groups,
system_metadata_ranges,
Expand Down
Loading
Loading