mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-08-03 05:13:10 +00:00
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>
36 lines
1.2 KiB
Go
36 lines
1.2 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Package lock provides a cross-platform, non-blocking advisory file lock used
|
|
// to ensure a single runner process owns a given runner file.
|
|
package lock
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// ErrLocked is returned by TryLock when another process already holds the lock.
|
|
var ErrLocked = errors.New("runner file is already locked by another process")
|
|
|
|
// TryLock takes a non-blocking exclusive advisory lock tied to runnerFile. The
|
|
// lock is placed on a sibling "<runnerFile>.lock" so it never interferes with
|
|
// in-place rewrites of the runner file itself.
|
|
//
|
|
// It returns a release function that drops the lock. The operating system also
|
|
// releases the lock automatically when the process exits, including on a hard
|
|
// kill, so a crashed runner never leaves a stale lock behind.
|
|
//
|
|
// If another process already holds the lock, it returns ErrLocked.
|
|
func TryLock(runnerFile string) (func() error, error) {
|
|
path := runnerFile + ".lock"
|
|
release, err := tryLock(path)
|
|
if errors.Is(err, ErrLocked) {
|
|
return nil, ErrLocked
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lock %q: %w", path, err)
|
|
}
|
|
return release, nil
|
|
}
|