コンテンツにスキップ

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_timeoutfastcgi_read_timeout

これらが十分な長さに設定されているか確認してください。


次のステップ

まずは「signalを使う方法」が環境で動作するか試してみるのが一番早いです。もし、サーバーのOSや具体的な実行環境(Linux/Windowsなど)を教えていただければ、より最適なコード案を提示できます。

どの環境でお使いでしょうか?