feat: make per-job timeout 0 mean "no timeout" instead of inherit
A per-job timeout now has three distinct states: unset inherits the global default, an explicit 0 means no timeout and does not inherit, and a positive value is the per-job limit. Job.TimeoutSeconds became *int so unset and 0 stay distinguishable in jobs.json. Also fixes the global default, which could not persist a 0. loadOrCreateConfig normalized DefaultTimeoutSeconds <= 0 back to 30 on every read of an existing gosentry.json, so "no timeout" only held until the next restart. The field is now written unconditionally (no omitempty) and read back as-is. Existing jobs and configs are unaffected: a job with no timeout_seconds still inherits, and a saved global default of 30 stays 30. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+15
-4
@@ -131,10 +131,21 @@ in flight increments `JobRuntime.PendingRuns`. When the current run finishes,
|
||||
|
||||
### Per-job command timeout
|
||||
|
||||
`domain.Job` carries a `TimeoutSeconds` field (`json:"timeout_seconds,omitempty"`),
|
||||
following the same inherit pattern as the overlap policy. `0` means inherit the
|
||||
global `Config.DefaultTimeoutSeconds` (default **0**, i.e. no timeout); a
|
||||
positive value overrides it for that job alone. `app.Service.effectiveTimeout`
|
||||
`domain.Job` carries a `TimeoutSeconds *int` field
|
||||
(`json:"timeout_seconds,omitempty"`), following the same inherit pattern as the
|
||||
overlap policy. It is a **pointer** because the setting has three states that
|
||||
must stay distinguishable on disk:
|
||||
|
||||
| `Job.TimeoutSeconds` | jobs.json | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `nil` | field absent | inherit `Config.DefaultTimeoutSeconds` |
|
||||
| `0` | `"timeout_seconds": 0` | no timeout, does **not** inherit |
|
||||
| `> 0` | `"timeout_seconds": 45` | per-job limit in seconds |
|
||||
|
||||
The global `Config.DefaultTimeoutSeconds` (default **0**, i.e. no timeout) is
|
||||
written unconditionally — no `omitempty` — for the same reason: `0` there is a
|
||||
deliberate choice, not a missing value, and `storage.loadOrCreateConfig` must not
|
||||
normalize it away. `app.Service.effectiveTimeout`
|
||||
resolves the effective duration under `mu` and `startRunLocked` snapshots it into
|
||||
`runEnv.timeout`. `runner.RunJob(ctx, job, trigger, logsDir, timeout)` takes the
|
||||
resolved duration as an argument, so the runner stays ignorant of the global
|
||||
|
||||
@@ -2,6 +2,24 @@
|
||||
|
||||
All notable GoSentry changes are recorded in this file.
|
||||
|
||||
## Unreleased
|
||||
|
||||
**Timeouts: 0 now means "no timeout" at both levels.**
|
||||
|
||||
- The global **Default timeout** in Settings now defaults to `0`, meaning jobs
|
||||
run to completion with no deadline instead of being killed after 30s.
|
||||
- A per-job timeout of `0` now also means "no timeout" and no longer inherits
|
||||
the global default. Leaving the job's timeout **empty** is what inherits.
|
||||
`Job.TimeoutSeconds` became `*int` so the three states — unset, explicit 0,
|
||||
and a positive limit — stay distinguishable in `jobs.json`.
|
||||
- Fixed: a global default of `0` did not survive a restart. `gosentry.json` was
|
||||
loaded with `0` treated as a missing value and silently reset to 30s, so the
|
||||
setting only held for the current session. `default_timeout_seconds` is now
|
||||
written unconditionally and read back as-is.
|
||||
|
||||
Existing jobs and configs are unaffected: a job with no `timeout_seconds` still
|
||||
inherits, and a saved global default of 30 stays 30.
|
||||
|
||||
## 0.13.0 - 2026-07-26
|
||||
|
||||
**Branded GoSentry color theme; Cancel/Defaults buttons in Settings.**
|
||||
|
||||
+3
-2
@@ -18,8 +18,9 @@ in [ARCHITECTURE.md](ARCHITECTURE.md); test conventions in [TESTS.md](TESTS.md).
|
||||
- Sequential mode runs jobs FIFO by order in `jobs.json`.
|
||||
- Scheduler tick is 1s — sub-second `@every` intervals are not supported.
|
||||
- Command timeout defaults to no timeout globally (`Config.DefaultTimeoutSeconds`
|
||||
= 0) and is overridable per job (`Job.TimeoutSeconds`, 0 = inherit the global
|
||||
default).
|
||||
= 0) and is overridable per job (`Job.TimeoutSeconds *int`: unset = inherit the
|
||||
global default, 0 = no timeout, positive = seconds). Neither zero may be
|
||||
normalized away on load — 0 is a value, not a missing field.
|
||||
- **History tab is session-only.** `JobRuntime.Logs` exists only in memory for the
|
||||
current process. Log files on disk feed aggregate statistics via `SeedStats`
|
||||
only. See [ARCHITECTURE.md](ARCHITECTURE.md).
|
||||
|
||||
Reference in New Issue
Block a user