fix: resolve the metadata backend before the garbage-collect gate
What does this MR do?
registry garbage-collect refuses to run wherever database.enabled is prefer and the metadata has fallen back to the filesystem. Offline collection is the only reclamation available there, so those registries have none.
Database.IsEnabled() already accounts for the fallback by testing !PreferFallback, but that field is only set during server startup, by initializeMetadataDatabase, which GCCmd.RunE never calls. So prefer reads as the database being in use.
This exports the server's own resolution as handlers.ResolveMetadataBackend and calls it from both processes, so the command reads the lockfiles rather than trusting configuration alone. Each refusal now names the signal it decided on, and prefer mode reports the conflicting-lockfile state.
How each process resolves the backend
flowchart TD
cfg["config.yml<br/>database.enabled: prefer<br/>filesystem-in-use lockfile present"]
cfg --> s1["registry serve<br/>initializeMetadataDatabase"]
cfg --> g1["registry garbage-collect<br/>GCCmd.RunE"]
g1 -.->|"before this MR<br/>configured value only"| old["refuses<br/>PreferFallback still false"]
s1 --> R
g1 --> R
R["handlers.ResolveMetadataBackend<br/>reads both lockfiles"]
R --> fb["PreferFallback set<br/>IsEnabled false"]
fb --> serves["server serves<br/>filesystem metadata"]
fb --> collects["collection runs"]Introduced in 7bfefd8e (feat(registry): add database prefer mode, 2025-10-31). Prefer has been the default since 0d6ff19f, so a registry can be in this state without ever having configured prefer.
Two commits, refactor then fix, left unsquashed per CONTRIBUTING's split-up-refactors rule.
Related to #2391
Reported by @niklasjanz.
Developed with AI assistance.
Verification
On a self-managed omnibus 19.4.0 instance, triggered through gitlab-ctl registry-garbage-collect against seeded storage restored identically before each arm.
database.enabled |
Lockfiles | Binary | Result |
|---|---|---|---|
prefer |
filesystem-in-use |
v4.41.0-gitlab, and master | refuses, same text |
prefer |
filesystem-in-use |
master + this MR | collects, driver=filesystem, 3 blobs swept |
true |
database-in-use |
master + this MR | refuses, quoting the configured value |
prefer |
database-in-use |
master + this MR | refuses, naming the absent lockfile |
prefer |
both | master + this MR | ErrInvalidLockfiles |
prefer |
both | master | generic message, lockfiles unmentioned |
false |
filesystem-in-use |
master + this MR | collects, 3 blobs swept |
false |
filesystem-in-use |
master | collects, the same 3 blobs |
The gate is split rather than moved. An explicitly enabled database cannot resolve to anything else, so it is refused on the configured value alone, before a driver exists, because the GCS driver discovers credentials at construction and can reach the network. Only prefer mode waits on storage to decide:
database.enabled |
Storage configuration | Binary | Result |
|---|---|---|---|
true |
GCS, keyfile missing | master + this MR | gate message, no driver error |
prefer |
GCS, keyfile missing | master + this MR | driver error, no gate message |
prefer |
GCS, keyfile missing | master | gate message, no driver error |
So the first gate runs before any driver exists, and a prefer-mode registry with unusable storage configuration now reports that driver error rather than the refusal. Collection was already impossible in that state.
registry/root_test.go covers the command, including a case that seeds an unreferenced blob and asserts the sweep removes it, and registry/handlers/app_test.go covers the resolution directly. The five TestInitializeMetadataDatabase_* cases and TestNewApp_PreferFallback_V1RoutesReturn404 in app_integration_test.go pass unchanged on master and on this branch against PostgreSQL 17.
Author checklist
- CODEOWNERS Review: This MR requires approval from at least one CODEOWNER per category/file.
- Assign one of conventional-commit prefixes to the MR.
-
fix: Indicates a bug fix, triggers a patch release.
-
- MR contains database changes including schema/background migrations:
- Change contains a breaking change - apply the breaking change label.
- Change is considered high risk - apply the label high-risk-change
- I created or linked to an existing issue for every added or updated
TODO,BUG,FIXMEorOPTIMIZEprefixed comment - Changes cannot be rolled back
Reviewer checklist
- Ensure the commit and MR tittle are still accurate.
- If the change contains a breaking change, verify the breaking change label.
- If the change is considered high risk, verify the label high-risk-change
- Identify if the change can be rolled back safely. (note: all other reasons for not being able to rollback will be sufficiently captured by major version changes).