Skip to content

fix(scheduler): unstick rate-limited tasks holding filled concurrency slots - #4804

Open
grutt wants to merge 2 commits into
mainfrom
fix/rate-limited-filled-concurrency-slots
Open

grutt wants to merge 2 commits into
mainfrom
fix/rate-limited-filled-concurrency-slots

Conversation

@grutt

@grutt grutt commented Aug 26, 2026

Copy link
Copy Markdown
Member

Description

Tasks that hit a rate limit while holding a filled GROUP_ROUND_ROBIN concurrency slot could deadlock their queue permanently: parked queue items were only requeued when the queuer had already observed rate limits in-process, so after a scheduler restart they never came back, and the schedule-timeout pass only deleted unfilled slots, so the filled slots never released. New runs then waited on the full group until they hit their scheduling timeout, indefinitely. Observed on a production tenant.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

What's Changed

  • Queuer always calls RequeueRateLimitedItems each loop (removed the in-memory hasRateLimits gate), so items parked in v1_rate_limited_queue_items are requeued even by a fresh queuer after a restart or lease change
  • RunGroupRoundRobin / RunChildGroupRoundRobin schedule-timeout now cancels any slot past schedule_timeout_at with no v1_task_runtime row (not actually running), instead of only unfilled slots
  • Add queuer unit tests and an integration test reproducing the filled-slot deadlock (all fail without the fix)

Checklist

Changes have been:

  • Tested (unit, integration, or manually with steps specified)
  • Linted and formatted

Testing

  • go test ./pkg/scheduling/v1/ -run TestQueuerRequeuesRateLimitedItems
  • go test -tags integration ./pkg/scheduling/v1/ (full suite, all 22 tests pass; the new test and unit tests fail on main)

🤖 AI Disclosure
  • I acknowledge that an LLM was used in the creation of this Pull Request, in accordance with Hatchet's AI_POLICY.md.

  • Details: Root-cause analysis, fix, and tests written with Cursor (Claude), reviewed by a human.

… slots

Rate-limited tasks keep their GROUP_ROUND_ROBIN slot filled while parked in
v1_rate_limited_queue_items. Two gaps let those slots deadlock a queue
permanently: the queuer only requeued parked items when it had already seen
rate limits in-process, so a scheduler restart never requeued them; and the
schedule-timeout pass only deleted unfilled slots, so the filled zombies
never released. Always requeue parked items, and time out any slot past
schedule_timeout_at that has no v1_task_runtime row (i.e. is not actually
running).
@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hatchet-docs Ready Ready Preview Aug 26, 2026 5:05pm

Request Review

@greptile-apps

greptile-apps Bot commented Aug 26, 2026

Copy link
Copy Markdown

Greptile Summary

The PR prevents rate-limited tasks from permanently occupying group round-robin concurrency slots after scheduler restarts.

  • Requeues parked rate-limited items on every queuer loop.
  • Times out filled concurrency slots when no matching task runtime exists.
  • Adds unit and integration coverage for restart recovery and filled-slot cleanup.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (2): Last reviewed commit: "test(scheduler): drop duplicate queuer r..." | Re-trigger Greptile

Comment thread pkg/scheduling/v1/queuer_test.go Outdated
@grutt
grutt requested a review from juliusgeo August 26, 2026 17:02
@github-actions github-actions Bot added the engine Related to the core Hatchet engine label Aug 26, 2026
After removing the hasRateLimits gate, the "already observed" test
exercised the same path as the fresh-process case.
checkpoint := start
var err error

