P3.1: Add ExecutionMode/OverlapPolicy to Config; add Pending to JobRuntime
Introduces ExecutionMode (parallel/sequential) and OverlapPolicy (skip/queue) types and constants in domain/config.go, wires defaults (parallel/skip) into loadOrCreateConfig and the normalization pass, and adds validation in validateConfig. Adds Pending bool to JobRuntime as the flag P3.3 will use to re-run a queued overlap. Marks P3.1 done. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -94,7 +94,7 @@ These land together because both edit `domain/job.go` and `storage/store.go`.
|
|||||||
- [x] P2.4 — `.gitignore` / `.dockerignore`
|
- [x] P2.4 — `.gitignore` / `.dockerignore`
|
||||||
|
|
||||||
### Phase 3 — Task-queue model + settings
|
### Phase 3 — Task-queue model + settings
|
||||||
- [ ] P3.1 — Config/runtime fields + defaults
|
- [x] P3.1 — Config/runtime fields + defaults
|
||||||
- [ ] P3.2 — Split dispatch into `app/run.go`
|
- [ ] P3.2 — Split dispatch into `app/run.go`
|
||||||
- [ ] P3.3 — Rework `RunDue`/`executeRun` for mode + overlap policy
|
- [ ] P3.3 — Rework `RunDue`/`executeRun` for mode + overlap policy
|
||||||
- [ ] P3.4 — Settings Queue selects
|
- [ ] P3.4 — Settings Queue selects
|
||||||
|
|||||||
@@ -476,5 +476,11 @@ func validateConfig(config domain.Config) error {
|
|||||||
if config.MaxLogAgeDays <= 0 {
|
if config.MaxLogAgeDays <= 0 {
|
||||||
return errors.New("max log age days must be a positive number")
|
return errors.New("max log age days must be a positive number")
|
||||||
}
|
}
|
||||||
|
if config.ExecutionMode != domain.ExecutionModeParallel && config.ExecutionMode != domain.ExecutionModeSequential {
|
||||||
|
return errors.New("execution mode must be 'parallel' or 'sequential'")
|
||||||
|
}
|
||||||
|
if config.OverlapPolicy != domain.OverlapPolicySkip && config.OverlapPolicy != domain.OverlapPolicyQueue {
|
||||||
|
return errors.New("overlap policy must be 'skip' or 'queue'")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func newTempService(t *testing.T, jobs []domain.Job) *Service {
|
|||||||
JobsPath: filepath.Join(dir, "jobs.json"),
|
JobsPath: filepath.Join(dir, "jobs.json"),
|
||||||
LogsDir: filepath.Join(dir, "logs"),
|
LogsDir: filepath.Join(dir, "logs"),
|
||||||
},
|
},
|
||||||
Config: domain.Config{JobsDir: ".", LogsDir: "logs", MaxLogFiles: 100, MaxLogAgeDays: 30},
|
Config: domain.Config{JobsDir: ".", LogsDir: "logs", MaxLogFiles: 100, MaxLogAgeDays: 30, ExecutionMode: domain.ExecutionModeParallel, OverlapPolicy: domain.OverlapPolicySkip},
|
||||||
}
|
}
|
||||||
return NewService(store, jobs)
|
return NewService(store, jobs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,28 @@ package domain
|
|||||||
// launches omit this flag and open the normal window.
|
// launches omit this flag and open the normal window.
|
||||||
const StartInTrayArgument = "--start-in-tray"
|
const StartInTrayArgument = "--start-in-tray"
|
||||||
|
|
||||||
|
// ExecutionMode controls whether due jobs run concurrently or one at a time.
|
||||||
|
type ExecutionMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ExecutionModeParallel allows all due jobs to start simultaneously.
|
||||||
|
ExecutionModeParallel ExecutionMode = "parallel"
|
||||||
|
// ExecutionModeSequential runs due jobs one after another, in order.
|
||||||
|
ExecutionModeSequential ExecutionMode = "sequential"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OverlapPolicy decides what happens when a job's next run fires while the
|
||||||
|
// previous run is still active.
|
||||||
|
type OverlapPolicy string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// OverlapPolicySkip discards the new run when the job is already running.
|
||||||
|
OverlapPolicySkip OverlapPolicy = "skip"
|
||||||
|
// OverlapPolicyQueue holds the new run and starts it as soon as the current
|
||||||
|
// run finishes.
|
||||||
|
OverlapPolicyQueue OverlapPolicy = "queue"
|
||||||
|
)
|
||||||
|
|
||||||
// Config is stored in gosentry.json next to the program. It contains only
|
// Config is stored in gosentry.json next to the program. It contains only
|
||||||
// application-level choices: where to read jobs from, where to write logs, and
|
// application-level choices: where to read jobs from, where to write logs, and
|
||||||
// how the desktop shell should behave.
|
// how the desktop shell should behave.
|
||||||
@@ -16,6 +38,8 @@ type Config struct {
|
|||||||
StartOnLogin bool `json:"start_on_login,omitempty"`
|
StartOnLogin bool `json:"start_on_login,omitempty"`
|
||||||
KeepRunningInTray bool `json:"keep_running_in_tray,omitempty"`
|
KeepRunningInTray bool `json:"keep_running_in_tray,omitempty"`
|
||||||
NotifyOnFailure bool `json:"notify_on_failure,omitempty"`
|
NotifyOnFailure bool `json:"notify_on_failure,omitempty"`
|
||||||
|
ExecutionMode ExecutionMode `json:"execution_mode,omitempty"`
|
||||||
|
OverlapPolicy OverlapPolicy `json:"overlap_policy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobsFile is the on-disk shape of jobs.json. Wrapping the slice in a top-level
|
// JobsFile is the on-disk shape of jobs.json. Wrapping the slice in a top-level
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ type JobRuntime struct {
|
|||||||
// scheduler comparisons. NextRun above is its formatted display string and is
|
// scheduler comparisons. NextRun above is its formatted display string and is
|
||||||
// the only form shown in the GUI.
|
// the only form shown in the GUI.
|
||||||
NextDue time.Time
|
NextDue time.Time
|
||||||
|
|
||||||
|
// Pending is set when a run was skipped due to the overlap policy being
|
||||||
|
// "queue". The scheduler will start this job as soon as the current run ends.
|
||||||
|
Pending bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRuntime builds the initial runtime state for a freshly loaded or created
|
// NewRuntime builds the initial runtime state for a freshly loaded or created
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ type yamlConfig struct {
|
|||||||
StartOnLogin bool `yaml:"start_on_login,omitempty"`
|
StartOnLogin bool `yaml:"start_on_login,omitempty"`
|
||||||
KeepRunningInTray bool `yaml:"keep_running_in_tray,omitempty"`
|
KeepRunningInTray bool `yaml:"keep_running_in_tray,omitempty"`
|
||||||
NotifyOnFailure bool `yaml:"notify_on_failure,omitempty"`
|
NotifyOnFailure bool `yaml:"notify_on_failure,omitempty"`
|
||||||
|
ExecutionMode domain.ExecutionMode `yaml:"execution_mode,omitempty"`
|
||||||
|
OverlapPolicy domain.OverlapPolicy `yaml:"overlap_policy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type yamlJob struct {
|
type yamlJob struct {
|
||||||
@@ -108,6 +110,8 @@ func loadOrCreateConfig(paths Paths) (domain.Config, error) {
|
|||||||
StartOnLogin: false,
|
StartOnLogin: false,
|
||||||
KeepRunningInTray: true,
|
KeepRunningInTray: true,
|
||||||
NotifyOnFailure: true,
|
NotifyOnFailure: true,
|
||||||
|
ExecutionMode: domain.ExecutionModeParallel,
|
||||||
|
OverlapPolicy: domain.OverlapPolicySkip,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(paths.ConfigPath); errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(paths.ConfigPath); errors.Is(err, os.ErrNotExist) {
|
||||||
@@ -147,6 +151,12 @@ func loadOrCreateConfig(paths Paths) (domain.Config, error) {
|
|||||||
if config.MaxLogAgeDays <= 0 {
|
if config.MaxLogAgeDays <= 0 {
|
||||||
config.MaxLogAgeDays = 30
|
config.MaxLogAgeDays = 30
|
||||||
}
|
}
|
||||||
|
if config.ExecutionMode == "" {
|
||||||
|
config.ExecutionMode = domain.ExecutionModeParallel
|
||||||
|
}
|
||||||
|
if config.OverlapPolicy == "" {
|
||||||
|
config.OverlapPolicy = domain.OverlapPolicySkip
|
||||||
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user