fix(fuse): read the size again for a baseline row without a revision id

A mirror upload that Drive did not list yet when it was read back left a
baseline row with no revision id. The next walk reused the recorded size
whenever the modification time matched, and a row without an id never
counts as moved. So a revision another computer saved in the same second
looked unchanged and never arrived (B254). The B250 simulation hit this
about one run in five on CI.

A row without an id now reuses its size only while Drive gives no id
either. Otherwise the walk reads the size, and an unchanged file gets
Drive's revision id written into its row, so each row costs one extra
read. Rows from before schema 44 are read once after upgrading.
This commit is contained in:
nvrl committed 2026-10-16 18:44:51 +02:00
1 parent c1911ea1de
commit 8f771fdcc4
4 files changed
+67 -2

No files matched your search

+17 -1
View File
@@ -776,7 +776,23 @@ impl Core {
}
match plan_file(l, r, baseline.get(rel)) {
FilePlan::Unchanged => {}
// The size was read for real for a row without a revision id
// (B254), and still matches: give the row the id, so later
// walks trust the recorded size again.
FilePlan::Unchanged => {
if let (Some(base), Some(revision)) =
(baseline.get(rel), r.and_then(|r| r.revision.as_ref()))
&& base.remote_revision_id.is_none()
{
let row = StoredSyncEntry {
remote_revision_id: Some(revision.clone()),
..base.clone()
};
if let Err(e) = self.db.sync_entry_upsert(folder_id, &row) {
debug!(rel, error = ?e, "sync: cannot record the revision id");
}
}
}
FilePlan::AdoptBaseline => {
// First sync of a file the same size on both sides: the apply
// step hashes the local copy and records the baseline only if
+19 -1
View File
@@ -333,6 +333,13 @@ pub(super) fn remote_subtree_changed(
/// The size the baseline recorded for `rel`, if its remote signature's mtime
/// still matches `mtime` and its revision is still `revision`.
///
/// A row without a revision id, while Drive reports one, cannot vouch for the
/// revision, so the size is read for real. Such a row comes from before
/// schema 44, or from an upload Drive did not list yet when it was read back.
/// Another computer's revision in the same second as that upload kept the
/// modification time, and only the size could show it (B254). The pass then
/// records the id on the row, so this costs one read per row.
pub(super) fn unchanged_remote_size(
baseline: &HashMap<String, StoredSyncEntry>,
rel: &str,
@@ -340,7 +347,9 @@ pub(super) fn unchanged_remote_size(
revision: Option<&str>,
) -> Option<i64> {
let base = baseline.get(rel)?;
if revision_moved(Some(base), revision) {
if revision_moved(Some(base), revision)
|| (base.remote_revision_id.is_none() && revision.is_some())
{
return None;
}
match remote_sig(base) {
@@ -756,6 +765,15 @@ mod file_plan_tests {
assert_eq!(unchanged_remote_size(&rows, "file", 31, Some("r1")), None);
}
/// A row without a revision id reuses its size only while Drive gives no
/// id either (B254).
#[test]
fn a_row_without_a_revision_makes_the_walk_read_the_size_again() {
let rows = HashMap::from([("file".to_string(), baseline(10, 20, 30, 40))]);
assert_eq!(unchanged_remote_size(&rows, "file", 30, Some("r1")), None);
assert_eq!(unchanged_remote_size(&rows, "file", 30, None), Some(40));
}
#[test]
fn a_missing_remote_signature_is_treated_as_changed() {
let local = local(10, 20);