Environment: ServiceStack.Server 10.0.8, .NET 10, SQL Server 2022 (16.0.4165), services.AddJobsFeature() with the default SQL Server dialect.
Symptom: the host fails to start once enough completed-job history accumulates. DbJobs.StartAsync → LoadJobQueue() throws:
Microsoft.Data.SqlClient.SqlException (0x80131904): Arithmetic overflow error converting expression to data type int.
at ServiceStack.OrmLite.OrmLiteReadCommandExtensions.Dictionary[K,V](...)
at ServiceStack.DbJobs.LoadJobQueue() in ServiceStack.Server/DbJobs.cs:line 977
at ServiceStack.DbJobs.StartAsync(CancellationToken stoppingToken) in ServiceStack.Server/DbJobs.cs:line 962
Error Number:8115,State:2,Class:16
With the default HostOptions.BackgroundServiceExceptionBehavior = StopHost, this takes the whole app down before Kestrel binds.
Root cause: LoadJobQueue() computes per-command duration stats over the full JobSummary history:
DurationMs = Sql.Custom(
$"CASE WHEN SUM({columns.DurationMs}) > {int.MaxValue} THEN {int.MaxValue} ELSE SUM({columns.DurationMs}) END"),
On SQL Server that overflow guard is dead code: SUM over an int column returns int, so the error is raised while evaluating SUM(DurationMs) itself - before the CASE comparison ever runs. Any (Command, Worker) group whose completed DurationMs values sum past 2,147,483,647 makes startup throw. The same pattern is used for the API durations query, which has the identical problem.
Because the sum runs over the entire retained history, it’s unbounded growth - every completed job moves each group closer to the cliff. In our case a dev machine got there quickly: scheduled jobs that bridged a laptop suspend recorded ~52,000,000 ms (~14.5 h) durations, and a few hundred of those pushed several groups to 9-13 billion ms.
Minimal repro (any SQL Server database):
SELECT SUM(x) FROM (VALUES (2000000000), (2000000000)) v(x);
-- Msg 8115: Arithmetic overflow error converting expression to data type int.
SELECT CASE WHEN SUM(x) > 2147483647 THEN 2147483647 ELSE SUM(x) END
FROM (VALUES (2000000000), (2000000000)) v(x);
-- Same error - the CASE can't rescue an overflowing aggregate.
Suggested fix: widen the aggregate before summing so the clamp works as intended, e.g.
CASE WHEN SUM(CAST(DurationMs AS BIGINT)) > 2147483647 THEN 2147483647 ELSE SUM(CAST(DurationMs AS BIGINT)) END
(or the dialect-appropriate cast for the other providers - SQLite/PostgreSQL/MySQL already widen integer SUMs, so SQL Server may be the only affected dialect).
Workaround for anyone hitting this: zero out the inflated rows so they fall out of the stats filter (WHERE DurationMs > 0):
UPDATE dbo.JobSummary SET DurationMs = 0 WHERE DurationMs > 3600000;
Happy to provide anything further.