mirror of
https://gitea.com/gitea/act_runner
synced 2026-09-21 19:37:07 +02:00
Implement speedups to job start and shutdown. - Create the job container while services are still becoming healthy, and poll their health at a flat one second instead of a 2s to 32s doubling backoff - Pull each service image once instead of twice, and fetch a warm action cache once instead of twice - Report the job result before reclaiming its volumes, and reap volumes stranded by a runner that died mid-job | Step | Scenario | Before | After | | --- | --- | --- | --- | | Complete job | Large workspace volume | 4.2s | 0.4s | | Set up job | One service, 2s health interval | 7.08s | 3.26s | | Set up job | Two cached actions from github.com | 1.81s | 1.34s | | Set up job | Two cached actions from gitea.com | 2.42s | 1.95s | | Set up job | Two actions, cold action cache | 6.62s | unchanged | | Set up job | Minimal job, no services or actions | 0.62s | unchanged | Assisted-by: Claude Code:Opus 5 Reviewed-on: https://gitea.com/gitea/runner/pulls/1218 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
90 lines
2.4 KiB
Go
90 lines
2.4 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// Copyright 2020 The nektos/act Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows || netbsd))
|
|
|
|
package container
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
|
|
cerrdefs "github.com/containerd/errdefs"
|
|
"github.com/moby/moby/client"
|
|
)
|
|
|
|
func CreateJobVolumes(ctx context.Context, runnerUUID string, volumeNames []string) error {
|
|
cli, err := GetDockerClient(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cli.Close()
|
|
|
|
for _, volumeName := range volumeNames {
|
|
if _, err := cli.VolumeCreate(ctx, client.VolumeCreateOptions{
|
|
Name: volumeName,
|
|
Labels: runnerLabels(runnerUUID),
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func RemoveOrphanJobVolumes(ctx context.Context, runnerUUID string, createdBefore time.Time) error {
|
|
cli, err := GetDockerClient(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to the docker daemon: %w", err)
|
|
}
|
|
defer cli.Close()
|
|
|
|
volumes, err := cli.VolumeList(ctx, client.VolumeListOptions{
|
|
Filters: make(client.Filters).Add("label", runnerUUIDLabel+"="+runnerUUID).Add("dangling", "true"),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var errs []error
|
|
for _, item := range volumes.Items {
|
|
created, err := time.Parse(time.RFC3339, item.CreatedAt)
|
|
// an unreadable or recent timestamp may belong to a job that is still starting up
|
|
if err != nil || created.After(createdBefore) || item.Labels[runnerUUIDLabel] != runnerUUID {
|
|
continue
|
|
}
|
|
if _, err := cli.VolumeRemove(ctx, item.Name, client.VolumeRemoveOptions{}); err != nil && !cerrdefs.IsNotFound(err) {
|
|
errs = append(errs, fmt.Errorf("failed to remove volume %s: %w", item.Name, err))
|
|
continue
|
|
}
|
|
common.Logger(ctx).Infof("removed docker volume %s left behind by an earlier job", item.Name)
|
|
}
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
func NewDockerVolumeRemoveExecutor(volumeName string, force bool) common.Executor {
|
|
return func(ctx context.Context) error {
|
|
common.Logger(ctx).Debugf("docker volume rm %s", volumeName)
|
|
|
|
if common.Dryrun(ctx) {
|
|
return nil
|
|
}
|
|
|
|
cli, err := GetDockerClient(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cli.Close()
|
|
|
|
_, err = cli.VolumeRemove(ctx, volumeName, client.VolumeRemoveOptions{Force: force})
|
|
if cerrdefs.IsNotFound(err) { // already gone is the outcome we wanted
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
}
|