Skip to content

Commit

Permalink
Merge branch 'master' into on-send-error-middleware-hook
Browse files Browse the repository at this point in the history
  • Loading branch information
s3rius authored Nov 6, 2024
2 parents 881c4d6 + 41fbc7b commit 65ce5e9
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 114 deletions.
202 changes: 120 additions & 82 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ packaging = ">=19"
# For prometheus metrics
prometheus_client = { version = "^0", optional = true }
# For ZMQBroker
pyzmq = { version = "^23.2.0", optional = true, markers = "python_version < '3.12'"}
pyzmq = { version = "^26", optional = true }
# For speed
uvloop = { version = ">=0.16.0,<1", optional = true, markers = "sys_platform != 'win32'" }
# For hot-reload.
Expand Down
2 changes: 1 addition & 1 deletion taskiq/abc/broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ async def shutdown(self) -> None:

for middleware in self.middlewares:
if middleware.__class__.shutdown != TaskiqMiddleware.shutdown:
await maybe_awaitable(middleware.shutdown)
await maybe_awaitable(middleware.shutdown())

await self.result_backend.shutdown()

Expand Down
6 changes: 5 additions & 1 deletion taskiq/cli/worker/process_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import os
import signal
import sys
from contextlib import suppress
Expand Down Expand Up @@ -265,7 +266,10 @@ def start(self) -> Optional[int]: # noqa: C901
action.handle(self.workers, self.args, self.worker_function)
reloaded_workers.add(action.worker_num)
elif isinstance(action, ShutdownAction):
logger.debug("Process manager closed.")
logger.debug("Process manager closed, killing workers.")
for worker in self.workers:
if worker.pid:
os.kill(worker.pid, signal.SIGINT)
return None

for worker_num, worker in enumerate(self.workers):
Expand Down
22 changes: 20 additions & 2 deletions taskiq/kicker.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def __init__(
self.broker = broker
self.labels = labels
self.custom_task_id: Optional[str] = None
self.custom_schedule_id: Optional[str] = None

def with_labels(
self,
Expand Down Expand Up @@ -77,6 +78,19 @@ def with_task_id(self, task_id: str) -> "AsyncKicker[_FuncParams, _ReturnType]":
self.custom_task_id = task_id
return self

def with_schedule_id(
self,
schedule_id: str,
) -> "AsyncKicker[_FuncParams, _ReturnType]":
"""
Set schedule_id for current execution.
:param schedule_id: custom schedule id.
:return: kicker with custom schedule id.
"""
self.custom_schedule_id = schedule_id
return self

def with_broker(
self,
broker: "AsyncBroker",
Expand Down Expand Up @@ -184,7 +198,9 @@ async def schedule_by_cron(
:return: schedule id.
"""
schedule_id = self.broker.id_generator()
schedule_id = self.custom_schedule_id
if schedule_id is None:
schedule_id = self.broker.id_generator()
message = self._prepare_message(*args, **kwargs)
cron_offset = None
if isinstance(cron, CronSpec):
Expand Down Expand Up @@ -219,7 +235,9 @@ async def schedule_by_time(
:param args: function's args.
:param kwargs: function's kwargs.
"""
schedule_id = self.broker.id_generator()
schedule_id = self.custom_schedule_id
if schedule_id is None:
schedule_id = self.broker.id_generator()
message = self._prepare_message(*args, **kwargs)
scheduled = ScheduledTask(
schedule_id=schedule_id,
Expand Down
63 changes: 36 additions & 27 deletions taskiq/receiver/receiver.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import inspect
import signal
from concurrent.futures import Executor
from logging import getLogger
from time import time
Expand Down Expand Up @@ -334,6 +335,12 @@ async def listen(self) -> None: # pragma: no cover
gr.start_soon(self.prefetcher, queue)
gr.start_soon(self.runner, queue)

# Propagate cancellation to the prefetcher & runner
def _cancel(*_: Any) -> None:
gr.cancel_scope.cancel()

signal.signal(signal.SIGINT, _cancel)

if self.on_exit is not None:
self.on_exit(self)

Expand Down Expand Up @@ -361,9 +368,7 @@ async def prefetcher(
message = await iterator.__anext__()
fetched_tasks += 1
await queue.put(message)
except asyncio.CancelledError:
break
except StopAsyncIteration:
except (asyncio.CancelledError, StopAsyncIteration):
break

await queue.put(QUEUE_DONE)
Expand Down Expand Up @@ -394,31 +399,35 @@ def task_cb(task: "asyncio.Task[Any]") -> None:
self.sem.release()

while True:
# Waits for semaphore to be released.
if self.sem is not None:
await self.sem.acquire()

self.sem_prefetch.release()
message = await queue.get()
if message is QUEUE_DONE:
# asyncio.wait will throw an error if there is nothing to wait for
if tasks:
logger.info("Waiting for running tasks to complete.")
await asyncio.wait(tasks, timeout=self.wait_tasks_timeout)
break
try:
# Waits for semaphore to be released.
if self.sem is not None:
await self.sem.acquire()

self.sem_prefetch.release()
message = await queue.get()
if message is QUEUE_DONE:
# asyncio.wait will throw an error if there is nothing to wait for
if tasks:
logger.info("Waiting for running tasks to complete.")
await asyncio.wait(tasks, timeout=self.wait_tasks_timeout)
break

task = asyncio.create_task(
self.callback(message=message, raise_err=False),
)
tasks.add(task)

# We want the task to remove itself from the set when it's done.
#
# Because if we won't save it anywhere,
# python's GC can silently cancel task
# and this behaviour considered to be a Hisenbug.
# https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/
task.add_done_callback(task_cb)
task = asyncio.create_task(
self.callback(message=message, raise_err=False),
)
tasks.add(task)

# We want the task to remove itself from the set when it's done.
#
# Because if we won't save it anywhere,
# python's GC can silently cancel task
# and this behaviour considered to be a Hisenbug.
# https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/
task.add_done_callback(task_cb)

except asyncio.CancelledError:
break

def _prepare_task(self, name: str, handler: Callable[..., Any]) -> None:
"""
Expand Down

0 comments on commit 65ce5e9

Please sign in to comment.