Skip to content
Merged
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
13 changes: 12 additions & 1 deletion apps/desktop-tauri/src/surfaces/TrayPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,18 @@ const tauriMocks = vi.hoisted(() => ({
}));

const eventMocks = vi.hoisted(() => ({
listen: vi.fn(),
// Declared with a working default, not a bare vi.fn(). `afterEach` calls
// vi.restoreAllMocks(), which resets each mock to the implementation it was
// created with — for a bare vi.fn() that is "return undefined". LocaleProvider
// does `listen(...).then(...)`, so any call that lands outside the
// beforeEach-to-test window threw "Cannot read properties of undefined
// (reading 'then')" and failed whichever test was on screen. Restoring to a
// resolved promise keeps that harmless; beforeEach still installs the
// listener-capturing version the tests drive.
listen: vi.fn(
(_event: string, _handler: (event: { payload: unknown }) => void) =>
Promise.resolve(() => {}),
),
listeners: new Map<string, Array<(event: { payload: unknown }) => void>>(),
}));

Expand Down
16 changes: 13 additions & 3 deletions rust/src/secure_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const STATE_LOCK_STALE: std::time::Duration = std::time::Duration::from_secs(120
///
/// Unix unlinks by name and never reports this state, so the retry is compiled
/// in everywhere but only enabled where it can happen.
const DELETE_PENDING_GRACE: std::time::Duration = std::time::Duration::from_secs(1);
const DELETE_PENDING_GRACE: std::time::Duration = std::time::Duration::from_millis(250);
const RETRIES_DELETE_PENDING: bool = cfg!(windows);

/// Serialize read-modify-write transactions across Ceiling processes.
Expand Down Expand Up @@ -257,8 +257,12 @@ impl StateWriteLock {
if !RETRIES_DELETE_PENDING {
return attempt;
}
// A directory sitting on the sibling path is also access-denied on
// Windows, and unlike a closing handle it never clears. Retrying
// that only delays a failure the caller needs now.
let denied = matches!(&attempt, LockAttempt::Failed(error)
if error.kind() == io::ErrorKind::PermissionDenied);
if error.kind() == io::ErrorKind::PermissionDenied)
&& !exclusive.is_dir();
if !denied || std::time::Instant::now() >= deadline {
return attempt;
}
Expand Down Expand Up @@ -2000,7 +2004,13 @@ mod tests {
attempts, 1,
"a failed fallback must not be retried as success"
);
assert!(started.elapsed() < std::time::Duration::from_secs(1));
// A directory on the sibling path is access-denied on Windows too, but
// it never clears. The delete-pending retry must not sit on it: this
// used to spend the whole grace period before failing.
assert!(
started.elapsed() < DELETE_PENDING_GRACE,
"an unfixable fallback must fail without waiting out the retry grace"
);
}

#[test]
Expand Down
Loading