Merge pull request #8937 from dokku/8912-cron-containers-are-never-retired-past-their-active-deadline

Retire cron containers past their active deadline
This commit is contained in:
Jose Diaz-Gonzalez
2026-08-12 16:26:44 -04:00
committed by GitHub
18 changed files with 1170 additions and 906 deletions

View File

@@ -42,6 +42,23 @@ var (
const MaintenancePropertyPrefix = "maintenance."
// DefaultTTLSeconds is how long a cron task may run before it is reaped. The
// docker-local scheduler stamps this onto the container as the
// com.dokku.active-deadline-seconds label, while the k3s scheduler renders it
// as the CronJob's activeDeadlineSeconds.
const DefaultTTLSeconds int64 = 86400
// validateTTLSeconds returns an error if the requested task lifetime is not a
// positive number of seconds. A zero or negative deadline would either expire
// the task the instant it starts or never expire it at all.
func validateTTLSeconds(ttlSeconds int64) error {
if ttlSeconds <= 0 {
return fmt.Errorf("--ttl-seconds must be a positive integer")
}
return nil
}
// CronTask is a struct that represents a cron task
type CronTask struct {
// ID is a unique identifier for the cron task

View File

@@ -123,3 +123,32 @@ func TestDokkuRunCommandAppTaskIgnoresLogFile(t *testing.T) {
t.Errorf("DokkuRunCommand() interpolated a redirect into an app task line: %q", got)
}
}
// TestValidateTTLSeconds pins that a cron task deadline must be a positive
// number of seconds. A zero or negative value would either expire the task the
// instant it starts or leave it running forever, and the docker-local retire
// pass only reaps containers whose deadline has actually elapsed.
func TestValidateTTLSeconds(t *testing.T) {
cases := []struct {
name string
ttlSeconds int64
wantErr bool
}{
{name: "default", ttlSeconds: DefaultTTLSeconds},
{name: "positive override", ttlSeconds: 1},
{name: "zero", ttlSeconds: 0, wantErr: true},
{name: "negative", ttlSeconds: -1, wantErr: true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := validateTTLSeconds(tc.ttlSeconds)
if tc.wantErr && err == nil {
t.Errorf("validateTTLSeconds(%d) = nil, want an error", tc.ttlSeconds)
}
if !tc.wantErr && err != nil {
t.Errorf("validateTTLSeconds(%d) = %v, want nil", tc.ttlSeconds, err)
}
})
}
}

View File

@@ -21,7 +21,7 @@ Additional commands:`
cron:list <app> [--format json|stdout], List scheduled cron tasks for an app
cron:report [<app>] [<flag>], Display report about an app
cron:resume <app> <cron_id>, Resume a cron task
cron:run <app> <cron_id> [--detach], Run a cron task on the fly
cron:run <app> <cron_id> [--detach] [--ttl-seconds SECONDS], Run a cron task on the fly
cron:set [--global|<app>] <key> <value>, Set or clear a cron property for an app
cron:suspend <app> <cron_id>, Suspend a cron task`
)

View File

@@ -49,10 +49,11 @@ func main() {
case "run":
args := flag.NewFlagSet("cron:run", flag.ExitOnError)
detached := args.Bool("detach", false, "--detach: run the container in a detached mode")
ttlSeconds := args.Int64("ttl-seconds", cron.DefaultTTLSeconds, "--ttl-seconds: number of seconds the task may run before it is reaped")
args.Parse(os.Args[2:])
appName := args.Arg(0)
cronID := args.Arg(1)
err = cron.CommandRun(appName, cronID, *detached)
err = cron.CommandRun(appName, cronID, *detached, *ttlSeconds)
case "set":
args := flag.NewFlagSet("cron:set", flag.ExitOnError)
global := args.Bool("global", false, "--global: set a global property")

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
@@ -97,11 +98,15 @@ func CommandResume(appName string, cronID string) error {
}
// CommandRun executes a cron task on the fly
func CommandRun(appName string, cronID string, detached bool) error {
func CommandRun(appName string, cronID string, detached bool, ttlSeconds int64) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
if err := validateTTLSeconds(ttlSeconds); err != nil {
return err
}
tasks, err := FetchCronTasks(FetchCronTasksInput{AppName: appName})
if err != nil {
return err
@@ -139,7 +144,7 @@ func CommandRun(appName string, cronID string, detached bool) error {
os.Setenv("DOKKU_CONCURRENCY_POLICY", concurrencyPolicy)
os.Setenv("DOKKU_CRON_ID", cronID)
os.Setenv("DOKKU_RM_CONTAINER", "1")
os.Setenv("DOKKU_RUN_TTL_SECONDS", "86400")
os.Setenv("DOKKU_RUN_TTL_SECONDS", strconv.FormatInt(ttlSeconds, 10))
scheduler := common.GetAppScheduler(appName)
args := append([]string{scheduler, appName, "0", "--"}, fields...)
_, err = common.CallPlugnTrigger(common.PlugnTriggerInput{