feat: complete runner-side cancellation handling (#1016)

Completes the runner side of the cancellation flow, superseding #825. Two parts:

### 1. Report cancellations correctly (`fix`)
When `Reporter.Close` ran with the state still `UNSPECIFIED` and the reporter's
context had been cancelled, the synthesised final state attributed the job to
`RESULT_FAILURE` with an "Early termination" log row — misreporting a
cancellation as a generic failure. `Close` now detects the cancelled context
and finalizes the task as `RESULT_CANCELLED`.

### 2. Advertise the `cancelling` capability (`feat`)
[actions-proto-go v0.6.0](https://gitea.com/gitea/actions-proto-go) adds a
`capabilities` field to `RegisterRequest`/`DeclareRequest`, so the runner can
now tell the server it understands the transitional cancelling state:

- Bumps `gitea.dev/actions-proto-go` to `v0.6.0`.
- Adds a single `RunnerCapabilities()` source of truth exposing
  `CapabilityCancelling`.
- Sends `Capabilities` on both register and declare.

With this the server records `HasCancellingSupport` and can rely on the runner
running post-step cleanup before a task is finalized as `RESULT_CANCELLED`.

## Compatibility

Wire-compatible against older servers: the new field uses a previously unused
field number (8 on `RegisterRequest`, 3 on `DeclareRequest`) and the client uses
the binary protobuf codec, so a server predating the field silently ignores it —
registration and declaration succeed and the feature simply stays off. It
activates only once both runner and server are on v0.6.0.

## Server side

The matching Gitea change (read `GetCapabilities()`, persist
`HasCancellingSupport`) is a separate PR against `gitea/gitea`.

Supersedes #825.

Reviewed-on: https://gitea.com/gitea/runner/pulls/1016
Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
Reviewed-by: wxiaoguang <29147+wxiaoguang@noreply.gitea.com>
This commit is contained in:
Nicolas
2026-06-11 09:00:31 +00:00
parent 526c46b485
commit 822af5029f
6 changed files with 111 additions and 20 deletions

View File

@@ -14,6 +14,7 @@ import (
"strings"
"time"
"gitea.com/gitea/runner/internal/app/run"
"gitea.com/gitea/runner/internal/pkg/client"
"gitea.com/gitea/runner/internal/pkg/config"
"gitea.com/gitea/runner/internal/pkg/labels"
@@ -365,11 +366,12 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs)
}
// register new runner.
resp, err := cli.Register(ctx, connect.NewRequest(&runnerv1.RegisterRequest{
Name: reg.Name,
Token: reg.Token,
Version: ver.Version(),
Labels: ls,
Ephemeral: reg.Ephemeral,
Name: reg.Name,
Token: reg.Token,
Version: ver.Version(),
Labels: ls,
Ephemeral: reg.Ephemeral,
Capabilities: run.RunnerCapabilities(),
}))
if err != nil {
log.WithError(err).Error("poller: cannot register new runner")

View File

@@ -37,6 +37,18 @@ import (
log "github.com/sirupsen/logrus"
)
// CapabilityCancelling tells the server this runner understands the
// transitional cancelling state and will run post-step cleanup before
// finalizing a task as RESULT_CANCELLED.
const CapabilityCancelling = "cancelling"
// RunnerCapabilities are the capability flags this runner advertises to the
// server during registration and declaration. The server uses them to enable
// transitional features that require runner-side support.
func RunnerCapabilities() []string {
return []string{CapabilityCancelling}
}
// Runner runs the pipeline.
type Runner struct {
name string
@@ -504,7 +516,8 @@ func (r *Runner) RunningCount() int64 {
func (r *Runner) Declare(ctx context.Context, labels []string) (*connect.Response[runnerv1.DeclareResponse], error) {
return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{
Version: ver.Version(),
Labels: labels,
Version: ver.Version(),
Labels: labels,
Capabilities: RunnerCapabilities(),
}))
}