Skip to content

Potential Bug: asyncio.create_task lacks strong references in telebot.AsyncTeleBot._process_polling #2572

Closed

Description

@fshp971

Bug Description

In the asynchronous polling loop of AsyncTeleBot (e.g., inside telebot.AsyncTeleBot._process_polling), incoming updates are dispatched to process_new_updates using asyncio.create_task(). However, the resulting asyncio.Task object is not assigned to any variable or collection, meaning no strong reference is kept. (See below code block, also see here)

        try:
            while self._polling:
                try:
                    updates = await self.get_updates(offset=self.offset, allowed_updates=allowed_updates, timeout=timeout, request_timeout=request_timeout)
                    if updates:
                        self.offset = updates[-1].update_id + 1
                        # noinspection PyAsyncCall
                        asyncio.create_task(self.process_new_updates(updates)) # Seperate task for processing updates
                    if interval: await asyncio.sleep(interval)
                    error_interval = 0.25 # drop error_interval if no errors

According to the official Python asyncio documentation, the event loop only maintains weak references to tasks. If a task yields control back to the event loop (e.g., awaiting network I/O or a sleep), Python's Garbage Collector (GC) may destroy the unreferenced task mid-execution. This results in the task disappearing silently without raising any exceptions.

I also copy the official explanation from Python documentation (https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) here to better illustrate the risk:

Important: Save a reference to the result of this function, to avoid a task disappearing mid-execution. The event loop only keeps weak references to tasks. A task that isn’t referenced elsewhere may get garbage collected at any time, even before it’s done. For reliable “fire-and-forget” background tasks, gather them in a collection:

Potential Solution

Add a queue to AsyncTeleBot to store asyncio.Task created via asyncio.create_task() is enough to solve the problem.

If it is ok, I would be happy to create a PR to fix it.

Activity

  1. All-The-Foxes commented on Mar 15, 2026

    @All-The-Foxes
    Contributor

    Why not just have a loop in process_new_updates that sleeps for 0.1 seconds if there's nothing to do and checks a class list variable that process_polling inserts the updates into instead of firing a background task every time there's updates? When there's items in the list, process_new_updates will pop them out so they're consumed. It doesn't need to be locked because nothing other than those two coros will ever be touching the list of pending updates.

    Or just await it directly. I don't see why you'd track and gather a list containing a single task every time updates are sent to your bot. The latent part was fetching them, processing them should be fast.

  2. gilangjavier commented on Mar 18, 2026

    @gilangjavier

    I can help fix this. I'll submit a PR once it's done.

  3. added a commit that references this issue on Apr 17, 2026
    1f4b28f
  4. Dramex commented on Apr 17, 2026

    @Dramex
    Contributor

    Opened #2588 with the minimal strong-ref fix (mirrors the reporter's proposal + the asyncio-docs pattern). @fshp971 — apologies if you were still planning a PR; happy to close mine if you prefer. @All-The-Foxes — I chose to preserve the fire-and-forget semantics rather than awaiting inline (since that changes concurrency behavior for existing bots), but noted in the PR that the alternatives you suggested are reasonable if maintainers prefer a different direction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions