1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! Bounded retry for the Windows file refusals that clear by themselves.
//!
//! An indexer, a virus scanner or a backup agent can hold a file this process wrote a moment ago
//! open without `FILE_SHARE_DELETE`. Windows then refuses a delete of that file, a rename over it,
//! or an open of it with `ERROR_ACCESS_DENIED` (5), `ERROR_SHARING_VIOLATION` (32) or
//! `ERROR_LOCK_VIOLATION` (33), although nothing about the caller's rights is wrong and the hold
//! ends by itself within moments. This module is the one place that knows which errors those are
//! and how long to wait for them, so a plain delete, rename or open that is the last step of a
//! longer, already-verified procedure can ride out a passing hold instead of failing the whole
//! procedure or leaving recovery material behind.
//!
//! What it guarantees:
//!
//! * **Windows only.** The three codes are Windows codes. On any other platform
//! [`is_transient_refusal`] is always `false`, so every operation runs exactly once.
//! * **Bounded.** An operation is attempted once and then retried at most
//! [`TRANSIENT_RETRY`]`.attempts` times (20), pausing `delay` (100 ms) before each retry, so a
//! persistent refusal costs at most two seconds. [`retry_transient_with`] takes a different
//! bound from the caller.
//! * **The original error comes back.** After the bound the last error is returned unchanged
//! (same kind, same OS code), so a true permission failure still fails, only later. Nothing is
//! wrapped, summarised or turned into success.
//! * **Only the operation is repeated.** The helper repeats the closure it is given and nothing
//! else; it changes no ACL, attribute or sharing mode. A caller that deletes or renames a file
//! it identified earlier proves that identity again inside the closure, so a name that changed
//! hands during the pause is never touched.
//!
//! Error 5 is also what a real ACL denial returns. The publish step of an atomic write therefore
//! retries it only when the caller opts in with [`crate::atomic::AccessDeniedRetry`]; this module
//! is for the calls that are not an atomic write (a plain `remove_file`, `remove_dir`, `rename` or
//! `open`), where retrying error 5 for a bounded time is the same trade.
use crateRetryPolicy;
use crate;
use io;
use Duration;
/// The bound [`retry_transient`] applies: at most 20 retries after the first attempt, 100 ms
/// before each.
pub const TRANSIENT_RETRY: RetryPolicy = RetryPolicy ;
/// True when `error` is a Windows refusal that usually clears by itself: `ERROR_ACCESS_DENIED`
/// (5), `ERROR_SHARING_VIOLATION` (32) or `ERROR_LOCK_VIOLATION` (33). Always `false` off Windows.
/// The classifier behind [`is_transient_refusal`], with the platform explicit so it is testable
/// everywhere.
/// Run `operation`; while it fails with a [transient refusal](is_transient_refusal), pause and run
/// it again, at most [`TRANSIENT_RETRY`]`.attempts` more times. The last result is returned as it
/// is.
/// [`retry_transient`] with the caller's bound: `policy.attempts` retries after the first
/// attempt, `policy.delay` before each. `attempts: 0` runs the operation once.
/// [`retry_transient`] for an operation that reports [`FsError`] (for example
/// [`crate::FileKey::of_path`]). An error is transient when it is [`FsError::Io`] whose source is.
/// Any other error, including a path that does not exist, is returned at once.
/// The loop behind the public entry points, with the classifier explicit so tests can drive it
/// with errors that are transient on every platform.