1

I am just over 3 weeks into teaching myself python so bear with me :')

I have a task loop that runs every 5 seconds indefinitely while the bot is running, the bot runs off the "RED discord bot" framework that uses modules called "cogs" which is what ive been working on, here is the portion of my code i've been having trouble with:

    # Crosschat loop logic
    @tasks.loop(seconds=3)
    async def getchat(self):
        data = await self.config.all_guilds()
        for guildID in data:
            guild = self.bot.get_guild(int(guildID))
            if not guild:
                continue
            guildsettings = await self.config.guild(guild).clusters()
            if not guildsettings:
                continue

            for cluster in guildsettings:
                if not guildsettings[cluster]:
                    continue
                globalchat = guildsettings[cluster]["globalchatchannel"]
                for server in guildsettings[cluster]["servers"]:
                    guildsettings[cluster]["servers"][server]["cluster"] = cluster
                    guildsettings[cluster]["servers"][server]["globalchat"] = globalchat
                    if not guildsettings[cluster]["servers"][server]["chatchannel"]:
                        return
                    channel = guild.get_channel(int(guildsettings[cluster]["servers"][server]["chatchannel"]))
                    if not channel:
                        return

                    """Loop option #1 using discord.ext.tasks"""
                    # await self.getchatrcon(guild,
                    #                        guildsettings[cluster]["servers"][server]["cluster"],
                    #                        guildsettings[cluster]["servers"][server]
                    #                        )

                    """Loop option #2 using asyncio task loop"""
                    chattask = []
                    chattask.append(self.getchatrcon(guild,
                                                  guildsettings[cluster]["servers"][server]["cluster"],
                                                  guildsettings[cluster]["servers"][server]
                                                  ))

                    # Gathers the getchat tasks with a timeout
                    try:
                        tasks = asyncio.gather(*chattask)
                        if len(asyncio.all_tasks()) > 60:
                            print(f"Task list full{len(asyncio.all_tasks())}, skipping iteration")
                            return
                        await asyncio.wait_for(tasks, timeout=2)
                    except asyncio.TimeoutError as e:
                        print(f"task timeout: {e}")
                        await asyncio.sleep(5)

    # Rcon function for getchat loop, parses the response to send to designated discord channels.
    async def getchatrcon(self, guild, cluster, server):
        guildsettings = await self.config.guild(guild).clusters()
        adminlogchannel = guild.get_channel(int(guildsettings[cluster]["adminlogchannel"]))
        globalchat = guild.get_channel(int(server["globalchat"]))
        chatchannel = guild.get_channel(int(server["chatchannel"]))

        try:
            res = await rcon.asyncio.rcon(
                command="getchat",
                host=server['ip'],
                port=server['port'],
                passwd=server['password']
            )
        except OSError as e:
            if e.osrror == 121:
                return await asyncio.sleep(30)
            if e.osrror == 10038:
                return await asyncio.sleep(60)


        if "Server received, But no response!!" in res:
            return
        msgs = res.split("\n")
        filteredmsg = []
        for msg in msgs:
            if msg.startswith("AdminCmd:"):
                adminmsg = msg
                await adminlogchannel.send(f"**{server['name'].capitalize()}**\n{box(adminmsg, lang='python')}")
            if "): " not in msg:
                continue
            if "tribe" and ", ID" in msg.lower():
                continue  # Add more features at a later date for tribe log channels
            else:
                if msg not in ['', ' ', 'Server received, But no response!! ']:
                    if not msg.startswith('SERVER:'):
                        filteredmsg.append(msg)
        for msg in filteredmsg:
            await globalchat.send(f"{chatchannel.mention}: {msg}")
            await chatchannel.send(msg)


    # Just waits till bot is ready to do the chat loop
    @getchat.before_loop
    async def before_getchat(self):
        print("Getting crosschat loop ready.")
        await self.bot.wait_until_red_ready()

What this code does is use the "getchat" command on all servers and return the messages to the channels i have set for the specified server that it came from. The code does work pretty well for the first hour or so of running it, however I get a "WinError 10038" that starts spamming the console after some time:

[07:28:46] CRITICAL Caught unhandled exception in event loop:                                                [red.main]
                    Exception in callback _ProactorBasePipeTransport._call_connection_lost(None)
┌───────────────────────────────────────── Traceback (most recent call last) ─────────────────────────────────────────┐
│ C:\Python38\lib\asyncio\events.py:81 in _run                                                                        │
│ >  81             self._context.run(self._callback, *self._args)                                                    │
│ C:\Python38\lib\asyncio\proactor_events.py:162 in _call_connection_lost                                             │
│ > 162                 self._sock.shutdown(socket.SHUT_RDWR)                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
OSError: [WinError 10038] An operation was attempted on something that is not a socket

I've noticed that when i restart the bot, the console gets flooded with this RuntimeError hundreds of times:

RuntimeError: Event loop is closed
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x000002EE9F655E50>
Traceback (most recent call last):
  File "C:\Python38\lib\asyncio\proactor_events.py", line 116, in __del__
    self.close()
  File "C:\Python38\lib\asyncio\proactor_events.py", line 108, in close
    self._loop.call_soon(self._call_connection_lost, None)
  File "C:\Python38\lib\asyncio\base_events.py", line 719, in call_soon
    self._check_closed()
  File "C:\Python38\lib\asyncio\base_events.py", line 508, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed

My issue/concerns/theories: The cog works fine for about an hour before starting to spam the WinError, and the massive amount of RuntimeError: Event loop is closed being spammed on bot restart made me think it was the task loop list having too many tasks overwhelming the bot maybe.

My question(s) is/are this:

Could the WinError be due to my "chattask" task loop list being overflowed with appended tasks?

Did I properly implement the timeout I tried to include?

Is there a better way to do this task loop?

The operating system the bot is running on is Windows 10, i have specified the following event loop policy in the code as well: asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

I am using this library for the RCON protocol: https://pypi.org/project/rcon/

I hope I clarified what my issue is well enough, thank you to anyone who can spot me some pointers and holding my hand a bit is always appreciated, learning py has been a pandoras box of trial and error but super fun so far!

Vertyco
  • 11
  • 2

0 Answers0