if q.hasRateLimits {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we removing this conditional?

strategy_id = @strategyId::bigint AND
schedule_timeout_at < NOW() AND
is_filled = FALSE
-- Filled slots are only running if they have a v1_task_runtime row.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slightly confused by the second line of this comment--the fix here is making it so that is_filled is predicated on whether a v1_task_runtime row exists, otherwise the slot isn't filled? Isn't this logic an inversion of that, where it will only be set to is_filled if there isnt a runtime row?

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark results

goos: linux
goarch: amd64
pkg: github.com/hatchet-dev/hatchet/pkg/scheduling/v1
cpu: AMD Ryzen 9 7950X3D 16-Core Processor          
                                                                    │ /tmp/old.txt  │            /tmp/new.txt             │
                                                                    │    sec/op     │    sec/op     vs base               │
BatchSchedulerWorstCaseMemory-8                                         1.559 ±  3%    1.691 ±  4%   +8.47% (p=0.002 n=6)
RateLimiter-8                                                          48.91µ ± 11%   59.47µ ± 45%  +21.59% (p=0.041 n=6)
Scheduler_ConcurrentAssignReplenishSnapshot-8                          234.5µ ±  6%   244.5µ ±  8%        ~ (p=0.093 n=6)
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_01-8           5.118m ±  4%   5.082m ±  5%        ~ (p=0.937 n=6)
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_04-8           5.906m ±  8%   5.869m ±  3%        ~ (p=0.818 n=6)
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_16-8           13.19m ±  3%   12.60m ±  3%   -4.51% (p=0.009 n=6)
Scheduler_InventoryShape_ReplenishMust/small_dense-8                   44.40µ ±  9%   45.52µ ± 13%        ~ (p=0.589 n=6)
Scheduler_InventoryShape_ReplenishMust/dense_high_action_fanout-8      7.886m ±  5%   7.842m ±  2%        ~ (p=0.699 n=6)
Scheduler_InventoryShape_ReplenishMust/dense_high_slot_capacity-8      1.358m ±  7%   1.365m ±  3%        ~ (p=0.818 n=6)
Scheduler_InventoryShape_ReplenishMust/sparse_low_fanout-8             449.2µ ±  7%   425.7µ ±  2%   -5.22% (p=0.009 n=6)
Scheduler_InventoryShape_ReplenishMust/partitioned_20-8                2.484m ±  7%   2.313m ±  2%   -6.86% (p=0.015 n=6)
Scheduler_InventoryShape_ActiveCountScan/small_dense-8                 11.53µ ±  3%   11.33µ ±  6%        ~ (p=0.310 n=6)
Scheduler_InventoryShape_ActiveCountScan/dense_high_action_fanout-8    3.865m ±  8%   3.825m ±  6%        ~ (p=0.394 n=6)
Scheduler_InventoryShape_ActiveCountScan/dense_high_slot_capacity-8    55.07µ ±  4%   54.48µ ±  3%        ~ (p=0.240 n=6)
Scheduler_InventoryShape_ActiveCountScan/sparse_low_fanout-8           65.38µ ±  3%   64.29µ ±  3%        ~ (p=0.310 n=6)
Scheduler_InventoryShape_ActiveCountScan/partitioned_20-8             1019.8µ ±  6%   989.2µ ±  3%   -3.00% (p=0.004 n=6)
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                  44.98µ ±  7%   47.87µ ±  4%   +6.42% (p=0.009 n=6)
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8     62.21µ ±  5%   76.21µ ±  8%  +22.49% (p=0.002 n=6)
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8     57.01µ ±  6%   72.03µ ±  8%  +26.36% (p=0.002 n=6)
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8            23.68µ ±  7%   26.04µ ± 23%   +9.98% (p=0.026 n=6)
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8               38.12µ ±  6%   37.36µ ±  8%        ~ (p=0.485 n=6)
Scheduler_Replenish_DenseSharedActions-8                               4.687m ±  9%   5.074m ±  8%   +8.25% (p=0.041 n=6)
geomean                                                                512.9µ         530.1µ         +3.36%

                                │ /tmp/old.txt │         /tmp/new.txt          │
                                │  bytes/item  │ bytes/item  vs base           │
BatchSchedulerWorstCaseMemory-8     70.17 ± 0%   70.17 ± 0%  ~ (p=1.000 n=6) ¹
¹ all samples are equal

                                │ /tmp/old.txt  │           /tmp/new.txt           │
                                │ worst_case_MB │ worst_case_MB  vs base           │
BatchSchedulerWorstCaseMemory-8     1.403k ± 0%     1.403k ± 0%  ~ (p=1.000 n=6) ¹
¹ all samples are equal

                                │   /tmp/old.txt   │           /tmp/new.txt            │
                                │ worst_case_bytes │ worst_case_bytes  vs base         │
BatchSchedulerWorstCaseMemory-8        1.403G ± 0%        1.403G ± 0%  ~ (p=0.818 n=6)

                                                                    │ /tmp/old.txt │             /tmp/new.txt             │
                                                                    │     B/op     │     B/op      vs base                │
BatchSchedulerWorstCaseMemory-8                                       6.400Gi ± 0%   6.400Gi ± 0%       ~ (p=0.937 n=6)
RateLimiter-8                                                         137.7Ki ± 0%   137.7Ki ± 0%       ~ (p=0.171 n=6)
Scheduler_ConcurrentAssignReplenishSnapshot-8                         258.7Ki ± 0%   258.8Ki ± 1%       ~ (p=1.000 n=6)
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_01-8          2.263Mi ± 0%   2.263Mi ± 0%       ~ (p=0.093 n=6)
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_04-8          2.663Mi ± 0%   2.663Mi ± 0%       ~ (p=0.781 n=6)
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_16-8          4.426Mi ± 0%   4.426Mi ± 0%       ~ (p=0.310 n=6)
Scheduler_InventoryShape_ReplenishMust/small_dense-8                  28.03Ki ± 0%   28.03Ki ± 0%       ~ (p=0.126 n=6)
Scheduler_InventoryShape_ReplenishMust/dense_high_action_fanout-8     3.732Mi ± 0%   3.730Mi ± 0%       ~ (p=0.310 n=6)
Scheduler_InventoryShape_ReplenishMust/dense_high_slot_capacity-8     3.132Mi ± 0%   3.132Mi ± 0%       ~ (p=0.732 n=6)
Scheduler_InventoryShape_ReplenishMust/sparse_low_fanout-8            665.1Ki ± 0%   665.1Ki ± 0%       ~ (p=1.000 n=6)
Scheduler_InventoryShape_ReplenishMust/partitioned_20-8               1.872Mi ± 0%   1.872Mi ± 0%       ~ (p=0.768 n=6)
Scheduler_InventoryShape_ActiveCountScan/small_dense-8                  168.0 ± 0%     168.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_action_fanout-8     168.0 ± 0%     168.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_slot_capacity-8     168.0 ± 0%     168.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/sparse_low_fanout-8            168.0 ± 0%     168.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/partitioned_20-8               168.0 ± 0%     168.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                 39.31Ki ± 0%   39.31Ki ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8    151.6Ki ± 0%   151.6Ki ± 0%       ~ (p=1.000 n=6)
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8    119.6Ki ± 0%   119.6Ki ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8           13.72Ki ± 0%   13.72Ki ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8              40.61Ki ± 0%   40.61Ki ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_Replenish_DenseSharedActions-8                              2.166Mi ± 0%   2.166Mi ± 0%       ~ (p=0.784 n=6)
geomean                                                               105.3Ki        105.3Ki       +0.00%
¹ all samples are equal

                                                                    │ /tmp/old.txt │            /tmp/new.txt             │
                                                                    │  allocs/op   │  allocs/op   vs base                │
BatchSchedulerWorstCaseMemory-8                                        3.721k ± 0%   3.723k ± 0%       ~ (p=0.965 n=6)
RateLimiter-8                                                          1.022k ± 0%   1.022k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_ConcurrentAssignReplenishSnapshot-8                          1.064k ± 1%   1.066k ± 0%       ~ (p=0.113 n=6)
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_01-8           6.684k ± 0%   6.683k ± 0%       ~ (p=0.545 n=6)
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_04-8           13.58k ± 0%   13.58k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_16-8           41.70k ± 0%   41.70k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/small_dense-8                    333.0 ± 0%    333.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_action_fanout-8      13.51k ± 0%   13.51k ± 0%       ~ (p=1.000 n=6)
Scheduler_InventoryShape_ReplenishMust/dense_high_slot_capacity-8      56.57k ± 0%   56.57k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/sparse_low_fanout-8             6.196k ± 0%   6.196k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/partitioned_20-8                15.21k ± 0%   15.21k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/small_dense-8                  4.000 ± 0%    4.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_action_fanout-8     4.000 ± 0%    4.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_slot_capacity-8     4.000 ± 0%    4.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/sparse_low_fanout-8            4.000 ± 0%    4.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/partitioned_20-8               4.000 ± 0%    4.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                   382.0 ± 0%    382.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8      430.0 ± 0%    430.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8      430.0 ± 0%    430.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8             277.0 ± 0%    277.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8                430.0 ± 0%    430.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_Replenish_DenseSharedActions-8                               4.891k ± 0%   4.891k ± 0%       ~ (p=1.000 n=6)
geomean                                                                 604.4         604.5       +0.01%
¹ all samples are equal

                                                                    │ /tmp/old.txt │            /tmp/new.txt             │
                                                                    │ action_rows  │ action_rows  vs base                │
Scheduler_ConcurrentAssignReplenishSnapshot-8                          10.00k ± 0%   10.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_01-8           50.00k ± 0%   50.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_04-8           50.00k ± 0%   50.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_16-8           50.00k ± 0%   50.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/small_dense-8                    200.0 ± 0%    200.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_action_fanout-8      86.20k ± 0%   86.20k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_slot_capacity-8      1.200k ± 0%   1.200k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/sparse_low_fanout-8             1.000k ± 0%   1.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/partitioned_20-8                20.00k ± 0%   20.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/small_dense-8                  200.0 ± 0%    200.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_action_fanout-8    86.20k ± 0%   86.20k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_slot_capacity-8    1.200k ± 0%   1.200k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/sparse_low_fanout-8           1.000k ± 0%   1.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/partitioned_20-8              20.00k ± 0%   20.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                   200.0 ± 0%    200.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8     86.20k ± 0%   86.20k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8     1.200k ± 0%   1.200k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8            1.000k ± 0%   1.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8               20.00k ± 0%   20.00k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                                5.421k        5.421k       +0.00%
¹ all samples are equal

                                                                    │ /tmp/old.txt │            /tmp/new.txt             │
                                                                    │   actions    │   actions    vs base                │
Scheduler_ConcurrentAssignReplenishSnapshot-8                           200.0 ± 0%    200.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_01-8            500.0 ± 0%    500.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_04-8            500.0 ± 0%    500.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_16-8            500.0 ± 0%    500.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/small_dense-8                    20.00 ± 0%    20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_action_fanout-8      1.724k ± 0%   1.724k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_slot_capacity-8       30.00 ± 0%    30.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/sparse_low_fanout-8             2.000k ± 0%   2.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/partitioned_20-8                2.000k ± 0%   2.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/small_dense-8                  20.00 ± 0%    20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_action_fanout-8    1.724k ± 0%   1.724k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_slot_capacity-8     30.00 ± 0%    30.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/sparse_low_fanout-8           2.000k ± 0%   2.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/partitioned_20-8              2.000k ± 0%   2.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                   20.00 ± 0%    20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8     1.724k ± 0%   1.724k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8      30.00 ± 0%    30.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8            2.000k ± 0%   2.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8               2.000k ± 0%   2.000k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                                 346.2         346.2       +0.00%
¹ all samples are equal

                                                                   │ /tmp/old.txt │            /tmp/new.txt            │
                                                                   │  batch_size  │ batch_size  vs base                │
Scheduler_ConcurrentAssignReplenishSnapshot-8                          64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                  64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8     64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8     64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8            64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8               64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                                64.00        64.00       +0.00%
¹ all samples are equal

                                                                    │ /tmp/old.txt │            /tmp/new.txt            │
                                                                    │  slot_types  │ slot_types  vs base                │
Scheduler_ConcurrentAssignReplenishSnapshot-8                           1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_01-8            1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_04-8            4.000 ± 0%   4.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_16-8            16.00 ± 0%   16.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/small_dense-8                    1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_action_fanout-8       1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_slot_capacity-8       1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/sparse_low_fanout-8              1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/partitioned_20-8                 1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/small_dense-8                  1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_action_fanout-8     1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_slot_capacity-8     1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/sparse_low_fanout-8            1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/partitioned_20-8               1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                   1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8             1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8                1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                                 1.245        1.245       +0.00%
¹ all samples are equal

                                                                    │   /tmp/old.txt   │               /tmp/new.txt               │
                                                                    │ slots_per_worker │ slots_per_worker  vs base                │
Scheduler_ConcurrentAssignReplenishSnapshot-8                               20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_01-8                20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_04-8                20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_16-8                20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/small_dense-8                        4.000 ± 0%         4.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_action_fanout-8           20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_slot_capacity-8          1.400k ± 0%        1.400k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/sparse_low_fanout-8                  20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/partitioned_20-8                     20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/small_dense-8                      4.000 ± 0%         4.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_action_fanout-8         20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_slot_capacity-8        1.400k ± 0%        1.400k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/sparse_low_fanout-8                20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/partitioned_20-8                   20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                       4.000 ± 0%         4.000 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8          20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8         1.400k ± 0%        1.400k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8                 20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8                    20.00 ± 0%         20.00 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                                     30.34              30.34       +0.00%
¹ all samples are equal

                                                                    │ /tmp/old.txt │             /tmp/new.txt             │
                                                                    │ unique_slots │ unique_slots  vs base                │
Scheduler_ConcurrentAssignReplenishSnapshot-8                          1.000k ± 0%    1.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_01-8           2.000k ± 0%    2.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_04-8           8.000k ± 0%    8.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_16-8           32.00k ± 0%    32.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/small_dense-8                    40.00 ± 0%     40.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_action_fanout-8      1.000k ± 0%    1.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_slot_capacity-8      56.00k ± 0%    56.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/sparse_low_fanout-8             4.000k ± 0%    4.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/partitioned_20-8                4.000k ± 0%    4.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/small_dense-8                  40.00 ± 0%     40.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_action_fanout-8    1.000k ± 0%    1.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_slot_capacity-8    56.00k ± 0%    56.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/sparse_low_fanout-8           4.000k ± 0%    4.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/partitioned_20-8              4.000k ± 0%    4.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                   40.00 ± 0%     40.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8     1.000k ± 0%    1.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8     56.00k ± 0%    56.00k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8            4.000k ± 0%    4.000k ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8               4.000k ± 0%    4.000k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                                2.444k         2.444k       +0.00%
¹ all samples are equal

                                                                    │ /tmp/old.txt │            /tmp/new.txt            │
                                                                    │   workers    │  workers    vs base                │
Scheduler_ConcurrentAssignReplenishSnapshot-8                           50.00 ± 0%   50.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_01-8            100.0 ± 0%   100.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_04-8            100.0 ± 0%   100.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_SlotTypeCardinality/slot_types_16-8            100.0 ± 0%   100.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/small_dense-8                    10.00 ± 0%   10.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_action_fanout-8       50.00 ± 0%   50.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/dense_high_slot_capacity-8       40.00 ± 0%   40.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/sparse_low_fanout-8              200.0 ± 0%   200.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ReplenishMust/partitioned_20-8                 200.0 ± 0%   200.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/small_dense-8                  10.00 ± 0%   10.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_action_fanout-8     50.00 ± 0%   50.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/dense_high_slot_capacity-8     40.00 ± 0%   40.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/sparse_low_fanout-8            200.0 ± 0%   200.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_ActiveCountScan/partitioned_20-8               200.0 ± 0%   200.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/small_dense-8                   10.00 ± 0%   10.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_action_fanout-8      50.00 ± 0%   50.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/dense_high_slot_capacity-8      40.00 ± 0%   40.00 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/sparse_low_fanout-8             200.0 ± 0%   200.0 ± 0%       ~ (p=1.000 n=6) ¹
Scheduler_InventoryShape_TryAssignBatch/partitioned_20-8                200.0 ± 0%   200.0 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                                 64.71        64.71       +0.00%
¹ all samples are equal

Compared against main (4dde179)

@Scalahansolo

Copy link
Copy Markdown

We put together a standalone reproduction for both defects covered by this PR: hatchet-rate-limit-repro.

The same tests run against the pinned PR base and this PR’s proposed fix:

Scenario PR base PR fix
Fresh scheduler recovers an eligible parked task Bug reproduced 10/10 Passed 10/10
Expired filled concurrency slot releases the waiting follow-up Bug reproduced 10/10 Passed 10/10

It uses the real scheduler and PostgreSQL repositories. To make the trigger deterministic, it seeds the parked-task state directly and constructs a fresh scheduling pool; it does not submit through an SDK or kill a full engine process. The recovery test checks that the queuer completed assignment passes, then confirms a direct repository call can recover the stuck row. The timeout test checks cancellation of the expired holder and queuing of the follow-up.

Run python3 reproduce.py after cloning; Docker, Git, and Python are the only local prerequisites. The repo includes recorded test evidence, and the comparison also passed on a clean GitHub Actions runner.

This gives us a repeatable check for both halves of the fix. Hope it helps with review and verification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine Related to the core Hatchet engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants