fix(upload): retry on O_EXCL collision in single-file upload (B12)

This commit is contained in:
2026-09-14 19:45:08 +08:00
parent 8023a7ec5c
commit 08b81fde60
+26 -9
View File
@@ -135,23 +135,40 @@ func (h *H) Upload(c *gin.Context) {
err(c, http.StatusBadRequest, "bad_format", "extension must be cbz/pdf/epub/txt/md") err(c, http.StatusBadRequest, "bad_format", "extension must be cbz/pdf/epub/txt/md")
return return
} }
dst, e := h.uniquePath(root, name) // B12: retry on O_EXCL collision — concurrent uploads with the same name
if e != nil { // can both get the same candidate from uniquePath (stat-then-create race).
err(c, http.StatusForbidden, "forbidden", e.Error()) var dst, tmp string
var out *os.File
for attempt := 0; attempt < 5; attempt++ {
var e error
dst, e = h.uniquePath(root, name)
if e != nil {
err(c, http.StatusForbidden, "forbidden", e.Error())
return
}
tmp = dst + ".upload-" + strconv.FormatInt(time.Now().UnixNano(), 36)
out, e = os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
if e == nil {
break
}
if !os.IsExist(e) {
err(c, http.StatusInternalServerError, "internal", "create tmp")
return
}
// O_EXCL collision — retry with fresh uniquePath.
}
if out == nil {
err(c, http.StatusConflict, "conflict", "too many concurrent uploads with same name")
return return
} }
src, e := fh.Open() src, e := fh.Open()
if e != nil { if e != nil {
out.Close()
os.Remove(tmp)
err(c, http.StatusInternalServerError, "internal", "open upload") err(c, http.StatusInternalServerError, "internal", "open upload")
return return
} }
defer src.Close() defer src.Close()
tmp := dst + ".upload-" + strconv.FormatInt(time.Now().UnixNano(), 36)
out, e := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
if e != nil {
err(c, http.StatusInternalServerError, "internal", "create tmp")
return
}
// B6: only MaxBytesError returns 413; other io.Copy failures (disk full, // B6: only MaxBytesError returns 413; other io.Copy failures (disk full,
// connection drop) return 500. // connection drop) return 500.
if _, e := io.Copy(out, src); e != nil { if _, e := io.Copy(out, src); e != nil {