Skip to content

Commit a1b8311

Browse files
committed
Don't wait for API request to finish to be build the next one
1 parent da82db9 commit a1b8311

9 files changed

Lines changed: 55 additions & 20 deletions

File tree

bot/exts/filtering/_ui/ui.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from bot.exts.filtering._filter_context import Event, FilterContext
2222
from bot.exts.filtering._filter_lists import FilterList
2323
from bot.exts.filtering._utils import FakeContext, normalize_type
24-
from bot.utils.lock import lock_arg
24+
from bot.utils.async_utils import lock_arg
2525
from bot.utils.messages import format_channel, format_user, upload_log
2626

2727
log = get_logger(__name__)

bot/exts/filtering/filtering.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@
6060
from bot.exts.utils.snekbox._io import FileAttachment
6161
from bot.log import get_logger
6262
from bot.pagination import LinePaginator
63+
from bot.utils.async_utils import lock_arg
6364
from bot.utils.channel import is_mod_channel
64-
from bot.utils.lock import lock_arg
6565
from bot.utils.message_cache import MessageCache
6666

6767
log = get_logger(__name__)

bot/exts/info/doc/_cog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from bot.converters import Inventory, PackageName, ValidURL
1818
from bot.log import get_logger
1919
from bot.pagination import LinePaginator
20-
from bot.utils.lock import SharedEvent, lock
20+
from bot.utils.async_utils import SharedEvent, lock
2121
from bot.utils.messages import send_denial, wait_for_deletion
2222

2323
from . import NAMESPACE, PRIORITY_PACKAGES, _batch_parser, doc_cache

bot/exts/info/doc/_redis_cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from async_rediscache.types.base import RedisObject
66

77
from bot.log import get_logger
8-
from bot.utils.lock import lock
8+
from bot.utils.async_utils import lock
99

1010
from ._doc_item import DocItem
1111

bot/exts/moderation/clean.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import time
55
from collections import defaultdict
66
from collections.abc import Callable, Iterable
7-
from contextlib import suppress
87
from datetime import datetime
98
from itertools import takewhile
109
from typing import Literal, TYPE_CHECKING
@@ -19,6 +18,7 @@
1918
from bot.converters import Age, ISODateTime
2019
from bot.exts.moderation.modlog import ModLog
2120
from bot.log import get_logger
21+
from bot.utils.async_utils import AsyncExecutor
2222
from bot.utils.channel import is_mod_channel
2323
from bot.utils.messages import upload_log
2424
from bot.utils.modlog import send_log_message
@@ -33,6 +33,9 @@
3333
# Type alias for message lookup ranges.
3434
CleanLimit = Message | Age | ISODateTime
3535

36+
# How many ongoing API requests a clean operation can have at the same time.
37+
CONCURRENT_REQUESTS_POOL = 10
38+
3639

3740
class CleanChannels(Converter):
3841
"""A converter to turn the string into a list of channels to clean, or the literal `*` for all public channels."""
@@ -89,11 +92,11 @@ def mod_log(self) -> ModLog:
8992

