はじめに
以前この記事でDiscord Botが二重起動しているという話をしました。
何かしらの無限ループを組み込んでいるBotが再起動した際に再び無限ループが実行されてしまっており、しかも元々あった無限ループが消えずに残ってしまっているため同時に複数の無限ループが発生してしまっていた、というのが問題でした。
先述の記事ではたまにDockerコンテナ自体を再起動するという解決策を取っていましたが、それは面倒です。
というわけで、今回は二重起動を根本的に解決する方法を調べたので、それを書き残しておこうと思います。
解決法
おそらく通信が不安定になったなどの理由で一旦Botがネットワークから切断され、再接続の際にon_ready関数によってスタートされるメインの無限ループが再度実行されてしまっていたことが二重起動の原因でした。
ここで、「元々あった無限ループが消えずに残っている」ことに注目します。無限ループを実行させるとき、すでに無限ループがあるかを確認し、あったら実行しない、ということをすればいいわけです。
この機能を実装するために、asyncio.create_taskを用います。
これにより、無限ループをコルーチンとしてスケジューリングすることができます。
スケジューリングが残っているかどうかで、無限ループがすでにあるか無いかを判断するわけです。
普通はこのメソッドをタスクの並行処理などに用いるようですが、今回は上記のような用い方をします。
サンプルプログラム
今回紹介した二重起動対策を組み込んだDiscord BotのメインPythonプログラムは以下の通りです。
import asyncio
import os
import discord
from discord.ext import commands
TOKEN = os.getenv("TOKEN")
DISCORD_CHANNEL_ID = int(os.environ.get("DISCORD_CHANNEL_ID"))
intent = discord.Intents.default()
intent.message_content = True
client = commands.Bot(command_prefix="-", intents=intent)
# すでに無限ループがあるかを示すグローバル変数
task = None
async def main():
while True:
# 処理
@client.event
async def on_ready():
global task
# スケジューリングがないなら以下を実行
if task is None or task.done():
print("Bot is ready!")
# スケジューリングする
task = asyncio.create_task(main())
client.run(TOKEN)
スケジューリングを組み込んだことにより、on_ready関数が再接続の際に再び実行されたとしても、main関数はコンテナ起動時の一回しか実行されないこととなります。
その他の設定ファイルはこの記事などを参考に設定してください。
おわりに
この方法を試すようになってから、一応Botが二重に起動してしまっているということはなくなりました。
非常に運用が楽になったので、もし無限ループを持つDiscord Botを運用している方がいらっしゃったら、この方法をぜひお試しください。