main
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
04f9c4e1a5 |
fix(webapp,run-engine,core): drop the hidden debounce ceiling, fail fast on an unusable maxDelay (#4521)
Debouncing with a `delay` longer than an hour did nothing at all. The engine applied a server-side ceiling on how long a debounced run could be pushed back, measured from the run's `createdAt` and defaulting to one hour. A run is only pushed back while its new execution time stays inside that ceiling, so a `delay` at or above it could never push anything: the waiting run was released, the trigger started its own run, and the next trigger repeated it. A `delay: "12h"` produced one run per trigger, each correctly delayed by 12h, with no error raised and nothing on the run to show the debounce key had been ignored. The ceiling is now unset by default. A debounce key with no `maxDelay` keeps collapsing triggers for as long as they keep arriving, which is what the docs have always described. Self-hosters who want a bound can still set `RUN_ENGINE_MAXIMUM_DEBOUNCE_DURATION_MS`. That has a consequence worth stating plainly, so the docs now carry a warning for it: with no `maxDelay`, a continuously triggered key never executes. Set `maxDelay` when the work has to happen eventually. **Failing fast on an unusable `maxDelay`.** A caller who sets `maxDelay` no longer than their `delay` hits exactly the dead end described above, so that pair is now rejected at trigger time instead of silently behaving as if no debounce were set: ``` debounce.maxDelay (1h) must be longer than debounce.delay (12h). A debounced run is only pushed back while it stays inside maxDelay, so with these values every trigger would create its own run. ``` An unparseable `maxDelay` is rejected too, rather than quietly falling back to no bound at all, and so is a `delay` given as a date rather than a duration, which could never work because the value is re-applied on every push. The same check runs against a configured server ceiling, so a self-hosted deployment that sets `RUN_ENGINE_MAXIMUM_DEBOUNCE_DURATION_MS` gets the error rather than the silent failure this PR is about. With no `maxDelay` and no configured ceiling, which is the default, there is nothing to conflict with and nothing is rejected. The docs, the `TriggerOptions` JSDoc and the engine option all now state that the room available to push is the gap between `delay` and `maxDelay`. The run engine suite gains the case that motivated this: four triggers on one key with a 12h delay now collapse to a single run. |
||
|
|
4c2c25511b |
test(webapp): split triggerTask engine test into per-concern files (#4167)
The engine `triggerTask` suite was a single 2447-line file with 23 `containerTest` cases, each spinning its own Postgres + Redis. vitest shards by whole file, so all 23 container setups landed on one shard and dominated its wall-clock. The recorded entry in `test-timings.json` badly under-counts the real cost (it does not capture the per-`containerTest` container startup that dominates on CI), so the duration-sharding sequencer treated the file as light and stacked it, producing one ~21 minute shard. Splitting does not reduce the number of container setups; it lets those 23 cases distribute across shards instead of stacking on one. The webapp unit-test stage is gated by its slowest shard, so this cuts the stage's wall-clock roughly in half. ## CI timing (before vs after) Real CI wall-clock of the `Unit Tests: Webapp` shards (`--shard=i/10`). "Before" is sampled from recent runs on other branches (unsplit file, from `main`); "after" is this PR. | Shard | Before (s) | After (s) | |------:|-----------:|----------:| | 1 | 250 | 359 | | 2 | 444 | 411 | | 3 | 497 | 659 | | 4 | **1257** | 284 | | 5 | 545 | 641 | | 6 | 284 | 644 | | 7 | 244 | 214 | | 8 | 340 | 445 | | 9 | 188 | 395 | | 10 | 234 | 567 | | **Slowest shard (gates the stage)** | **~1247s (≈21m)** | **659s (≈11m)** | | Sum of all shards | 4283 | 4619 | Before: shard 4 is the long pole at 1237s / 1247s / 1257s across three sampled runs (the `triggerTask` file plus whatever else the packer put with it). After: the six pieces spread across shards, the slowest drops to 659s. The small rise in summed time is the extra per-file container startup, paid in parallel across shards, so the gating number still falls by about 10 minutes. ## Change Split into six per-concern files that share a `triggerTaskTestHelpers` module (the `vi.mock` calls stay per-file, since vitest hoists them): - `triggerTask.test.ts` (3): trigger + concurrencyKey coercion - `triggerTask.idempotency.test.ts` (4): idempotency + queue resolution - `triggerTask.debounce.test.ts` (4): retries + debounce validation - `triggerTask.mollifier.test.ts` (4): mollifier call-site behaviour - `triggerTask.metadataCache.test.ts` (4): DefaultQueueManager task metadata cache - `triggerTask.residency.test.ts` (4): child run residency inheritance All 23 cases are preserved. The file's `test-timings.json` entry is split across the new files so bin-packing stays balanced. While rewriting these files, cleanup was moved to `onTestFinished(() => engine.quit())` so an `engine`/`Redis` leaked on a failing assertion no longer persists on the worker-scoped Redis and cascades into later cases (`hookTimeout` raised to 60s so the after-cleanup gets the full budget). Prisma lookups switched from `findUnique` to `findFirst` to match the repo convention. Verified: all six files run green locally (23/23), oxlint and oxfmt clean. |