2
0

feat: implement job-isolated cache directories
Some checks failed
CI / build-and-test (push) Has been cancelled
Release / build (amd64, darwin) (push) Successful in 17s
Release / build (amd64, linux) (push) Successful in 21s
Release / build (amd64, windows) (push) Successful in 17s
Release / build (arm64, darwin) (push) Successful in 16s
Release / build (arm64, linux) (push) Successful in 43s
Release / release (push) Successful in 29s

- Each job now gets its own cache directory: ~/.cache/act/jobs/{taskId}/
- Cache is cleaned up automatically after job completion
- Periodic cleanup removes stale job caches older than 2 hours
- Eliminates race conditions in npm/pnpm cache operations
- No more ENOTEMPTY errors from concurrent tool installs
- Fix workflow: use linux-latest and setup-go@v4
This commit is contained in:
GitCaddy
2026-01-11 22:16:44 +00:00
parent b303a83a77
commit f314ffb036
6 changed files with 62 additions and 4 deletions

View File

@@ -7,7 +7,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: linux-latest
strategy:
matrix:
include:
@@ -26,7 +26,7 @@ jobs:
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@v4
with:
go-version-file: 'go.mod'
cache: false
@@ -52,7 +52,7 @@ jobs:
release:
needs: build
runs-on: ubuntu-latest
runs-on: linux-latest
steps:
- uses: actions/checkout@v4

BIN
act_runner-darwin-amd64 Executable file
View File

Binary file not shown.

BIN
act_runner-windows-amd64.exe Executable file
View File

Binary file not shown.

BIN
act_runner_test Executable file
View File

Binary file not shown.

View File

@@ -187,6 +187,19 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
// Start periodic capabilities update goroutine
go periodicCapabilitiesUpdate(ctx, runner, ls.Names(), dockerHost, cfg.Container.WorkdirParent)
// Start periodic stale job cache cleanup (every hour, remove caches older than 2 hours)
go func() {
ticker := time.NewTicker(1 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
runner.CleanStaleJobCaches(2 * time.Hour)
}
}
}()
poller := poll.New(cfg, cli, runner)
poller.SetBandwidthManager(bandwidthManager)

View File

@@ -7,6 +7,7 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
@@ -41,6 +42,49 @@ type Runner struct {
runningTasks sync.Map
}
// getJobCacheDir returns a job-isolated cache directory
func (r *Runner) getJobCacheDir(taskID int64) string {
return filepath.Join(r.cfg.Host.WorkdirParent, "jobs", fmt.Sprintf("%d", taskID))
}
// cleanupJobCache removes the job-specific cache directory after completion
func (r *Runner) cleanupJobCache(taskID int64) {
jobCacheDir := r.getJobCacheDir(taskID)
if err := os.RemoveAll(jobCacheDir); err != nil {
log.Warnf("failed to cleanup job cache %s: %v", jobCacheDir, err)
} else {
log.Infof("cleaned up job cache: %s", jobCacheDir)
}
}
// CleanStaleJobCaches removes job cache directories older than maxAge
func (r *Runner) CleanStaleJobCaches(maxAge time.Duration) {
jobsDir := filepath.Join(r.cfg.Host.WorkdirParent, "jobs")
entries, err := os.ReadDir(jobsDir)
if err != nil {
return // directory may not exist yet
}
cutoff := time.Now().Add(-maxAge)
for _, entry := range entries {
if !entry.IsDir() {
continue
}
info, err := entry.Info()
if err != nil {
continue
}
if info.ModTime().Before(cutoff) {
jobPath := filepath.Join(jobsDir, entry.Name())
if err := os.RemoveAll(jobPath); err != nil {
log.Warnf("failed to remove stale job cache %s: %v", jobPath, err)
} else {
log.Infof("evicted stale job cache: %s", jobPath)
}
}
}
}
func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner {
ls := labels.Labels{}
for _, v := range reg.Labels {
@@ -95,6 +139,7 @@ func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
}
r.runningTasks.Store(task.Id, struct{}{})
defer r.runningTasks.Delete(task.Id)
defer r.cleanupJobCache(task.Id)
ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout)
defer cancel()
@@ -202,7 +247,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
// On Windows, Workdir will be like "\<parent_directory>\<owner>\<repo>"
Workdir: filepath.FromSlash(fmt.Sprintf("/%s/%s", strings.TrimLeft(r.cfg.Container.WorkdirParent, "/"), preset.Repository)),
BindWorkdir: false,
ActionCacheDir: filepath.FromSlash(r.cfg.Host.WorkdirParent),
ActionCacheDir: filepath.FromSlash(r.getJobCacheDir(task.Id)),
ReuseContainers: false,
ForcePull: r.cfg.Container.ForcePull,