Affected endpoints: apiMirrorsDrop, apiMirrorsUpdate.
Both endpoints shared the same architectural flaw as the previously fixed
publish, repos, and snapshot endpoints: operations were performed outside
the task lock, with stale DB state used inside the lock.
Issues Fixed:
1. apiMirrorsDrop - Collections created before task lock
Problem: mirrorCollection and snapshotCollection created before task lock.
Snapshot dependency check done with stale factory. Concurrent drops both
load pre-task state, both see same snapshot dependencies. If snapshots
created after pre-task check, can delete mirror used by snapshots.
Fix: Create fresh taskCollectionFactory inside task, fresh load of mirror
after lock acquired, fresh snapshot check with current factory, drop using
fresh collections.
2. apiMirrorsUpdate - Mirror loaded before task lock
Problem: remote loaded outside task, rename duplicate check with stale
factory. Concurrent updates both load pre-task state, long-running update
uses stale mirror reference. TOCTOU race: rename check passes, another
creates mirror with same name, update saves with stale data.
Fix: Create fresh taskCollectionFactory inside task, fresh load of mirror
after lock acquired, pre-task rename validation, fresh rename check inside
lock, use fresh mirror and collections for all operations.
Root cause analysis:
The fundamental issue is the split between pre-task work and task-protected
work. Collections and objects were being loaded before lock acquisition, then
stale copies used inside the lock.
Correct pattern (from fixed publish.go, repos.go, and snapshot.go):
1. HTTP Handler (before task lock):
- Shallow load for 404 check only
- Extract resource keys
- Submit task with resources
2. Task Closure (after lock acquired):
- Create fresh collectionFactory
- Fresh load of all objects
- LoadComplete on fresh copies
- All mutations on fresh state
- All checks atomic inside lock
- Save using fresh collections
This ensures:
- Concurrent operations are serialized by task queue
- No stale DB state used for mutations
- No lost updates from concurrent modifications
- No TOCTOU races on duplicate checks
- No loss of mirrors used by snapshots
- No stale data in long-running updates
add API response wrappers with NumPackages derived from RefList length; keep show endpoint payloads unchanged for backward compatibility; add API tests for list endpoint NumPackages; update swagger response schemas for list endpoints
Several sections of the code *required* a LocalPackagePool, but they
could still perform their operations with a standard PackagePool.
Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
this change imports downloaded packages into the pool immediately after download.
in case mirroring is aborted and later resumed, already downloaded packages will not be downloaded anymore.
When trying to delete a mirror that has snapshot and not providing the
force option, the API should not return a `500
StatusInternalServerError`.
A `403 StatusForbidden` is more appropriate when the condition is
expected by the server.