Skip to content

Commit 6ef1a22

Browse files
committed
Merge branch 'release/0.2.0'
2 parents 820287a + 4055588 commit 6ef1a22

File tree

11 files changed

+895
-838
lines changed

11 files changed

+895
-838
lines changed

docs/examples/extending/broker.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import asyncio
2-
from typing import Any, Callable, Coroutine, Optional, TypeVar
1+
from typing import AsyncGenerator, Callable, Optional, TypeVar
32

43
from taskiq import AsyncBroker, AsyncResultBackend, BrokerMessage
54

@@ -29,14 +28,9 @@ async def kick(self, message: BrokerMessage) -> None:
2928
# Send a message.
3029
pass
3130

32-
async def listen(
33-
self,
34-
callback: Callable[[BrokerMessage], Coroutine[Any, Any, None]],
35-
) -> None:
36-
loop = asyncio.get_event_loop()
31+
async def listen(self) -> AsyncGenerator[BrokerMessage, None]:
3732
while True:
3833
# Get new message.
39-
# new_message = ...
40-
# Create a new task to execute.
41-
# loop.create_task(callback(new_message))
42-
pass
34+
new_message: BrokerMessage = ... # type: ignore
35+
# Yield it!
36+
yield new_message

docs/extending-taskiq/broker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ These rules are optional, and it's ok if your broker doesn't implement them.
1919
1. If the message has the `delay` label with int or float number, this task's `execution` must be delayed
2020
with the same number of seconds as in the delay label.
2121
2. If the message has the `priority` label, this message must be sent with priority. Tasks with
22-
higher priorities are executed faster.
22+
higher priorities are executed sooner.

poetry.lock

Lines changed: 844 additions & 793 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "taskiq"
3-
version = "0.1.8"
3+
version = "0.2.0"
44
description = "Distributed task queue with full async support"
55
authors = ["Pavel Kirilin <[email protected]>"]
66
maintainers = ["Pavel Kirilin <[email protected]>"]

