Files
act_runner/internal/pkg/lock/lock_windows.go
bircni 0cd0e52a24 fix!: guard against two runner processes sharing one runner file (#1099)
Starting two runner daemons with the same `.runner` file makes both present an
identical UUID+token, so Gitea treats them as one runner and they cancel each
other's jobs. This adds a non-blocking advisory lock on a sibling
`<runner-file>.lock`: the daemon (and `register`) acquire it at startup, and a
second process on the same host fails fast with a clear error instead of silently
interfering. The OS releases the lock when the process exits — including a hard
kill — so no stale lock is left behind. Legitimate multi-runner setups are
unaffected since each already uses its own `runner.file`.

Note: this covers the common single-host case; two hosts sharing a copied
`.runner` (e.g. over NFS) would still need server-side detection in Gitea.

---------

Co-authored-by: Zettat123 <zettat123@gmail.com>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1099
Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
2026-07-31 12:15:04 +00:00

37 lines
994 B
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build windows
package lock
import (
"errors"
"os"
"golang.org/x/sys/windows"
)
// tryLock opens (creating if needed) the lock file and takes a non-blocking
// exclusive lock on it via LockFileEx. The returned release closes the file,
// which drops the lock; Windows also drops it when the process exits.
func tryLock(path string) (func() error, error) {
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600)
if err != nil {
return nil, err
}
handle := windows.Handle(f.Fd())
overlapped := new(windows.Overlapped)
if err := windows.LockFileEx(handle, windows.LOCKFILE_EXCLUSIVE_LOCK|windows.LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, overlapped); err != nil {
_ = f.Close()
if errors.Is(err, windows.ERROR_LOCK_VIOLATION) {
return nil, ErrLocked
}
return nil, err
}
return func() error {
_ = windows.UnlockFileEx(handle, 0, 1, 0, overlapped)
return f.Close()
}, nil
}