LonamiWebs/Telethon

Client instances conflict in PyQT5 thread

MiHaKun opened this issue · 1 comments

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.