Skip to content

Client instances conflict in PyQT5 thread  #4513

@MiHaKun

Description

@MiHaKun

Code that causes the issue

I don't know whether it is a "bug"( I have upgrade) . If I shouldn't have posted this question in this section, I apologize. Please let me know where it would be more appropriate. This issue seems to be related to a conflict.


To avoid blocking the normal operation of the UI, I start a thread when the user clicks a button. The thread uses asyncio event loop execute an asynchronous entry program.

The following code is a simplified version of the implementation of this process.

WinMain

    def OnBtnStartClick(self):
        self.btnStart.setEnabled(False)
        for session in self.sessions:
             self.session_queue.put(session) # type: queue.Queue()
        threading.Thread(target=self.BGThreadBody, args=(features,)).start()

    def BGThreadBody(self, features):
        self.loop = asyncio.new_event_loop()
        self.loop.run_until_complete(self._run_tasks(features))

    async def _run_tasks(self, features):
        cs = []
        for idx in range(self.uiThreadCount.value()): # 1 : works well , 2: exception popup ..
            task = TelegramFeatureTask(
                name=str(idx + 1),
                session_queue=self.session_queue,
            )
            c = self.loop.create_task(task._run_async())
            self.tasks.append(task)
            cs.append(c)
        await asyncio.gather(*cs)

feature

class TelegramFeatureTask:
    def __init__(
        self,
        name: str,
        session_queue: Queue):
        self.session_queue = session_queue
        self.name = name


    async def _run_async(self):
        while not self.session_queue.empty():
            s = self.session_queue.get(timeout=1)
            if not s:
                break
            tg_session = StringSession(s)
            client = TelegramClient(
                session=tg_session,
                **config['login_param'] # simple read from config file
            )        
            await asyncio.wait_for(client.connect(), timeout=10)
            # await client.connect()
            me = await client.get_me()
            if not me:
                raise NoMeError()
            upload = await self.client.upload_file(config['avatars'][self.name])
            photo = await self.client(functions.photos.UploadProfilePhotoRequest(file=upload))
            await client.disconnect()
            

these code works well in console multithread . But failed in PyQT thread .

upload = await client.upload_file(avatar)
photo = await client(functions.photos.UploadProfilePhotoRequest(file=upload))

Exception detail .

File part 0 missing (caused by UploadProfilePhotoRequest)
Sleeping for 4s (0:00:04) on UpdateProfilePhotoRequest flood wait

Expected behavior

no exceptions .

Actual behavior

File part 0 missing (caused by UploadProfilePhotoRequest)
Sleeping for 4s (0:00:04) on UpdateProfilePhotoRequest flood wait

Traceback

I debug this problem for days .
I add log in telethon's UserMethods call and found there mightbe a conflict .

the log code added in telethon/client/users.py:

class UserMethods:
    async def __call__(self: 'TelegramClient', request, ordered=False, flood_sleep_threshold=None):
        return await self._call(self._sender, request, ordered=ordered)

    async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sleep_threshold=None):
        # print(f"self: {id(self)} equl:{id(asyncio.get_running_loop()) == id(self._loop)} running_loop: {id(asyncio.get_running_loop())} , self._loop: {id(self._loop)} request: {type(request)}`{id(request)}` sender: {type(sender)}`{id(sender)}`")
        print(f"self: {id(self)}  request: {type(request)}`{id(request)}` sender: {type(sender)}`{id(sender)}`")
        if self._loop is not None and self._loop != helpers.get_running_loop():
            raise RuntimeError('The asyncio event loop must not change after connection (see the FAQ for details)')
        # if the loop is None it will fail with a connection error later on
        if flood_sleep_threshold is None:
            flood_sleep_threshold = self.flood_sleep_threshold
        requests = (request if utils.is_list_like(request) else (request,))
        for r in requests:
            if not isinstance(r, TLRequest):
                raise _NOT_A_REQUEST()
            await r.resolve(self, utils)

# ...............
# ...............

I print the self(client) 's id, self._loop id (never changed) , runningloop is equl self._loop ( they are always the same ) request type and id , sender's id .

the confilct log is here :
image

  • the task count is 2 .
  • the get_me/ upload 's request log is right .
  • the uploadprofilerequest's log is confilct . the second task's instance changed to the first task's instance and caused error TypeInputPhoto and flood rpc error .

I read the FAQ and Compatibility and Convenience document .
I understand that the esteemed author has advised everyone to avoid using threads whenever possible. However, it seems that in GUI programs, the only way to prevent blocking is to start a thread. Therefore, I would still appreciate any suggestions regarding this issue. Thank you.

Btw: In fact , my oldest code was run each client in thread's eventloop (multithread which have one loop each) . it caused " RunningTimeError(The asyncio event loop must not change after connection (see the FAQ for details)) " , but the code works well in console application too ....

Telethon version

1.38.1

Python version

3.12.7

Operating system (including distribution name and version)

Windows 11

Other details

PyQt5==5.15.11
PyQt5-Qt5==5.15.2
PyQt5_sip==12.15.0

Checklist

  • The error is in the library's code, and not in my own.
  • I have searched for this issue before posting it and there isn't an open duplicate.
  • I ran pip install -U https://github.com/LonamiWebs/Telethon/archive/v1.zip and triggered the bug in the latest version.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions