fix(files): review round 2, the server — locks the GC and folders read under, the mount check, the old mount name
- GC: the expiry warning marks each file conditionally on the expiry it saw, one commit each (a file deleted meanwhile no longer fails the run; one re-timed keeps its fresh expiry unwarned); eviction counts the disk again when a candidate was freed meanwhile, so it never evicts for space already free. - Folders: a retention change locks the folder and its files, and a finishing file reads the retention under a share lock, so neither misses the other; a folder delete locks the files it takes home (a file moved out meanwhile stays put, one deleted meanwhile no longer fails the delete). - Upload: the folder is checked before the probe and held only once the job names it. - Dedup enqueue locks the cached asset, so a release meanwhile yields a fresh one instead of a 500. - Mount check reads the kernel's mount table instead of comparing devices (the vfs storage driver gave a mounted volume the device of /), and only inside a container; the API also logs a fresh, empty volume in the library's place. - Compose files fall back to DOWNLOAD_HOST_PATH; the upgrade notes cover the from-source pull. - Folder routes call owned_or_404 directly.
This commit is contained in:
@@ -498,6 +498,50 @@ def test_an_upload_to_a_folder_it_may_not_use_is_refused_before_its_body_is_read
|
||||
assert db.execute(select(DownloadJob.id)).all() == before
|
||||
|
||||
|
||||
def test_an_upload_holds_its_folder_only_once_its_file_is_about_to_be_in_it(
|
||||
db, make_user, as_user, _root, upload_headers, monkeypatch
|
||||
):
|
||||
# Held from the start, the folder waited out the probe and the poster (up to a minute) before a
|
||||
# rename or a delete of it could go on; not held at all, a delete could take it from under the file.
|
||||
alice = make_user()
|
||||
own = _own(db, alice.id)
|
||||
steps: list[object] = []
|
||||
real = folders.own_folder
|
||||
|
||||
def own_folder(db_, user_id, folder_id, *, hold=True):
|
||||
steps.append(("folder", hold))
|
||||
return real(db_, user_id, folder_id, hold=hold)
|
||||
|
||||
def probe(_p):
|
||||
steps.append("probe")
|
||||
|
||||
monkeypatch.setattr(folders, "own_folder", own_folder)
|
||||
monkeypatch.setattr(uploadmod.probe, "probe_json", probe)
|
||||
r = as_user(alice).post(
|
||||
"/api/files/upload", content=b"x", headers=upload_headers("a.txt", folder=own.id)
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
# The route's own look before the body, the ingest's look, the probe, then the hold.
|
||||
assert steps[-3:] == [("folder", False), "probe", ("folder", True)]
|
||||
|
||||
|
||||
def test_a_file_moved_holds_the_folder_it_goes_to(db, make_user, as_user, monkeypatch):
|
||||
alice = make_user()
|
||||
job = _file(db, alice.id)
|
||||
own = _own(db, alice.id)
|
||||
holds: list[bool] = []
|
||||
real = folders.own_folder
|
||||
|
||||
def own_folder(db_, user_id, folder_id, *, hold=True):
|
||||
holds.append(hold)
|
||||
return real(db_, user_id, folder_id, hold=hold)
|
||||
|
||||
monkeypatch.setattr(folders, "own_folder", own_folder)
|
||||
r = as_user(alice).patch(f"/api/files/{job.id}", json={"folder_id": own.id})
|
||||
assert r.status_code == 200, r.text
|
||||
assert holds == [True]
|
||||
|
||||
|
||||
def test_a_folder_deleted_while_its_upload_streams_refuses_the_upload(
|
||||
db, make_user, as_user, _root, _no_probe, upload_headers, monkeypatch
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user