9093
@staticmethod
9194
def _validate_input(
92-
channels: CleanChannels | None,
93-
bots_only: bool,
94-
users: list[User] | None,
95-
first_limit: CleanLimit | None,
96-
second_limit: CleanLimit | None,
95+
channels: CleanChannels | None,
96+
bots_only: bool,
97+
users: list[User] | None,
98+
first_limit: CleanLimit | None,
99+
second_limit: CleanLimit | None,
97100
) -> None:
98101
"""Raise errors if an argument value or a combination of values is invalid."""
99102
if first_limit is None:
@@ -295,7 +298,9 @@ async def _delete_messages_individually(self, channel_messages: dict[TextChannel
295298
deleted.append(message)
296299
return deleted
297300

298-
async def _delete_found(self, message_mappings: dict[TextChannel, list[Message]]) -> list[Message]:
301+
async def _delete_found(
302+
self, message_mappings: dict[TextChannel, list[Message]], executor:AsyncExecutor
303+
) -> list[Message]:
299304
"""
300305
Delete the detected messages.
301306
@@ -322,18 +327,19 @@ async def _delete_found(self, message_mappings: dict[TextChannel, list[Message]]
322327

323328
if len(to_delete) == 100:
324329
# Only up to 100 messages can be deleted in a bulk
325-
await channel.delete_messages(to_delete)
330+
executor.submit(channel.delete_messages(to_delete))
326331
deleted.extend(to_delete)
327-
to_delete.clear()
332+
to_delete = []
328333

329334
if not self.cleaning:
330335
return deleted
331336
if len(to_delete) > 0:
332337
# Deleting any leftover messages if there are any
333-
with suppress(NotFound):
334-
await channel.delete_messages(to_delete)
338+
executor.submit(channel.delete_messages(to_delete))
335339
deleted.extend(to_delete)
336340

341+
await executor.gather(return_exceptions=True)
342+
337343
if old_messages:
338344
if not self.cleaning:
339345
return deleted
@@ -418,9 +424,11 @@ async def _clean_messages(
418424
# Needs to be called after standardizing the input.
419425
predicate = self._build_predicate(first_limit, second_limit, bots_only, users, regex)
420426

427+
executor = AsyncExecutor(limit=CONCURRENT_REQUESTS_POOL)
428+
421429
if attempt_delete_invocation:
422430
# Delete the invocation first
423-
await self._delete_invocation(ctx)
431+
executor.submit(self._delete_invocation(ctx))
424432

425433
if self._use_cache(first_limit):
426434
log.trace(f"Messages for cleaning by {ctx.author.id} will be searched in the cache.")
@@ -442,8 +450,9 @@ async def _clean_messages(
442450

443451
# Now let's delete the actual messages with purge.
444452
self.mod_log.ignore(Event.message_delete, *message_ids)
445-
deleted_messages = await self._delete_found(message_mappings)
453+
deleted_messages = await self._delete_found(message_mappings, executor)
446454
self.cleaning = False
455+
log.trace("Cleaning completed, wrapping up")
447456

448457
if not channels:
449458
channels = deletion_channels

bot/exts/moderation/silence.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from bot.bot import Bot
1515
from bot.converters import HushDurationConverter
1616
from bot.log import get_logger
17-
from bot.utils.lock import LockedResourceError, lock, lock_arg
17+
from bot.utils.async_utils import LockedResourceError, lock, lock_arg
1818

1919
log = get_logger(__name__)
2020

bot/exts/utils/reminders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
from bot.log import get_logger
3030
from bot.pagination import LinePaginator
3131
from bot.utils import time
32+
from bot.utils.async_utils import lock_arg
3233
from bot.utils.checks import has_any_role_check, has_no_roles_check
33-
from bot.utils.lock import lock_arg
3434
from bot.utils.messages import send_denial
3535

3636
log = get_logger(__name__)

bot/exts/utils/snekbox/_cog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from bot.exts.utils.snekbox._eval import EvalJob, EvalResult
3333
from bot.exts.utils.snekbox._io import FileAttachment
3434
from bot.log import get_logger
35-
from bot.utils.lock import LockedResourceError, lock_arg
35+
from bot.utils.async_utils import LockedResourceError, lock_arg
3636

3737
if TYPE_CHECKING:
3838
from bot.exts.filtering.filtering import Filtering
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,32 @@ async def wait(self) -> None:
4949
await self._event.wait()
5050

5151

52+
class AsyncExecutor:
53+
"""Execute coroutines with a pool limit for concurrent execution."""
54+
55+
def __init__(self, *, limit: int):
56+
self._semaphore = asyncio.Semaphore(limit)
57+
self._running_tasks = set()
58+
59+
def submit[T](self, coro: Awaitable[T]) -> asyncio.Task[T]:
60+
"""Wraps the coroutine with _semaphore logic, schedules it on the event loop, and ensures cleanup."""
61+
task = asyncio.create_task(self.execute(coro))
62+
self._running_tasks.add(task)
63+
task.add_done_callback(self._running_tasks.discard)
64+
return task
65+
66+
async def execute[T](self, coro: Awaitable[T]) -> T:
67+
"""Executes the coroutine once there is an available slot in the _semaphore."""
68+
async with self._semaphore:
69+
return await coro
70+
71+
async def gather(self, return_exceptions: bool = False) -> list[Any]:
72+
"""Waits for all submitted coroutines to finish execution."""
73+
if self._running_tasks:
74+
return await asyncio.gather(*self._running_tasks, return_exceptions=return_exceptions)
75+
return []
76+
77+
5278
def lock(
5379
namespace: Hashable,
5480
resource_id: ResourceId,

0 commit comments

Comments
 (0)