taskiq/abc/broker.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import ( # noqa: WPS235
88
TYPE_CHECKING,
99
Any,
10+
AsyncGenerator,
1011
Awaitable,
1112
Callable,
1213
Coroutine,
@@ -149,17 +150,14 @@ async def kick(
149150
"""
150151

151152
@abstractmethod
152-
async def listen(
153-
self,
154-
callback: Callable[[BrokerMessage], Coroutine[Any, Any, None]],
155-
) -> None:
153+
def listen(self) -> AsyncGenerator[BrokerMessage, None]:
156154
"""
157155
This function listens to new messages and yields them.
158156
159157
This it the main point for workers.
160158
This function is used to get new tasks from the network.
161159
162-
:param callback: function to call when message received.
160+
:yield: incoming messages.
163161
:return: nothing.
164162
"""
165163

taskiq/brokers/inmemory_broker.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import inspect
22
from collections import OrderedDict
3-
from typing import Any, Callable, Coroutine, Optional, TypeVar, get_type_hints
3+
from typing import Any, AsyncGenerator, Callable, Optional, TypeVar, get_type_hints
44

55
from taskiq_dependencies import DependencyGraph
66

@@ -143,17 +143,13 @@ async def kick(self, message: BrokerMessage) -> None:
143143

144144
await self.receiver.callback(message=message)
145145

146-
async def listen(
147-
self,
148-
callback: Callable[[BrokerMessage], Coroutine[Any, Any, None]],
149-
) -> None:
146+
def listen(self) -> AsyncGenerator[BrokerMessage, None]:
150147
"""
151148
Inmemory broker cannot listen.
152149
153150
This method throws RuntimeError if you call it.
154151
Because inmemory broker cannot really listen to any of tasks.
155152
156-
:param callback: message callback.
157153
:raises RuntimeError: if this method is called.
158154
"""
159155
raise RuntimeError("Inmemory brokers cannot listen.")

taskiq/brokers/shared_broker.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Callable, Coroutine, Optional, TypeVar
1+
from typing import AsyncGenerator, Optional, TypeVar
22

33
from taskiq.abc.broker import AsyncBroker
44
from taskiq.decor import AsyncTaskiqDecoratedTask
@@ -59,16 +59,12 @@ async def kick(self, message: BrokerMessage) -> None:
5959
"without setting the default_broker.",
6060
)
6161

62-
async def listen(
63-
self,
64-
callback: Callable[[BrokerMessage], Coroutine[Any, Any, None]],
65-
) -> None: # type: ignore
62+
async def listen(self) -> AsyncGenerator[BrokerMessage, None]: # type: ignore
6663
"""
6764
Shared broker cannot listen to tasks.
6865
6966
This method will throw an exception.
7067
71-
:param callback: message callback.
7268
:raises TaskiqError: if called.
7369
"""
7470
raise TaskiqError("Shared broker cannot listen")

taskiq/brokers/zmq_broker.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import asyncio
21
from logging import getLogger
3-
from typing import Any, Callable, Coroutine, Optional, TypeVar
2+
from typing import AsyncGenerator, Callable, Optional, TypeVar
43

54
from taskiq.abc.broker import AsyncBroker
65
from taskiq.abc.result_backend import AsyncResultBackend
@@ -62,16 +61,12 @@ async def kick(self, message: BrokerMessage) -> None:
6261
with self.socket.connect(self.sub_host) as sock:
6362
await sock.send_string(message.json())
6463

65-
async def listen(
66-
self,
67-
callback: Callable[[BrokerMessage], Coroutine[Any, Any, None]],
68-
) -> None:
64+
async def listen(self) -> AsyncGenerator[BrokerMessage, None]:
6965
"""
7066
Start accepting new messages.
7167
72-
:param callback: function to call when message received.
68+
:yields: incoming messages.
7369
"""
74-
loop = asyncio.get_event_loop()
7570
with self.socket.connect(self.sub_host) as sock:
7671
while True:
7772
received_str = await sock.recv_string()
@@ -80,4 +75,4 @@ async def listen(
8075
except ValueError:
8176
logger.warning("Cannot parse received message %s", received_str)
8277
continue
83-
loop.create_task(callback(broker_msg))
78+
yield broker_msg

taskiq/cli/worker/args.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class WorkerArgs:
2323
shutdown_timeout: float = 5
2424
reload: bool = False
2525
no_gitignore: bool = False
26+
max_async_tasks: int = 100
2627

2728
@classmethod
2829
def from_cli( # noqa: WPS213
@@ -123,6 +124,14 @@ def from_cli( # noqa: WPS213
123124
dest="no_gitignore",
124125
help="Do not use gitignore to check for updated files.",
125126
)
127+
parser.add_argument(
128+
"--max-async-tasks",
129+
type=int,
130+
dest="max_async_tasks",
131+
default=100,
132+
help="Maximum simultaneous async tasks per worker process. "
133+
+ "Infinite if less than 1",
134+
)
126135

127136
namespace = parser.parse_args(args)
128137
return WorkerArgs(**namespace.__dict__)

taskiq/cli/worker/async_task_runner.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
from logging import getLogger
23

34
from taskiq.abc.broker import AsyncBroker
@@ -25,4 +26,24 @@ async def async_listen_messages(
2526
logger.info("Inicialized receiver.")
2627
receiver = Receiver(broker, cli_args)
2728
logger.info("Listening started.")
28-
await broker.listen(receiver.callback)
29+
tasks = set()
30+
async for message in broker.listen():
31+
task = asyncio.create_task(
32+
receiver.callback(message=message, raise_err=False),
33+
)
34+
tasks.add(task)
35+
36+
# We want the task to remove itself from the set when it's done.
37+
#
38+
# Because python's GC can silently cancel task
39+
# and it considered to be Hisenbug.
40+
# https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/
41+
task.add_done_callback(tasks.discard)
42+
43+
# If we have finite number of maximum simultanious tasks,
44+
# we await them when we reached the limit.
45+
# But we don't await all of them, we await only first completed task,
46+
# and then continue.
47+
if 1 <= cli_args.max_async_tasks <= len(tasks):
48+
_, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
49+
tasks = pending

0 commit comments

Comments
 (0)