PythonのCGIでタイムアウト処理
[質問]
Webサーバーで、Pythonのsympy.factorint() を利用し、素因数分解するCGIを動かしています。こういう場合、60秒でタイムアウトする処理をCGIだけで書けますか?
[回答] Gemini
結論から申し上げますと、CGIプログラム(Pythonスクリプト)側だけで完全に正確なタイムアウト制御を行うのは、少し工夫が必要です。
特に sympy.factorint() のような重い計算処理は、Pythonの「シグナル」や「マルチプロセッシング」を利用して制御する必要があります。
なぜ単純にはいかないのか?
通常、factorint() のような関数は処理が完了するまで制御を戻しません。そのため、メインのスレッドで単純に時間を計測しても、「計算の真っ最中」に処理を止めることができないからです。
CGI側で実装する主な2つのアプローチを紹介します。
1. シグナル(signal)を使う方法(手軽)
UNIX系サーバー(Linux等)であれば、signal モジュールを使って、指定時間後に例外を発生させることができます。
import signal
from sympy import factorint
# タイムアウト時に発生させるカスタム例外
class TimeoutException(Exception):
pass
def handler(signum, frame):
raise TimeoutException()
# 60秒のタイマーを設定
signal.signal(signal.SIGALRM, handler)
signal.alarm(60)
try:
# ここに重い処理
result = factorint(n)
print(result)
except TimeoutException:
print("Content-Type: text/html\n")
print("エラー:60秒以内に計算が終わりませんでした。")
finally:
# タイマーを解除
signal.alarm(0)
- メリット: コードがシンプル。
- デメリット: Windowsサーバーでは動作しません。また、計算がCライブラリなどの深いレベルでロックされていると、シグナルが割り込めないケースが稀にあります。
2. multiprocessing を使う方法(確実)
別のプロセスとして計算を実行し、メインプロセスがそれを監視する方法です。
from multiprocessing import Process, Queue
from sympy import factorint
import time
def worker(n, queue):
res = factorint(n)
queue.put(res)
if __name__ == "__main__":
n = 123456789... # 巨大な数
queue = Queue()
p = Process(target=worker, args=(n, queue))
p.start()
# 60秒間待機
p.join(timeout=60)
if p.is_alive():
p.terminate() # 処理を強制終了
p.join()
print("Status: 504 Gateway Timeout\n")
print("Time out!")
else:
result = queue.get()
print("Content-Type: text/plain\n")
print(result)
- メリット: OSを問わず動作し、確実にプロセスをキルできるため安全です。
- デメリット: プロセスを生成するため、メモリ消費が少し増えます。
重要な注意点:サーバー設定との兼ね合い
CGIだけで頑張っても、Webサーバー(ApacheやNginx)側のタイムアウト設定が60秒以下になっていると、プログラムが動いている途中でサーバー側から接続を切られてしまいます(504 Gateway Timeout)。
- Apacheの場合:
Timeoutディレクティブ - Nginxの場合:
proxy_read_timeoutやfastcgi_read_timeout
これらが十分な長さに設定されているか確認してください。
次のステップ
まずは「signalを使う方法」が環境で動作するか試してみるのが一番早いです。もし、サーバーのOSや具体的な実行環境(Linux/Windowsなど)を教えていただければ、より最適なコード案を提示できます。
どの環境でお使いでしょうか?