Skip to content

Commit ced1909

Browse files
authored
fix(receiver): harden prefetch and listener lifecycle (#645)
* fix(receiver): harden prefetch and listener lifecycle Track delivery and prefetch capacity ownership across shutdown paths. Close pending listener reads, preserve late deliveries, and restore fail-fast transport error propagation. Keep middleware accounting balanced, report discarded deliveries, and retain deferred lifecycle diagnostics when task group failures overlap. * fix(receiver): tighten prefetch ownership checks Reject invalid prefetch values before receiver retries and retain delivery accounting until execution capacity is acquired. Add deterministic coverage for CLI defaults, cancellation races, and listener lifecycle cleanup. Refs #528 * fix(receiver): simplify prefetch listener lifecycle - remove undocumented prefetch middleware hooks and metrics - preserve listener errors during task group failures - restore capacity when callback handoff fails - strengthen shutdown and cleanup coverage Refs: #528 * refactor(receiver): simplify prefetch queue cleanup
1 parent ae2b788 commit ced1909

10 files changed

Lines changed: 1281 additions & 111 deletions

File tree

‎docs/guide/cli.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ The number of signals before a hard kill can be configured with the `--hardkill-
162162
* `--log-format` is used to set a log format (default `%(asctime)s][%(name)s][%(levelname)-7s][%(processName)s] %(message)s`).
163163
* `--max-async-tasks` - maximum number of simultaneously running async tasks.
164164
* `--max-async-tasks-jitter` – Randomly varies the max async task limit between --max-async-tasks and a jittered value, helping prevent simultaneous worker restarts.
165-
* `--max-prefetch` - number of tasks to be prefetched before execution. (Useful for systems with high message rates, but brokers should support acknowledgements).
165+
* `--max-prefetch` - maximum number of deliveries allowed to wait for execution beyond the available async execution capacity. With a finite `--max-async-tasks` limit, a worker admits at most its effective async execution limit plus `max_prefetch` running or waiting deliveries. The default `0` disables additional buffering, and the value must be non-negative. This option is useful for systems with high message rates, but brokers should support acknowledgements.
166166
* `--max-threadpool-threads` - number of threads for sync function execution.
167167
* `--no-propagate-errors` - if this parameter is enabled, exceptions won't be thrown in generator dependencies.
168168
* `--receiver` - python path to custom receiver class.

‎taskiq/api/receiver.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,18 @@ async def run_receiver_task(
4545
:param validate_params: whether to validate params or not.
4646
:param max_async_tasks: maximum number of simultaneous async tasks.
4747
:param max_async_tasks_jitter: random jitter to add to max_async_tasks.
48-
:param max_prefetch: maximum number of tasks to prefetch.
48+
:param max_prefetch: maximum number of deliveries allowed to wait beyond
49+
the available async execution capacity. Must be non-negative.
4950
:param propagate_exceptions: whether to propagate exceptions in generators or not.
5051
:param run_startup: whether to run startup function or not.
5152
:param ack_time: acknowledge type to use.
5253
:param use_process_pool: whether to use process pool or threadpool.
5354
:raises asyncio.CancelledError: if the task was cancelled.
55+
:raises ValueError: if max_prefetch is negative.
5456
"""
57+
if max_prefetch < 0:
58+
raise ValueError("max_prefetch cannot be negative.")
59+
5560
finish_event = asyncio.Event()
5661

5762
def on_exit(_: Receiver) -> None:

‎taskiq/cli/worker/args.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,10 @@ def from_cli(
224224
type=int,
225225
dest="max_prefetch",
226226
default=0,
227-
help="Maximum prefetched tasks per worker process. ",
227+
help=(
228+
"Maximum deliveries waiting beyond async execution capacity. "
229+
"Must be non-negative. "
230+
),
228231
)
229232
parser.add_argument(
230233
"--no-configure-logging",
@@ -283,6 +286,8 @@ def from_cli(
283286
args,
284287
namespace=None if defaults is None else Namespace(**defaults),
285288
)
289+
if namespace.max_prefetch < 0:
290+
parser.error("argument --max-prefetch: max_prefetch cannot be negative.")
286291
# If there are any patterns specified, remove default.
287292
# This is an argparse limitation.
288293
if len(namespace.tasks_pattern) > 1:

‎taskiq/middlewares/opentelemetry_middleware.py‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,6 @@ def __init__(
230230
unit="1",
231231
description="Number of tasks currently executing in the worker.",
232232
)
233-
# 9- Number of tasks executing
234-
self.number_of_broker_prefetched_tasks = self._meter.create_up_down_counter(
235-
"worker_prefetched_tasks",
236-
unit="1",
237-
description="Number of tasks currently prefetched in the worker.",
238-
)
239233

240234
def _observe_memory(self, options: Any) -> Generator[Observation, None, None]:
241235
if self.broker and self.broker.is_worker_process:
@@ -447,11 +441,3 @@ def post_execute(
447441
-1,
448442
attributes={"task_name": message.task_name},
449443
)
450-
451-
def on_prefetch_queue_add(self) -> None:
452-
"""This hook is called after task is added to the worker prefetch queue."""
453-
self.number_of_broker_prefetched_tasks.add(1)
454-
455-
def on_prefetch_queue_remove(self) -> None:
456-
"""This hook is called after task is removed from the worker prefetch queue."""
457-
self.number_of_broker_prefetched_tasks.add(-1)

0 commit comments

Comments
 (0)