Skip to content

Commit 816c8d3

Browse files
authored
Validate operations on FileHandles (#1113)
* Validate operations on FileHandles This commit allows State to track whether a file handle is valid for read etc operations. Our GC mechanism keeps track of the open-handles but assumes the correctness of callers, meaning if multiple calls to `release` are made at the filesystem level a node may be GC'ed before its time. The State now keeps a record of which FileHandles exist and whether they remain valid, meaning the GC mechanism cannot be misled. REF SMPTNG-532 Signed-off-by: Brian L. Troutwine <[email protected]> * accidentally dropped a counter Signed-off-by: Brian L. Troutwine <[email protected]> * scoot advance time back Signed-off-by: Brian L. Troutwine <[email protected]> * indentation Signed-off-by: Brian L. Troutwine <[email protected]> --------- Signed-off-by: Brian L. Troutwine <[email protected]>
1 parent 326245d commit 816c8d3

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

lading/src/generator/file_gen/logrotate_fs.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -370,14 +370,14 @@ impl Filesystem for LogrotateFS {
370370

371371
counter!("fs_release").increment(1);
372372

373-
// Remove the FileHandle from the mapping
373+
// Remove `fh->FileHandle` from the set of open_files.
374374
let file_handle = {
375375
let mut open_files = self.open_files.lock().expect("lock poisoned");
376376
open_files.remove(&fh)
377377
};
378378

379379
if let Some(file_handle) = file_handle {
380-
// Close the file in the state
380+
// Close the file in the model
381381
state.close_file(tick, file_handle);
382382
reply.ok();
383383
} else {

lading/src/generator/file_gen/logrotate_fs/model.rs

+54
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ pub(crate) struct State {
298298
next_file_handle: u64,
299299
inode_scratch: Vec<Inode>,
300300
load_profile: LoadProfile,
301+
valid_file_handles: FxHashMap<u64, Inode>, // Track valid FileHandle IDs -> Inode
301302
}
302303

303304
impl std::fmt::Debug for State {
@@ -387,6 +388,7 @@ impl State {
387388
next_file_handle: 0,
388389
inode_scratch: Vec::with_capacity(concurrent_logs as usize),
389390
load_profile,
391+
valid_file_handles: FxHashMap::default(),
390392
};
391393

392394
if concurrent_logs == 0 {
@@ -501,6 +503,7 @@ impl State {
501503
file.open(now);
502504
let id = self.next_file_handle;
503505
self.next_file_handle = self.next_file_handle.wrapping_add(1);
506+
self.valid_file_handles.insert(id, inode);
504507
Some(FileHandle { id, inode })
505508
} else {
506509
None
@@ -519,6 +522,7 @@ impl State {
519522

520523
if let Some(Node::File { file, .. }) = self.nodes.get_mut(&handle.inode) {
521524
file.close(now);
525+
self.valid_file_handles.remove(&handle.id);
522526
} else {
523527
panic!("Invalid file handle");
524528
}
@@ -816,6 +820,17 @@ impl State {
816820
) -> Option<Bytes> {
817821
self.advance_time(now);
818822

823+
// Check if the FileHandle is still valid and maps to the correct inode
824+
if let Some(&inode) = self.valid_file_handles.get(&file_handle.id) {
825+
// Doesn't match the node.
826+
if inode != file_handle.inode {
827+
return None;
828+
}
829+
} else {
830+
// No longer valid.
831+
return None;
832+
}
833+
819834
let inode = file_handle.inode;
820835
match self.nodes.get_mut(&inode) {
821836
Some(Node::File { ref mut file }) => {
@@ -1240,6 +1255,45 @@ mod test {
12401255
);
12411256
}
12421257
}
1258+
1259+
// Property 9: Unlinked files remain readable so long as there is a
1260+
// valid file handle.
1261+
//
1262+
// Files that are unlinked and read-only should remain in the state's
1263+
// nodes as long as they have open handles. They should be removed only
1264+
// when open_handles == 0. Reads should still be possible via valid open
1265+
// file handles.
1266+
for (&inode, node) in &state.nodes {
1267+
if let Node::File { file } = node {
1268+
if file.unlinked && file.read_only {
1269+
if file.open_handles > 0 {
1270+
// Should remain in state.nodes
1271+
assert!(state.nodes.contains_key(&inode),
1272+
"Unlinked, read-only file with open handles should remain in state.nodes");
1273+
1274+
// There should be valid file handles pointing to this inode
1275+
let valid_handles: Vec<_> = state
1276+
.valid_file_handles
1277+
.iter()
1278+
.filter_map(|(&handle_id, &fh_inode)| {
1279+
if fh_inode == inode {
1280+
Some(handle_id)
1281+
} else {
1282+
None
1283+
}
1284+
})
1285+
.collect();
1286+
1287+
assert!(!valid_handles.is_empty(),
1288+
"Unlinked, read-only file with open handles should have valid file handles");
1289+
} else {
1290+
// Should be removed from state.nodes after GC
1291+
assert!(!state.nodes.contains_key(&inode),
1292+
"Unlinked, read-only file with zero open handles should be removed from state.nodes");
1293+
}
1294+
}
1295+
}
1296+
}
12431297
}
12441298

12451299
fn compute_expected_bytes_written(

0 commit comments

Comments
 (0)