mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix: handle potential DB conflict due to concurrent upload requests in postFile (#19005)
This issue manifests when users have multiple templates which rely on the same files, for example see: https://github.com/coder/coder/issues/17442 --------- Signed-off-by: Callum Styan <callumstyan@gmail.com>
This commit is contained in:
+17
-5
@@ -118,11 +118,23 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error saving file.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
if database.IsUniqueViolation(err, database.UniqueFilesHashCreatedByKey) {
|
||||
// The file was uploaded by some concurrent process since the last time we checked for it, fetch it again.
|
||||
file, err = api.Database.GetFileByHashAndCreator(ctx, database.GetFileByHashAndCreatorParams{
|
||||
Hash: hash,
|
||||
CreatedBy: apiKey.UserID,
|
||||
})
|
||||
api.Logger.Info(ctx, "postFile handler hit UniqueViolation trying to upload file after already checking for the file existence", slog.F("hash", hash), slog.F("created_by_id", apiKey.UserID))
|
||||
}
|
||||
// At this point the first error was either not the UniqueViolation OR there's still an error even after we
|
||||
// attempt to fetch the file again, so we should return here.
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error saving file.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.UploadResponse{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -69,6 +70,30 @@ func TestPostFiles(t *testing.T) {
|
||||
_, err = client.Upload(ctx, codersdk.ContentTypeTar, bytes.NewReader(data))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("InsertConcurrent", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var end sync.WaitGroup
|
||||
wg.Add(1)
|
||||
end.Add(3)
|
||||
for range 3 {
|
||||
go func() {
|
||||
wg.Wait()
|
||||
data := make([]byte, 1024)
|
||||
_, err := client.Upload(ctx, codersdk.ContentTypeTar, bytes.NewReader(data))
|
||||
end.Done()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
}
|
||||
wg.Done()
|
||||
end.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
func TestDownload(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user