生のPHP Curlのデフォルトタイムアウトは0(無制限)の罠

掲載日

障害発生

あるシンプルなAPI連携処理がありました。

APIを実行するとJSONが返ってくるので、パースして削除されたIDはDELETE、重複しているIDはUPDATE、重複していないIDはINSERTするようなよくある処理です。

さらにこの更新対象テーブルは一つだけ、実行頻度も30分間隔で利用者からの更新もないテーブル(連携のためだけのテーブル)だったため、Lockなんて早々起きないだろうと思っていました。(敗因1)

取得できなかったIDのデータは削除するという仕様のため、

APIのデータが上手く取得できなかった場合はデータ更新したくないため、念のためtry catchとtransactionだけ実装(敗因2)していました。

 

ここからが本題で、生のPHP cURL処理はなんとデフォルトタイムアウトが0(無制限)です。

ネットワークの状態等によっては一生プロセスが残ることになってしまいます。

ゾンビプロセスが残るのも問題ですが、cURLを実行している位置が不味く、transactionの中でcURLを実行してしまっていたので一生解放待ちのtransactionが生まれてしまいました

よって、アクセス中のテーブルはロックされ、後続の30分おきの処理もロックされているので解放待ちになり…
ということで、一個のAPI実行への考慮不足で、シンプルなつもりの処理で接続数が爆増する不具合を引き起こしてしまいました。

 

CURLOPT_TIMEOUTCURLOPT_CONNECTTIMEOUTを設定する。

以下の処理があるだけで防げた事故です。タイムアウトをちゃんと設定しようという事ですね…。

<?php
$curl = curl_init('https://example.com');
    
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); // 接続タイムアウト
curl_setopt($curl, CURLOPT_TIMEOUT, 30); // リクエスト自体のタイムアウト

なお、普通のサイトにはcURLを実行しても正常なレスポンスが返ってきてしまうので、再現したい場合は以下のような無限ループの処理を作成します。(ファイル名をloop_response_test.phpとします。)

<?php

while (ob_get_level()) {
    ob_end_flush();
}

$index = 0;
while (true) {
    echo $index . "\n";

    flush();

    sleep(1);

    $index++;
}

※無限ループなのでうっかり公開状態で放置しないよう注意してください。

続いて以下のような実行処理を作成し、先に作ったファイルloop_response_test.phpApache等でアクセスできるようにした上で実行します。

<?php

$curl = curl_init("http://XXXX/loop_response_test.php");

$option = array(
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT => 10,
);

curl_setopt_array($curl, $option);

$response = curl_exec($curl);

$curl_errno = curl_errno($curl);
$curl_error = curl_error($curl);


echo var_dump($curl_errno);
echo var_dump($curl_error);

以下のような実行結果が表示され、10秒後にタイムアウトしたことが分かります。

> php timeout_test.php
0
1
2
(中略)
8
9
int(28)
string(67) "Operation timed out after 10010 milliseconds with 20 bytes received"

場合によってはファイルロックもした方が良い

こういった定期的に実行する処理には以下のようなファイルロック処理も有効です。
(自分の場合、処理自体はphpで作って実行はshellscriptで管理しているので以下はshellscriptの処理になります。一貫してphpでやっても問題ないと思います。)

#!/bin/bash

lockfile=/tmp/.LockProcessName
if [ -f $lockfile ]
then
    cat $lockfile
    echo "上記時間よりロック中"

    # アラートメール(※処理の間隔によっては、時間の比較を入れて30分以上ロックされていたらメール等、適切に変更する。)
    sendmail -f root@example.com  -t example@example.com -s "[warning]【処理名】がロックされています。"

    exit
fi

date > $lockfile

# ここに定期的に実行する処理

rm -f $lockfile

ロックされていたらアラートメールを送って対応も出来ますし、夜間で爆睡していて気が付かなかったとしてもAPI連携が止まるだけで他のシステムには影響を及ぼさずに済みます。

※複雑な依存関係がある場合等ファイルロックだけだとどうにもならないケース等あるのであくまで解決策の一つとして。

参考

  • cURL 関数の実行を許容する最大秒数を指定します。 デフォルトは 0 で、転送中に関数がタイムアウトしないことを意味します。 cURL 7.1.0 以降で利用可能です。
記事の作成者のW.Aのアイコン

この記事を書いた人

W.A
茨城県在住Webエンジニアです。 PHP、PostgreSQL、Linuxなどを業務で使用しています。 趣味ではGoやNuxt、Flutterをやってます。

Comment

関連記事

  • PHPで見た目が同じ違う漢字に立ち向かうのサムネイル画像

    PHPで見た目が同じ違う漢字に立ち向かう

    掲載

    見た目は同じなのにDBやCtrl+Fでの文字列検索にヒットしない文字がある原因は「CJK部首補助」等の特殊な文字コードの混入かもしれません。PDFコピペ等で発生するこういったトラブルを、PHPでの自前マッピングで解決した備忘録です。

check コピーしました!