mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-08-06 00:44:22 +02:00
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>
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
"gitea.com/gitea/runner/internal/pkg/config"
|
||||
"gitea.com/gitea/runner/internal/pkg/envcheck"
|
||||
"gitea.com/gitea/runner/internal/pkg/labels"
|
||||
"gitea.com/gitea/runner/internal/pkg/lock"
|
||||
"gitea.com/gitea/runner/internal/pkg/metrics"
|
||||
"gitea.com/gitea/runner/internal/pkg/ver"
|
||||
|
||||
@@ -49,6 +50,25 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
|
||||
return fmt.Errorf("failed to load registration file: %w", err)
|
||||
}
|
||||
|
||||
// Guard against a second runner process sharing this runner file: two
|
||||
// processes with the same identity are indistinguishable to Gitea and
|
||||
// end up cancelling each other's jobs.
|
||||
releaseLock, err := lock.TryLock(cfg.Runner.File)
|
||||
if errors.Is(err, lock.ErrLocked) {
|
||||
log.Errorf("another gitea-runner process is already using %q; each runner process needs its own runner file (runner.file)", cfg.Runner.File)
|
||||
return err
|
||||
} else if err != nil {
|
||||
// Best-effort guard: if the lock file can't be created (e.g. a
|
||||
// read-only runner-file mount), warn and start anyway rather than
|
||||
// refusing to run.
|
||||
log.Warnf("could not lock runner file %q, continuing without the single-process guard: %v", cfg.Runner.File, err)
|
||||
} else {
|
||||
// Held until shutdown finishes: the draining runner still owns this
|
||||
// identity on the server, so releasing early would let a restart
|
||||
// reintroduce the duplicate-identity job cancellations.
|
||||
defer func() { _ = releaseLock() }()
|
||||
}
|
||||
|
||||
lbls := resolveLabels(daemArgs.Labels, cfg.Runner.Labels, reg.Labels)
|
||||
|
||||
ls := labels.Labels{}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"gitea.com/gitea/runner/internal/pkg/client"
|
||||
"gitea.com/gitea/runner/internal/pkg/config"
|
||||
"gitea.com/gitea/runner/internal/pkg/labels"
|
||||
"gitea.com/gitea/runner/internal/pkg/lock"
|
||||
"gitea.com/gitea/runner/internal/pkg/ver"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
@@ -346,6 +347,19 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi
|
||||
}
|
||||
|
||||
func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) error {
|
||||
// Refuse to rewrite the runner file while another process is using it.
|
||||
releaseLock, err := lock.TryLock(cfg.Runner.File)
|
||||
if errors.Is(err, lock.ErrLocked) {
|
||||
return fmt.Errorf("another process is already using %q; stop it before re-registering", cfg.Runner.File)
|
||||
} else if err != nil {
|
||||
// Best-effort guard: if the lock file can't be created, warn and
|
||||
// register anyway; writing the runner file will surface any real
|
||||
// permission problem with a clearer error.
|
||||
log.Warnf("could not lock runner file %q, continuing without the single-process guard: %v", cfg.Runner.File, err)
|
||||
} else {
|
||||
defer func() { _ = releaseLock() }()
|
||||
}
|
||||
|
||||
// initial http client
|
||||
cli := client.New(
|
||||
inputs.InstanceAddr,
|
||||
|
||||
Reference in New Issue
Block a user