From 38c6407ffd642234118bfde4f97fafed883144ae Mon Sep 17 00:00:00 2001 From: Rapptz Date: Thu, 14 Aug 2025 00:39:41 -0400 Subject: [PATCH] Maintain a reference to View dispatched tasks --- discord/ui/modal.py | 6 ++++-- discord/ui/view.py | 24 +++++++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 4e6d0eb22..39f6bd906 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -221,8 +221,10 @@ class Modal(View): def _dispatch_submit( self, interaction: Interaction, components: List[ModalSubmitComponentInteractionDataPayload] - ) -> None: - asyncio.create_task(self._scheduled_task(interaction, components), name=f'discord-ui-modal-dispatch-{self.id}') + ) -> asyncio.Task[None]: + return asyncio.create_task( + self._scheduled_task(interaction, components), name=f'discord-ui-modal-dispatch-{self.id}' + ) def to_dict(self) -> Dict[str, Any]: payload = { diff --git a/discord/ui/view.py b/discord/ui/view.py index 57e036864..7b4fdd1c4 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -36,6 +36,7 @@ from typing import ( Optional, Sequence, TYPE_CHECKING, + Set, Tuple, Type, Union, @@ -573,11 +574,11 @@ class BaseView: self.__stopped.set_result(True) asyncio.create_task(self.on_timeout(), name=f'discord-ui-view-timeout-{self.id}') - def _dispatch_item(self, item: Item, interaction: Interaction): + def _dispatch_item(self, item: Item, interaction: Interaction) -> Optional[asyncio.Task[None]]: if self.__stopped.done(): return - asyncio.create_task(self._scheduled_task(item, interaction), name=f'discord-ui-view-dispatch-{self.id}') + return asyncio.create_task(self._scheduled_task(item, interaction), name=f'discord-ui-view-dispatch-{self.id}') def _refresh(self, components: List[Component]) -> None: # fmt: off @@ -842,6 +843,7 @@ class ViewStore: # component_type is the key self._dynamic_items: Dict[re.Pattern[str], Type[DynamicItem[Item[Any]]]] = {} self._state: ConnectionState = state + self.__tasks: Set[asyncio.Task[None]] = set() @property def persistent_views(self) -> Sequence[BaseView]: @@ -855,6 +857,10 @@ class ViewStore: # fmt: on return list(views.values()) + def add_task(self, task: asyncio.Task[None]) -> None: + self.__tasks.add(task) + task.add_done_callback(self.__tasks.discard) + def add_dynamic_items(self, *items: Type[DynamicItem[Item[Any]]]) -> None: for item in items: pattern = item.__discord_ui_compiled_template__ @@ -965,9 +971,11 @@ class ViewStore: for pattern, item in self._dynamic_items.items(): match = pattern.fullmatch(custom_id) if match is not None: - asyncio.create_task( - self.schedule_dynamic_item_call(component_type, item, interaction, custom_id, match), - name=f'discord-ui-dynamic-item-{item.__name__}-{custom_id}', + self.add_task( + asyncio.create_task( + self.schedule_dynamic_item_call(component_type, item, interaction, custom_id, match), + name=f'discord-ui-dynamic-item-{item.__name__}-{custom_id}', + ) ) def dispatch_view(self, component_type: int, custom_id: str, interaction: Interaction) -> None: @@ -1014,7 +1022,9 @@ class ViewStore: return # Note, at this point the View is *not* None - item.view._dispatch_item(item, interaction) # type: ignore + task = item.view._dispatch_item(item, interaction) # type: ignore + if task is not None: + self.add_task(task) def dispatch_modal( self, @@ -1027,7 +1037,7 @@ class ViewStore: _log.debug("Modal interaction referencing unknown custom_id %s. Discarding", custom_id) return - modal._dispatch_submit(interaction, components) + self.add_task(modal._dispatch_submit(interaction, components)) def remove_interaction_mapping(self, interaction_id: int) -> None: # This is called before re-adding the view