Describe the bug
scheduler_loop in src/utils/task_manager/scheduler.rs and the equivalent path in src/utils/task_manager/cron.rs call .unwrap() directly on Redis command results (conn.hget(...), conn_clone.zadd(...)) on the main polling loop, which runs every second. Any error returned from these calls — including a client-side command timeout — causes an unhandled panic instead of being logged/retried. Because this runs directly inside tokio::join!(ping_server(), scheduler_loop(conn)) in main.rs (not inside a tokio::spawn), the panic takes down the entire process, not just the scheduler task.
This is triggered by a second, related gap: there is no concurrency limit on periodic backup task dispatch. scheduler_loop pulls all due tasks (zrangebyscore(SCHEDULE_KEY, 0, now)) and tokio::spawns every one of them immediately, with no semaphore or max-concurrency bound. I checked the full source tree and found no concurrency-limiting construct anywhere in the codebase.
In our case: N backup jobs shared the same cron schedule (00:00) and all fired at once → host CPU hit 100% from the concurrent dump processes → Redis, starved of CPU on the same host, became slow enough to respond that the agent's Redis client calls exceeded their timeout → the next scheduler loop tick hit the .unwrap() on that timeout error and panicked → agent restarted under the container's restart policy.
To Reproduce
- Configure [N] backup jobs on the same host, all with a cron schedule that fires at the same timestamp (e.g.
0 0 * * *)
- Let the schedule tick — all due tasks are dispatched concurrently with no throttling (
scheduler_loop, src/utils/task_manager/scheduler.rs)
- Observe host CPU saturate to 100% from simultaneous dump processes
- Under sustained CPU starvation, Redis becomes slow to respond and the agent's Redis client calls start timing out
- On the next scheduler loop iteration,
conn.hget(&key, "data").await.unwrap() (scheduler.rs) / the equivalent in cron.rs receives an Err (timeout) and panics
- Under a process supervisor/container restart policy, the agent restarts and immediately re-enters the same panic on the next tick — crash loop until CPU pressure eases enough for Redis to respond in time
Expected behavior
- Redis errors on the scheduler hot path — including command timeouts — should be handled gracefully (logged + backoff/retry), not
.unwrap()'d into a process panic.
- Periodic task dispatch should be bounded by a configurable concurrency limit so multiple jobs scheduled for the same tick don't all execute at once and starve host CPU (which in turn starves Redis if co-located).
Environment
- Deployment: Docker
- Agent version: reproduced through
1.17.1
Additional context
Relevant lines on main (1.18.5):
src/utils/task_manager/scheduler.rs: conn.hget(&key, "data").await.unwrap(), conn_clone.zadd(SCHEDULE_KEY, &key, next_ts).await.unwrap() — both unhandled, both on the loop that runs every second
src/utils/task_manager/cron.rs: conn.hget(&redis_key, "data").await.unwrap() — same pattern
Describe the bug
scheduler_loopinsrc/utils/task_manager/scheduler.rsand the equivalent path insrc/utils/task_manager/cron.rscall.unwrap()directly on Redis command results (conn.hget(...),conn_clone.zadd(...)) on the main polling loop, which runs every second. Any error returned from these calls — including a client-side command timeout — causes an unhandled panic instead of being logged/retried. Because this runs directly insidetokio::join!(ping_server(), scheduler_loop(conn))inmain.rs(not inside atokio::spawn), the panic takes down the entire process, not just the scheduler task.This is triggered by a second, related gap: there is no concurrency limit on periodic backup task dispatch.
scheduler_looppulls all due tasks (zrangebyscore(SCHEDULE_KEY, 0, now)) andtokio::spawns every one of them immediately, with no semaphore or max-concurrency bound. I checked the full source tree and found no concurrency-limiting construct anywhere in the codebase.In our case: N backup jobs shared the same cron schedule (00:00) and all fired at once → host CPU hit 100% from the concurrent dump processes → Redis, starved of CPU on the same host, became slow enough to respond that the agent's Redis client calls exceeded their timeout → the next scheduler loop tick hit the
.unwrap()on that timeout error and panicked → agent restarted under the container's restart policy.To Reproduce
0 0 * * *)scheduler_loop,src/utils/task_manager/scheduler.rs)conn.hget(&key, "data").await.unwrap()(scheduler.rs) / the equivalent incron.rsreceives anErr(timeout) and panicsExpected behavior
.unwrap()'d into a process panic.Environment
1.17.1Additional context
Relevant lines on
main(1.18.5):src/utils/task_manager/scheduler.rs:conn.hget(&key, "data").await.unwrap(),conn_clone.zadd(SCHEDULE_KEY, &key, next_ts).await.unwrap()— both unhandled, both on the loop that runs every secondsrc/utils/task_manager/cron.rs:conn.hget(&redis_key, "data").await.unwrap()— same pattern