Add locking to ensure safe concurrent access to config file (#881)

Reviewed-on: https://gitea.com/gitea/tea/pulls/881
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
This commit is contained in:
techknowlogick
2026-02-03 23:48:18 +00:00
committed by techknowlogick
parent 0d5bf60632
commit ae9eb4f2c0
12 changed files with 645 additions and 114 deletions

View File

@@ -0,0 +1,82 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build unix
package config
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
)
func TestConfigLock_CrossProcess(t *testing.T) {
// Create a temp directory for test
tmpDir, err := os.MkdirTemp("", "tea-lock-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
lockPath := filepath.Join(tmpDir, "config.yml.lock")
// Acquire lock in main process
unlock, err := acquireConfigLock(lockPath, 5*time.Second)
if err != nil {
t.Fatalf("failed to acquire lock: %v", err)
}
defer unlock()
// Spawn a subprocess that tries to acquire the same lock
// The subprocess should fail to acquire within timeout
script := fmt.Sprintf(`
package main
import (
"os"
"syscall"
)
func main() {
file, err := os.OpenFile(%q, os.O_CREATE|os.O_RDWR, 0o600)
if err != nil {
os.Exit(2)
}
defer file.Close()
// Try non-blocking lock
err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil {
// Lock is held - expected behavior
os.Exit(0)
}
// Lock was acquired - unexpected
syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
os.Exit(1)
}
`, lockPath)
// Write and run the test script
scriptPath := filepath.Join(tmpDir, "locktest.go")
if err := os.WriteFile(scriptPath, []byte(script), 0o600); err != nil {
t.Fatalf("failed to write test script: %v", err)
}
cmd := exec.Command("go", "run", scriptPath)
if err := cmd.Run(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == 1 {
t.Error("subprocess acquired lock when it should have been held")
} else if exitErr.ExitCode() == 2 {
t.Errorf("subprocess failed to open lock file: %v", err)
}
} else {
t.Errorf("subprocess execution failed: %v", err)
}
}
// Exit code 0 means lock was properly held - success
}