diff --git a/pkg/lockutil/lockutil_test.go b/pkg/lockutil/lockutil_test.go new file mode 100644 index 00000000000..210110c4dac --- /dev/null +++ b/pkg/lockutil/lockutil_test.go @@ -0,0 +1,56 @@ +package lockutil + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "testing" +) + +const parallel = 20 + +func TestWithDirLock(t *testing.T) { + dir := t.TempDir() + log := filepath.Join(dir, "log") + + errc := make(chan error, 10) + for i := 0; i < parallel; i++ { + go func() { + err := WithDirLock(dir, func() error { + if _, err := os.Stat(log); err == nil { + return nil + } else if !errors.Is(err, os.ErrNotExist) { + return err + } + logFile, err := os.OpenFile(log, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) + if err != nil { + return err + } + defer logFile.Close() + if _, err := fmt.Fprintf(logFile, "writer %d\n", i); err != nil { + return err + } + return logFile.Close() + }) + errc <- err + }() + } + + for i := 0; i < parallel; i++ { + err := <-errc + if err != nil { + t.Error(err) + } + } + + data, err := os.ReadFile(log) + if err != nil { + t.Fatal(err) + } + lines := strings.Split(strings.Trim(string(data), "\n"), "\n") + if len(lines) != 1 { + t.Errorf("Expected one writer, got %v", lines) + } +} diff --git a/pkg/lockutil/lockutil_windows.go b/pkg/lockutil/lockutil_windows.go index 31f31294604..f191559ab51 100644 --- a/pkg/lockutil/lockutil_windows.go +++ b/pkg/lockutil/lockutil_windows.go @@ -33,20 +33,37 @@ var ( procUnlockFileEx = modkernel32.NewProc("UnlockFileEx") ) +const ( + // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx + LOCKFILE_EXCLUSIVE_LOCK = 0x00000002 + LOCKFILE_FAIL_IMMEDIATELY = 0x00000001 +) + func WithDirLock(dir string, fn func() error) error { dirFile, err := os.OpenFile(dir+".lock", os.O_CREATE, 0o644) if err != nil { return err } defer dirFile.Close() - // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx - // 1 lock immediately - if err := lockFileEx(syscall.Handle(dirFile.Fd()), 1, 0, 1, 0, &syscall.Overlapped{}); err != nil { + if err := lockFileEx( + syscall.Handle(dirFile.Fd()), // hFile + LOCKFILE_EXCLUSIVE_LOCK, // dwFlags + 0, // dwReserved + 1, // nNumberOfBytesToLockLow + 0, // nNumberOfBytesToLockHigh + &syscall.Overlapped{}, // lpOverlapped + ); err != nil { return fmt.Errorf("failed to lock %q: %w", dir, err) } defer func() { - if err := unlockFileEx(syscall.Handle(dirFile.Fd()), 0, 1, 0, &syscall.Overlapped{}); err != nil { + if err := unlockFileEx( + syscall.Handle(dirFile.Fd()), // hFile + 0, // dwReserved + 1, // nNumberOfBytesToLockLow + 0, // nNumberOfBytesToLockHigh + &syscall.Overlapped{}, // lpOverlapped + ); err != nil { logrus.WithError(err).Errorf("failed to unlock %q", dir) } }()