Protect upload calls with a mutex for each site

This commit is contained in:
Melon 2025-01-07 23:54:12 +00:00
parent e0fb935aaf
commit 303e789d82
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
3 changed files with 20 additions and 1 deletions

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.23.4
require ( require (
github.com/1f349/mjwt v0.4.1 github.com/1f349/mjwt v0.4.1
github.com/1f349/syncmap v0.0.3
github.com/charmbracelet/log v0.4.0 github.com/charmbracelet/log v0.4.0
github.com/cloudflare/tableflip v1.2.3 github.com/cloudflare/tableflip v1.2.3
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1

2
go.sum
View File

@ -2,6 +2,8 @@ github.com/1f349/mjwt v0.4.1 h1:ooCroMMw2kcL5c9L3sLbdtxI0H4/QC8RfTxiloKr+4Y=
github.com/1f349/mjwt v0.4.1/go.mod h1:qwnzokkqc7Z9YmKA1m9beI3OZL1GvGYHOQU2rOwoV1M= github.com/1f349/mjwt v0.4.1/go.mod h1:qwnzokkqc7Z9YmKA1m9beI3OZL1GvGYHOQU2rOwoV1M=
github.com/1f349/rsa-helper v0.0.2 h1:N/fLQqg5wrjIzG6G4zdwa5Xcv9/jIPutCls9YekZr9U= github.com/1f349/rsa-helper v0.0.2 h1:N/fLQqg5wrjIzG6G4zdwa5Xcv9/jIPutCls9YekZr9U=
github.com/1f349/rsa-helper v0.0.2/go.mod h1:VUQ++1tYYhYrXeOmVFkQ82BegR24HQEJHl5lHbjg7yg= github.com/1f349/rsa-helper v0.0.2/go.mod h1:VUQ++1tYYhYrXeOmVFkQ82BegR24HQEJHl5lHbjg7yg=
github.com/1f349/syncmap v0.0.3 h1:Xc0XJwS+OF8UrbPG/C6eLubNSVFinEZ6SEIx7WmAZ0Y=
github.com/1f349/syncmap v0.0.3/go.mod h1:MAfbJgAHKVZgkEqxpbgmMP4yga7Ajmo/KMJLoXVCmZk=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA=

View File

@ -8,6 +8,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/1f349/bluebell/database" "github.com/1f349/bluebell/database"
"github.com/1f349/syncmap"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -17,6 +18,7 @@ import (
"path/filepath" "path/filepath"
"slices" "slices"
"strings" "strings"
"sync"
) )
var indexBranches = []string{ var indexBranches = []string{
@ -82,7 +84,7 @@ type sitesQueries interface {
} }
func New(storage afero.Fs, db sitesQueries) *Handler { func New(storage afero.Fs, db sitesQueries) *Handler {
return &Handler{storage, db} return &Handler{storageFs: storage, db: db}
} }
const maxFileSize = 1 * humanize.GiByte const maxFileSize = 1 * humanize.GiByte
@ -90,6 +92,7 @@ const maxFileSize = 1 * humanize.GiByte
type Handler struct { type Handler struct {
storageFs afero.Fs storageFs afero.Fs
db sitesQueries db sitesQueries
mu syncmap.Map[string, *sync.Mutex]
} }
func (h *Handler) Handle(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { func (h *Handler) Handle(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
@ -144,6 +147,19 @@ func (h *Handler) extractTarGzUpload(fileData io.Reader, site, branch string) er
return fmt.Errorf("invalid site: %w", err) return fmt.Errorf("invalid site: %w", err)
} }
key := site + "@" + branch
// ensure upload mutex is locked
actual, _ := h.mu.LoadOrStore(key, new(sync.Mutex))
actual.Lock()
defer func() {
// The mutex is no longer used so delete it here to safe memory in a "lots of
// sites" configuration. Delete should happen first to prevent another upload
// reusing the mutex.
h.mu.Delete(key)
actual.Unlock()
}()
siteBranchPath := filepath.Join(site, "@"+branch) siteBranchPath := filepath.Join(site, "@"+branch)
siteBranchOldPath := filepath.Join(site, "old@"+branch) siteBranchOldPath := filepath.Join(site, "old@"+branch)