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
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:
@@ -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
BIN
act_runner-darwin-amd64
Executable file
Binary file not shown.
BIN
act_runner-windows-amd64.exe
Executable file
BIN
act_runner-windows-amd64.exe
Executable file
Binary file not shown.
BIN
act_runner_test
Executable file
BIN
act_runner_test
Executable file
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user