Skip to content

Commit d28605d

Browse files
committed
Fix handling of swap files
1 parent 1f9b3a1 commit d28605d

File tree

1 file changed

+67
-101
lines changed
  • sequencer/src/persistence

1 file changed

+67
-101
lines changed

sequencer/src/persistence/fs.rs

+67-101
Original file line numberDiff line numberDiff line change
@@ -222,41 +222,36 @@ impl Inner {
222222
fn collect_garbage(
223223
&mut self,
224224
view: ViewNumber,
225-
intervals: &[RangeInclusive<u64>],
225+
intervals: &[RangeInclusive<ViewNumber>],
226226
) -> anyhow::Result<()> {
227-
let view_number = view.u64();
228-
let prune_view = view.saturating_sub(self.view_retention);
229-
230-
let delete_files =
231-
|intervals: &[RangeInclusive<u64>], keep, dir_path: PathBuf| -> anyhow::Result<()> {
232-
if !dir_path.is_dir() {
233-
return Ok(());
234-
}
227+
let view_number = view;
228+
let prune_view = ViewNumber::new(view.saturating_sub(self.view_retention));
229+
230+
let delete_files = |intervals: &[RangeInclusive<ViewNumber>],
231+
keep,
232+
dir_path: PathBuf|
233+
-> anyhow::Result<()> {
234+
if !dir_path.is_dir() {
235+
return Ok(());
236+
}
235237

236-
for entry in fs::read_dir(dir_path)? {
237-
let entry = entry?;
238-
let path = entry.path();
239-
240-
if let Some(file) = path.file_stem().and_then(|n| n.to_str()) {
241-
if let Ok(v) = file.parse::<u64>() {
242-
// If the view is the anchor view, keep it no matter what.
243-
if let Some(keep) = keep {
244-
if keep == v {
245-
continue;
246-
}
247-
}
248-
// Otherwise, delete it if it is time to prune this view _or_ if the
249-
// given intervals, which we've already successfully processed, contain
250-
// the view; in this case we simply don't need it anymore.
251-
if v < prune_view || intervals.iter().any(|i| i.contains(&v)) {
252-
fs::remove_file(&path)?;
253-
}
254-
}
238+
for (v, path) in view_files(dir_path)? {
239+
// If the view is the anchor view, keep it no matter what.
240+
if let Some(keep) = keep {
241+
if keep == v {
242+
continue;
255243
}
256244
}
245+
// Otherwise, delete it if it is time to prune this view _or_ if the given
246+
// intervals, which we've already successfully processed, contain the view; in
247+
// this case we simply don't need it anymore.
248+
if v < prune_view || intervals.iter().any(|i| i.contains(&v)) {
249+
fs::remove_file(&path)?;
250+
}
251+
}
257252

258-
Ok(())
259-
};
253+
Ok(())
254+
};
260255

261256
delete_files(intervals, None, self.da_dir_path())?;
262257
delete_files(intervals, None, self.vid_dir_path())?;
@@ -276,22 +271,13 @@ impl Inner {
276271
&self,
277272
view: ViewNumber,
278273
consumer: &impl EventConsumer,
279-
) -> anyhow::Result<Vec<RangeInclusive<u64>>> {
274+
) -> anyhow::Result<Vec<RangeInclusive<ViewNumber>>> {
280275
// Generate a decide event for each leaf, to be processed by the event consumer. We make a
281276
// separate event for each leaf because it is possible we have non-consecutive leaves in our
282277
// storage, which would not be valid as a single decide with a single leaf chain.
283278
let mut leaves = BTreeMap::new();
284-
for entry in fs::read_dir(self.decided_leaf_path())? {
285-
let entry = entry?;
286-
let path = entry.path();
287-
288-
let Some(file) = path.file_stem().and_then(|n| n.to_str()) else {
289-
continue;
290-
};
291-
let Ok(v) = file.parse::<u64>() else {
292-
continue;
293-
};
294-
if v > view.u64() {
279+
for (v, path) in view_files(self.decided_leaf_path())? {
280+
if v > view {
295281
continue;
296282
}
297283

@@ -302,22 +288,20 @@ impl Inner {
302288
.context(format!("parsing decided leaf {}", path.display()))?;
303289

304290
// Include the VID share if available.
305-
let vid_share = self
306-
.load_vid_share(ViewNumber::new(v))?
307-
.map(|proposal| proposal.data);
291+
let vid_share = self.load_vid_share(v)?.map(|proposal| proposal.data);
308292
if vid_share.is_none() {
309-
tracing::debug!(view = v, "VID share not available at decide");
293+
tracing::debug!(?v, "VID share not available at decide");
310294
}
311295

312296
// Fill in the full block payload using the DA proposals we had persisted.
313-
if let Some(proposal) = self.load_da_proposal(ViewNumber::new(v))? {
297+
if let Some(proposal) = self.load_da_proposal(v)? {
314298
let payload = Payload::from_bytes(
315299
&proposal.data.encoded_transactions,
316300
&proposal.data.metadata,
317301
);
318302
leaf.fill_block_payload_unchecked(payload);
319303
} else {
320-
tracing::debug!(view = v, "DA proposal not available at decide");
304+
tracing::debug!(?v, "DA proposal not available at decide");
321305
}
322306

323307
let info = LeafInfo {
@@ -339,7 +323,7 @@ impl Inner {
339323
if let Some((oldest_view, _)) = leaves.first_key_value() {
340324
// The only exception is when the oldest leaf is the genesis leaf; then there was no
341325
// previous decide event.
342-
if *oldest_view > 0 {
326+
if *oldest_view > ViewNumber::genesis() {
343327
leaves.pop_first();
344328
}
345329
}
@@ -350,7 +334,7 @@ impl Inner {
350334
let height = leaf.leaf.block_header().block_number();
351335
consumer
352336
.handle_event(&Event {
353-
view_number: ViewNumber::new(view),
337+
view_number: view,
354338
event: EventType::Decide {
355339
qc: Arc::new(qc.to_qc2()),
356340
leaf_chain: Arc::new(vec![leaf]),
@@ -423,15 +407,12 @@ impl Inner {
423407
let mut anchor: Option<(Leaf2, QuorumCertificate2<SeqTypes>)> = None;
424408

425409
// Return the latest decided leaf.
426-
for entry in
427-
fs::read_dir(self.decided_leaf_path()).context("opening decided leaf directory")?
428-
{
429-
let file = entry.context("reading decided leaf directory")?.path();
410+
for (_, path) in view_files(self.decided_leaf_path())? {
430411
let bytes =
431-
fs::read(&file).context(format!("reading decided leaf {}", file.display()))?;
412+
fs::read(&path).context(format!("reading decided leaf {}", path.display()))?;
432413
let (leaf, qc) =
433414
bincode::deserialize::<(Leaf, QuorumCertificate<SeqTypes>)>(&bytes)
434-
.context(format!("parsing decided leaf {}", file.display()))?;
415+
.context(format!("parsing decided leaf {}", path.display()))?;
435416
if let Some((anchor_leaf, _)) = &anchor {
436417
if leaf.view_number() > anchor_leaf.view_number() {
437418
let leaf2 = leaf.into();
@@ -760,47 +741,10 @@ impl SequencerPersistence for Persistence {
760741
return Ok(Default::default());
761742
}
762743

763-
// Then, we want to get the entries in this directory since they'll be the
764-
// key/value pairs for our map.
765-
let files = fs::read_dir(dir_path.clone())?.filter_map(|entry| {
766-
let entry = entry.ok()?;
767-
if entry.file_type().ok()?.is_file() && entry.path().extension()? == "txt" {
768-
Some(entry.path())
769-
} else {
770-
None
771-
}
772-
});
773-
744+
// Read quorum proposals from every data file in this directory.
774745
let mut map = BTreeMap::new();
775-
for file in files {
776-
// Parse each file into a proposal if possible. We ignore files we don't recognize or
777-
// can't parse, as sometimes we can end up with random extra files (e.g. swap files) in
778-
// the directory.
779-
//
780-
// Get the stem to remove the ".txt" from the end.
781-
let Some(file_name) = file.file_stem() else {
782-
continue;
783-
};
784-
785-
// Parse the filename (which corresponds to the view)
786-
let file_name = file_name.to_string_lossy();
787-
let Ok(view_number) = file_name.parse::<u64>() else {
788-
tracing::info!(
789-
%file_name,
790-
"ignoring extraneous file in quorum proposals directory"
791-
);
792-
continue;
793-
};
794-
795-
// Now, we'll try and load the proposal associated with this function. In this case we
796-
// do propagate errors: errors from the file system are more likely to be some transient
797-
// issue (e.g. failure to connect to a network-mounted file system) than an issue with
798-
// the file itself (like a swap file being left over in the directory). Thus, this file
799-
// likely does have good data even if we can't read it, so we do want to propagate the
800-
// error and eventually retry.
801-
let proposal_bytes = fs::read(file)?;
802-
803-
// Then, deserialize.
746+
for (view, path) in view_files(&dir_path)? {
747+
let proposal_bytes = fs::read(path)?;
804748
let proposal: Proposal<SeqTypes, QuorumProposal<SeqTypes>> =
805749
match bincode::deserialize(&proposal_bytes) {
806750
Ok(proposal) => proposal,
@@ -811,17 +755,14 @@ impl SequencerPersistence for Persistence {
811755
// many proposals as we can rather than letting one bad proposal cause the
812756
// entire operation to fail, and it is still possible that this was just
813757
// some unintended file whose name happened to match the naming convention.
814-
tracing::warn!(
815-
view_number,
816-
"ignoring malformed quorum proposal file: {err:#}"
817-
);
758+
tracing::warn!(?view, "ignoring malformed quorum proposal file: {err:#}");
818759
continue;
819760
}
820761
};
821762
let proposal2 = convert_proposal(proposal);
822763

823764
// Push to the map and we're done.
824-
map.insert(ViewNumber::new(view_number), proposal2);
765+
map.insert(view, proposal2);
825766
}
826767

827768
Ok(map)
@@ -987,6 +928,31 @@ fn migrate_network_config(
987928
Ok(network_config)
988929
}
989930

931+
/// Get all paths under `dir` whose name is of the form <view number>.txt.
932+
fn view_files(
933+
dir: impl AsRef<Path>,
934+
) -> anyhow::Result<impl Iterator<Item = (ViewNumber, PathBuf)>> {
935+
Ok(fs::read_dir(dir.as_ref())?.filter_map(move |entry| {
936+
let dir = dir.as_ref().display();
937+
let entry = entry.ok()?;
938+
if !entry.file_type().ok()?.is_file() {
939+
tracing::debug!(%dir, ?entry, "ignoring non-file in data directory");
940+
return None;
941+
}
942+
let path = entry.path();
943+
if path.extension()? != "txt" {
944+
tracing::debug!(%dir, ?entry, "ignoring non-text file in data directory");
945+
return None;
946+
}
947+
let file_name = path.file_stem()?;
948+
let Ok(view_number) = file_name.to_string_lossy().parse::<u64>() else {
949+
tracing::debug!(%dir, ?file_name, "ignoring extraneous file in data directory");
950+
return None;
951+
};
952+
Some((ViewNumber::new(view_number), entry.path().to_owned()))
953+
}))
954+
}
955+
990956
#[cfg(test)]
991957
mod testing {
992958
use tempfile::TempDir;

0 commit comments

Comments
 (0)