Skip to content

Commit e14c22c

Browse files
committed
fix: fail closed on cache lookup failures
1 parent 05658d5 commit e14c22c

24 files changed

Lines changed: 2412 additions & 429 deletions

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ cc = "1.0"
142142
chrono = "0.4.42"
143143
divan = { package = "codspeed-divan-compat", version = "4" }
144144
filetime = "0.2"
145+
opendal = { version = "0.55.0", default-features = false, features = ["services-memory"] }
145146
predicates = "=3.1.0"
146147
serial_test = "3.1"
147148
temp-env = "0.3.6"

docs/Configuration.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,16 @@ export SCCACHE_MULTILEVEL_WRITE_ERROR_POLICY="ignore"
218218
export SCCACHE_MULTILEVEL_WRITE_ERROR_POLICY="all"
219219
```
220220

221+
For every backend with an `*_RW_MODE` setting, sccache verifies the requested
222+
capability at server startup. `READ_WRITE` requires both readable storage and a
223+
successful write probe. `READ_ONLY` does not attempt a write, but still requires
224+
readable existing storage. A capability-check error prevents server startup.
225+
For object backends whose API reports missing objects and missing namespaces
226+
with the same status, sccache verifies namespace readability once at startup
227+
when list capability is available and reuses that result for subsequent misses.
228+
Credentialless public S3 and OSS caches remain exempt because public object-read
229+
access does not imply list access.
230+
221231
#### disk (local)
222232

223233
* `SCCACHE_DIR` local on disk artifact cache directory

docs/FailClosedCacheErrors.md

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,79 @@
33
## Goal
44

55
sccache must distinguish a missing cache entry from a cache lookup that
6-
failed. A missing entry remains a normal cache miss. Any other read used to
7-
determine a lookup result, capability-check, transport, or cache-entry decoding
8-
failure remains an error until it reaches the existing caller, compiler, or
9-
statistics handling.
6+
failed. Only confirmed absence is a normal cache miss. A capability, transport,
7+
filesystem, timeout, or decoding failure is fatal to that compilation; sccache
8+
does not hide the failure by invoking the compiler and publishing a replacement
9+
entry.
1010

1111
## Behavior
1212

13-
- `NotFound` from a cache backend is a cache miss.
14-
- Every other backend read failure is an error.
13+
- A backend `NotFound` is a miss only after any separately addressable
14+
bucket, container, or root namespace has been shown to be readable during
15+
startup validation. That proof is cached rather than repeated for every
16+
cold-cache lookup.
17+
- Every other backend read failure, and a cache lookup timeout, is an error.
1518
- A multi-level cache does not convert a level read failure into a miss.
1619
- Client-side path, raw, and preprocessor storage RPCs preserve daemon storage
1720
errors instead of encoding them as absent values.
18-
- Invalid cached bytes are errors rather than misses.
21+
- Invalid archive bytes and corrupt present members, including stdout, stderr,
22+
required outputs, and optional outputs, are errors rather than misses.
1923
- Disk path and preprocessor entry read failures are errors rather than misses.
24+
- Preprocessor dependency validation treats confirmed deletion or content
25+
change as invalidation, but propagates other filesystem failures.
2026
- A failed storage handshake is an error rather than an assumed read-write
2127
capability.
28+
- Client-side storage RPCs have an absolute deadline shorter than the compiler
29+
lookup deadline. When it expires, sccache shuts down the IPC connection to
30+
interrupt synchronous I/O, including a response that is arriving in small
31+
increments.
2232
- Explicitly configured read-only storage does not require write capability.
2333
- Requested read-write storage fails its capability check when it cannot write.
2434
- A server disconnect after `CompileStarted` is an error unless
2535
`SCCACHE_IGNORE_SERVER_IO_ERROR=1` explicitly enables local fallback.
2636

27-
These rules apply consistently to direct daemon compilation, client-side
28-
compilation, single-level storage, and multi-level storage.
37+
These rules apply to direct daemon compilation, client-side compilation,
38+
single-level storage, and multi-level storage. Only a confirmed miss or an
39+
explicit no-cache/recache request can reach compiler execution.
2940

30-
Optional post-hit backfill remains best-effort. A failure in that optimization
31-
does not replace an already validated cache hit with a miss.
41+
Two optimizations remain best-effort:
42+
43+
- A preprocessor entry metadata refresh does not discard an already validated
44+
preprocessor hit when the storage is read-only or the refresh fails.
45+
- Multi-level backfill starts only after the consumer has validated the entire
46+
hit. It copies the exact bytes that were validated, and a backfill failure
47+
does not invalidate the completed hit.
3248

3349
## Implementation Boundaries
3450

3551
`RemoteStorage` owns the OpenDAL error classification. `MultiLevelStorage`
3652
preserves errors returned by its levels. `DiskCache` distinguishes absent
37-
entries from path and preprocessor read failures. The storage IPC protocol
38-
carries fallible handshake, path, raw-read, and preprocessor-read results.
39-
`IpcStorage` converts those protocol errors and cache decoding errors into its
40-
existing `Storage` error result. Existing compiler handling remains responsible
41-
for recording cache errors and deciding the compilation outcome.
42-
43-
No alternate fallback path or second error policy is introduced.
53+
entries from path and preprocessor read failures, initializes according to its
54+
configured mode, and avoids filesystem mutation in read-only mode. The storage
55+
IPC protocol carries an explicit version and fallible path, raw-read, and
56+
preprocessor-read results. The daemon validates capability and size once at
57+
startup; client-side handshakes return that validated metadata rather than
58+
re-probing the backend for every compiler invocation.
59+
60+
Public S3 and OSS caches configured with the existing no-credentials option
61+
are the deliberate namespace-validation exception: public object reads do not
62+
imply bucket-list permission, so sccache preserves credentialless read-only
63+
operation and uses the provider's object-read `NotFound` classification without
64+
an independent list probe.
65+
66+
Multi-level storage also caches every level's startup-validated mode. Writes
67+
skip read-only levels without re-running remote capability probes, and a mixed
68+
read-write/read-only chain remains writable through its read-write levels.
69+
70+
The compiler boundary accepts only a hit, confirmed miss, or explicit cache
71+
control result. Storage errors, lookup timeouts, and cache-entry extraction
72+
errors return before compiler dispatch.
4473

4574
## Compatibility
4675

47-
The storage IPC response shape changes, so clients and daemons must use the
48-
same sccache build. A mismatched peer fails instead of silently treating an
49-
error as a cache miss.
76+
The storage IPC protocol is versioned, so clients and daemons must use a
77+
compatible sccache build. A mismatched peer fails during the handshake instead
78+
of interpreting a changed payload shape.
5079

5180
The capability-check behavior intentionally reverses the rate-limit tolerance
5281
introduced by sccache PR #1557. A rate-limited read or unavailable requested
@@ -57,15 +86,23 @@ operation must configure read-only mode explicitly.
5786

5887
Behavior tests must prove:
5988

60-
1. A direct remote `NotFound` is a miss and another remote read error is an
61-
error.
89+
1. A remote missing object in a readable namespace is a miss, while an
90+
unreadable namespace and other remote read failures are errors.
6291
2. A multi-level read error is not converted into a miss.
63-
3. Client-side path and raw-read errors cross the IPC boundary as errors.
64-
4. Disk path and preprocessor read failures are errors.
65-
5. Invalid client-side cached bytes are errors.
66-
6. Handshake capability and metadata errors cross the IPC boundary as errors.
67-
7. Explicit read-only mode does not require a successful write check.
68-
8. A post-`CompileStarted` disconnect fails by default and falls back locally
92+
3. A later-level hit returns without waiting for backfill, and corrupt or
93+
inconsistent bytes are never backfilled.
94+
4. Client-side path, raw-read, and preprocessor-read errors cross the IPC
95+
boundary as errors.
96+
5. Invalid cached bytes and corrupt present members are errors; absent optional
97+
members and empty stdout/stderr remain valid.
98+
6. Disk read-write checks prove write capability, while read-only checks do not
99+
create or mutate cache files.
100+
7. Preprocessor corruption and unexpected dependency I/O failures are errors,
101+
while confirmed dependency change remains invalidation.
102+
8. Lookup errors and timeouts return before compiler dispatch.
103+
9. Handshake capability and metadata are checked once at startup, and a
104+
protocol mismatch fails explicitly.
105+
10. A post-`CompileStarted` disconnect fails by default and falls back locally
69106
only with the documented opt-in.
70107

71108
The affected feature configurations, formatter, linter, and library test suite

docs/GHA.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@ The `SCCACHE_GHA_RW_MODE` environment variable can be set to `READ_ONLY` to make
1919

2020
## Behavior
2121

22-
In case sccache reaches the rate limit of the service, the build will continue, but the storage might not be performed.
22+
At startup, sccache verifies the configured cache capability. A rate-limited
23+
read or a failed requested write check prevents the server from starting.
24+
Configure `SCCACHE_GHA_RW_MODE=READ_ONLY` when write capability is intentionally
25+
unavailable; read and transport failures remain errors.

docs/OSS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ The `SCCACHE_OSS_RW_MODE` environment variable can be set to `READ_ONLY` to make
1313
Sccache is able to load credentials from environment variables: `ALIBABA_CLOUD_ACCESS_KEY_ID` and `ALIBABA_CLOUD_ACCESS_KEY_SECRET`.
1414

1515
Alternatively, the `SCCACHE_OSS_NO_CREDENTIALS` environment variable can be set to use public readonly access to the OSS bucket, without the need for credentials. Valid values for this environment variable are `true`, `1`, `false`, and `0`. This can be useful for implementing a readonly cache for pull requests, which typically cannot be given access to credentials for security reasons.
16+
17+
Set `SCCACHE_OSS_RW_MODE=READ_ONLY` with credentialless mode. It requires public
18+
object-read access but does not require public bucket-list access. Because the
19+
bucket cannot be probed independently in that mode, sccache uses the OSS
20+
object-read response to classify a missing cache key.

docs/S3.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,8 @@ Sccache is able to load credentials from various sources. Including:
3535
- AssumeRoleWithWebIdentity: assume role with web webIdentity specified by `AWS_ROLE_ARN` and `AWS_WEB_IDENTITY_TOKEN_FILE`.
3636

3737
Alternatively, the `SCCACHE_S3_NO_CREDENTIALS` environment variable can be set to use public readonly access to the S3 bucket, without the need for credentials. Valid values for this environment variable are `true`, `1`, `false`, and `0`. This can be useful for implementing a readonly cache for pull requests, which typically cannot be given access to credentials for security reasons.
38+
39+
Set `SCCACHE_S3_RW_MODE=READ_ONLY` with credentialless mode. It requires public
40+
object-read access but does not require public bucket-list access. Because the
41+
bucket cannot be probed independently in that mode, sccache uses the S3
42+
object-read response to classify a missing cache key.

0 commit comments

Comments
 (0)