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:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: linux-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version-file: 'go.mod'
|
go-version-file: 'go.mod'
|
||||||
cache: false
|
cache: false
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
release:
|
release:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: linux-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- 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
|
// Start periodic capabilities update goroutine
|
||||||
go periodicCapabilitiesUpdate(ctx, runner, ls.Names(), dockerHost, cfg.Container.WorkdirParent)
|
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 := poll.New(cfg, cli, runner)
|
||||||
poller.SetBandwidthManager(bandwidthManager)
|
poller.SetBandwidthManager(bandwidthManager)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -41,6 +42,49 @@ type Runner struct {
|
|||||||
runningTasks sync.Map
|
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 {
|
func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner {
|
||||||
ls := labels.Labels{}
|
ls := labels.Labels{}
|
||||||
for _, v := range reg.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{}{})
|
r.runningTasks.Store(task.Id, struct{}{})
|
||||||
defer r.runningTasks.Delete(task.Id)
|
defer r.runningTasks.Delete(task.Id)
|
||||||
|
defer r.cleanupJobCache(task.Id)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout)
|
ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout)
|
||||||
defer cancel()
|
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>"
|
// 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)),
|
Workdir: filepath.FromSlash(fmt.Sprintf("/%s/%s", strings.TrimLeft(r.cfg.Container.WorkdirParent, "/"), preset.Repository)),
|
||||||
BindWorkdir: false,
|
BindWorkdir: false,
|
||||||
ActionCacheDir: filepath.FromSlash(r.cfg.Host.WorkdirParent),
|
ActionCacheDir: filepath.FromSlash(r.getJobCacheDir(task.Id)),
|
||||||
|
|
||||||
ReuseContainers: false,
|
ReuseContainers: false,
|
||||||
ForcePull: r.cfg.Container.ForcePull,
|
ForcePull: r.cfg.Container.ForcePull,
|
||||||
|
|||||||
Reference in New Issue
Block a user