検索
ホームページバックエンド開発PHPチュートリアルPHPスクリプト実行時のタイムアウト機構の詳細説明、PHPスクリプトの仕組みの詳細説明_PHPチュートリアル

PHPスクリプト実行時のタイムアウトの仕組みを詳しく解説, PHPスクリプトの仕組みを詳しく解説

PHP開発を行う際、スクリプトのタイムアウトを制御するためにmax_input_timeとmax_execution_timeを設定することが多いです。しかし、その背後にある原理については考えたこともありませんでした。

この 2 日間の自由時間を利用して、この問題を学習してください。

タイムアウト設定

PHP の ini 設定がどのように機能するかはよくあるトピックです。

まず、php.iniで設定します。 php が起動すると (php_module_startup 段階)、ini ファイルを読み取って解析しようとします。簡単に言うと、解析プロセスは、ini ファイルを分析し、正当なキーと値のペアを抽出して、configuration_hash テーブルに保存することです。

OK、その後、php はさらに zend_startup_extensions を呼び出して各モジュール (php コア モジュールとロードする必要があるすべての拡張機能を含む) を開始します。各モジュールのスタートアップ関数では、REGISTER_INI_ENTRIES アクションが完了します。 REGISTER_INI_ENTRIES は、configuration_hash テーブルからモジュールに対応するいくつかの構成を取り出し、処理関数を呼び出し、最後に処理された値をモジュールのグローバル変数に格納する役割を果たします。

max_input_time、max_execution_time これら 2 つの設定は php Core モジュールに属します。 php Core の場合、REGISTER_INI_ENTRIES は引き続き php_module_startup で発生します。 php Core モジュールにも属する設定には、expose_php、display_errors、memory_limit などが含まれます...

概略図は次のとおりです。

リーリー

上で述べたように、REGISTER_INI_ENTRIES は構成ごとに異なる関数を呼び出します。 max_execution_time に対応する関数を直接見てみましょう:

リーリー

PHP の起動フェーズに注目するだけなので、今のところ前半だけを見てください。この関数の動作は非常に単純で、max_execution_time は EG (timeout_秒) に格納されます。

max_input_time に関しては、特別な処理関数はありません。デフォルトでは、max_input_time は PG (max_input_time) に格納されます。

REGISTER_INI_ENTRIES が完了すると、次のことが起こります:

max_execution_time ----> EG(timeout_秒)に保存します

max_input_time ----> PG(max_input_time)に保存します

リクエストタイムアウト制御

PHP の起動フェーズで何が起こるかを理解したので、実際にリクエストを処理するときに PHP がどのようにタイムアウトを管理するかを続けて見てみましょう。

php_request_startup 関数には次のコードがあります:

リーリー

php_request_startupのタイミングは非常に特殊です。

CGI を例にとると、php_request_startup は、php が元のリクエストと CGI からいくつかの CGI 環境変数を取得した後にのみ呼び出されます。実際に上記のコードを実行すると、リクエストは取得できているのでSG(request_info)は準備完了状態ですが、PHPの$_GET、$_POST、$_FILEなどのスーパーグローバル変数はまだ生成されていません。

コードから理解する:

1. ユーザーが max_input_time を -1 に設定するか、設定しない場合、スクリプトのライフサイクルは EG (timeout_秒) によってのみ制限されます。

2. それ以外の場合、リクエスト起動フェーズのタイムアウト制御はPG(max_input_time)の対象となります。

3. zend_set_timeout 関数はタイマーを設定します。指定した時間が経過すると、タイマーが PHP プロセスに通知します。 zend_set_timeout については、以下で詳しく分析します。

php_request_startupが完了すると、phpの実際の実行フェーズ、つまりphp_execute_scriptに入ります。 php_execute_script で確認できます:

リーリー

OK、ここでコードが実行され、max_input_time タイムアウトが発生していない場合、max_execution_time のタイムアウトが再指定されます。

zend_set_timeout を呼び出して max_execution_time を渡すことによっても同じことが行われます。 Windows では元のタイマーをオフにするには、zend_unset_timeout を明示的に呼び出す必要があるが、Linux ではそうではないことに特に注意してください。これは、2 つのプラットフォームでのタイマーの実装原理が異なるためです。これについては、以下で詳しく説明します。

最後に、図はタイムアウト制御プロセスを表すために使用されます。左側のケースは、ユーザーが max_input_time と max_execution_time の両方を設定したことを示しています。右側の違いは、ユーザーが max_execution_time のみを設定したことです:

zend_set_timeout

前述したように、タイマーの設定には zend_set_timeout 関数が使用されます。実装を詳しく見てみましょう:

リーリー

上記の実装は基本的に 2 つのプラットフォームに完全に分割できます:

まず Linux について見てみましょう:

Linux でのタイマーははるかに簡単で、setitimer 関数を呼び出すだけです。さらに、zend_set_timeout は SIGPROF シグナルのハンドラーを zend_timeout に設定します。

setitimer を呼び出すときは、it_interval を 0 に設定して、このタイマーが 2 回おきではなく 1 回だけトリガーされることを示すことに注意してください。 setitimer は 3 つの方法で時間を測定できます。PHP では、ユーザー コードとカーネル コードの実行時間を同時に計算する ITIMER_PROF を使用します。時間が経過すると、SIGPROF 信号が生成されます。

PHP プロセスが SIGPROF シグナルを受信すると、現在実行中の内容に関係なく、zend_timeout にジャンプします。 zend_timeout は実際にタイムアウトを処理する関数です。

もう一度 Windows を見てください:

首先会启动一个子线程,该线程主要用于设置定时器,同时维护EG(timed_out)变量。

子线程一旦生成,主线程便会向子线程发送一条消息:WM_REGISTER_ZEND_TIMEOUT。子线程接收到WM_REGISTER_ZEND_TIMEOUT之后,产生一个定时器并开始计时。同时,子线程会设置EG(timed_out) = 0。这很重要!windows平台下正是通过判断EG(timed_out)是否为1,来决定是否超时。

如果定时器到时间了,子线程收到WM_TIMER消息,则取消定时器,并且设置EG(timed_out) = 1。

如果需要关闭定时器,则子线程会收到WM_UNREGISTER_ZEND_TIMEOUT消息。关闭定时器,并不会改变EG(timed_out)。

相关代码还是很清晰的:

static LRESULT CALLBACK zend_timeout_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message) {
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
     
    // 生成一个定时器,开始计时
    case WM_REGISTER_ZEND_TIMEOUT:
      /* wParam is the thread id pointer, lParam is the timeout amount in seconds */
      if (lParam == 0) {
        KillTimer(timeout_window, wParam);
      } else {
        SetTimer(timeout_window, wParam, lParam*1000, NULL);
        EG(timed_out) = 0;
      }
      break;
     
    // 关闭定时器
    case WM_UNREGISTER_ZEND_TIMEOUT:
      /* wParam is the thread id pointer */
      KillTimer(timeout_window, wParam);
      break;
     
    // 超时了,也需关闭定时器
    case WM_TIMER: {
        KillTimer(timeout_window, wParam);
        EG(timed_out) = 1;
      }
      break;
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}

根据上文描述,最终都是需要跳转到zend_timeout来处理超时的。那windows下如何进入zend_timeout呢?

window下仅在execute函数中(zend_vm_execute.h刚开始的地方),可以看到调用zend_timeout:

while (1) {
  int ret;
#ifdef ZEND_WIN32
  if (EG(timed_out)) {  // windows下的超时,执行每条opcode之前都判断是否需要调用zend_timeout
    zend_timeout(0);
  }
#endif
 
  if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
  ...
  }
}

上述代码可以看到:

在windows下,每执行完成一条opcode指令,就会进行一次超时判断。

因为主线程执行opcode的同时,子线程可能已经发生超时,而windows并没有什么机制可以让主线程停止手头的工作,直接跳入zend_timeout。所以只好利用子线程先将EG(timed_out)设置为1,然后主线程在等到当前opcode执行完成、进入下一条opcode之前,判断一下EG(timed_out)再调用zend_timeout。

因此准确的讲,windows的超时,其实是有一点点延时的。至少在某一个opcode执行的过程中,无法被打断。当然,正常情况下,单条opcode的执行时间会很短。但是可以很容易人为构造出一些很耗时的函数,使得function call需要等待较长时间。此时,如果子线程判断出超时了,则还需要经过漫长的等待,直到主线程完成该条opcode之后,才能调用zend_timeout。

zend_unset_timeout

void zend_unset_timeout(TSRMLS_D) /* {{{ */
{
#ifdef ZEND_WIN32
   
  // 通过发送WM_UNREGISTER_ZEND_TIMEOUT消息来关闭定时器
  if(timeout_thread_initialized) {
    PostThreadMessage(timeout_thread_id, WM_UNREGISTER_ZEND_TIMEOUT, (WPARAM) GetCurrentThreadId(), (LPARAM) 0);
  }
#else
  if (EG(timeout_seconds)) {
    struct itimerval no_timeout;
    no_timeout.it_value.tv_sec = no_timeout.it_value.tv_usec = no_timeout.it_interval.tv_sec = no_timeout.it_interval.tv_usec = 0;
     
    // 全置0,相当于关闭定时器
    setitimer(ITIMER_PROF, &no_timeout, NULL);
  }
#endif
}

zend_unset_timeout同样分成两种平台的实现。

先看linux:

linux下的关闭定时器也很简单。只要将struct itimerval中的4个值都设置为0,就行了。

再看windows:

由于windows是利用一个独立的线程来计时。因此,zend_unset_timeout会向该线程发送WM_UNREGISTER_ZEND_TIMEOUT消息。WM_UNREGISTER_ZEND_TIMEOUT对应的动作是去调用KillTimer来关闭定时器。注意,线程本身并不退出。

前文留下了一个问题,在php_execute_script中,windows下面要显示调用zend_unset_timeout来关闭定时器,而linux下不需要。因为对于一个linux进程来说,只能存在一个setitimer定时器。也就是说,重复调用setitimer,后面的定时器会直接覆盖前面的。

zend_timeout

ZEND_API void zend_timeout(int dummy) /* {{{ */
{
  TSRMLS_FETCH();
 
  if (zend_on_timeout) {
    zend_on_timeout(EG(timeout_seconds) TSRMLS_CC);
  }
 
  zend_error(E_ERROR, "Maximum execution time of %d second%s exceeded", EG(timeout_seconds), EG(timeout_seconds) == 1 ? "" : "s");
}

如前文所述,zend_timeout是实际处理超时的函数。它的实现也很简单。

如果有配置exit_on_timeout,则zend_on_timeout会尝试调用sapi_terminate_process关闭sapi进程。如果无需exit_on_timeout,则直接进入zend_error进行出错处理。大部分情况下,我们并不会设置exit_on_timeout,毕竟我们期望的是虽然一个请求超时了,但是进程仍然保留下来,服务下一个请求。

zend_error除了会打印错误日志,还会利用longjump跳转到boilout指定的栈帧,一般是zend_end_try或者zend_catch宏所在的地方。关于longjump,可以另起一个话题,本文就不具体叙述了。在php_execute_script里面,zend_error会使得程序跳转到zend_end_try的位置然后继续执行。继续执行是指,会调用php_request_shutdown等函数来完成收尾工作。

直到这里,php脚本的超时机制算是讲清楚了。

最后来看一个疑似php内核的bug。

windows下max_input_time的bug

回忆一下,之前有提到windows下只有一个地方调用了zend_timeout,就是execute函数里,准确讲是每条opcode执行之前。

那么,假如发生max_input_time类型的超时,即使子线程将EG(timed_out)被置为1,也得延迟到execute中才能进行超时处理。貌似一切正常。

而问题的关键之处便在于,我们并不能保证主线程执行到execute时,EG(timed_out)任然为1。一旦进入execute之前,EG(timed_out)被子线程修改成0,那么max_input_time类型的超时就永远不会被handle了。

为何EG(timed_out)会被子线程又修改为0呢?原因在于:php_execute_script中,调用了zend_set_timeout(INI_INT("max_execution_time"), 0)来设置定时器。

zend_set_timeout は、WM_REGISTER_ZEND_TIMEOUT メッセージを子スレッドに送信します。子スレッドがこのメッセージを受信すると、タイマーの作成に加えて、EG(timed_out) = 0 も設定されます (詳細については、上でインターセプトした zend_timeout_WndProc コード スニペットを参照してください)。スレッドの実行は不確実であるため、メインスレッドの実行時に子スレッドがメッセージを受信したかどうかを判断して EG (timed_out) を 0 に設定することはできません。

写真に示すように、

赤線の時点でexecuteの判定が発生した場合、EG(timed_out)が1となり、executeはzend_timeoutを呼び出してタイムアウト処理を行います。

青線の時点で実行判定が発生した場合、EG(timed_out)は0にリセットされており、max_input_timeタイムアウトは完全にカバーされています。

興味があるかもしれない記事:

  • PHPにおけるいくつかの一般的なタイムアウト処理の包括的なまとめ
  • PHP file_get_contentsのタイムアウト処理方法の設定
  • 厳密なPHPセッションのセッションタイムアウト設定方法
  • phpcurlのタイムアウト設定例
  • 設定方法PHPページ関数のタイムアウト制限
  • PHPページ関数のタイムアウト時間を設定する方法

www.bkjia.comtru​​ehttp://www.bkjia.com/PHPjc/1100914.html技術記事 PHP スクリプト実行時のタイムアウトの仕組みの詳細説明 PHP スクリプトの開発を行う場合、スクリプトのタイムアウトを制御するために max_input_time と max_execution_time を設定することがよくあります。でも…
声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
PHPセッションを失敗させる可能性のあるいくつかの一般的な問題は何ですか?PHPセッションを失敗させる可能性のあるいくつかの一般的な問題は何ですか?Apr 25, 2025 am 12:16 AM

PHPSESSIONの障害の理由には、構成エラー、Cookieの問題、セッションの有効期限が含まれます。 1。構成エラー:正しいセッションをチェックして設定します。save_path。 2.Cookieの問題:Cookieが正しく設定されていることを確認してください。 3.セッションの有効期限:セッションを調整してください。GC_MAXLIFETIME値はセッション時間を延長します。

PHPでセッション関連の問題をどのようにデバッグしますか?PHPでセッション関連の問題をどのようにデバッグしますか?Apr 25, 2025 am 12:12 AM

PHPでセッションの問題をデバッグする方法は次のとおりです。1。セッションが正しく開始されるかどうかを確認します。 2.セッションIDの配信を確認します。 3.セッションデータのストレージと読み取りを確認します。 4.サーバーの構成を確認します。セッションIDとデータを出力し、セッションファイルのコンテンツを表示するなど、セッション関連の問題を効果的に診断して解決できます。

session_start()が複数回呼び出されるとどうなりますか?session_start()が複数回呼び出されるとどうなりますか?Apr 25, 2025 am 12:06 AM

session_start()への複数の呼び出しにより、警告メッセージと可能なデータ上書きが行われます。 1)PHPは警告を発し、セッションが開始されたことを促します。 2)セッションデータの予期しない上書きを引き起こす可能性があります。 3)session_status()を使用してセッションステータスを確認して、繰り返しの呼び出しを避けます。

PHPでセッションのライフタイムをどのように構成しますか?PHPでセッションのライフタイムをどのように構成しますか?Apr 25, 2025 am 12:05 AM

PHPでのセッションライフサイクルの構成は、session.gc_maxlifetimeとsession.cookie_lifetimeを設定することで達成できます。 1)session.gc_maxlifetimeサーバー側のセッションデータのサバイバル時間を制御します。 0に設定すると、ブラウザが閉じているとCookieが期限切れになります。

セッションを保存するためにデータベースを使用することの利点は何ですか?セッションを保存するためにデータベースを使用することの利点は何ですか?Apr 24, 2025 am 12:16 AM

データベースストレージセッションを使用することの主な利点には、持続性、スケーラビリティ、セキュリティが含まれます。 1。永続性:サーバーが再起動しても、セッションデータは変更されないままになります。 2。スケーラビリティ:分散システムに適用され、セッションデータが複数のサーバー間で同期されるようにします。 3。セキュリティ:データベースは、機密情報を保護するための暗号化されたストレージを提供します。

PHPでカスタムセッション処理をどのように実装しますか?PHPでカスタムセッション処理をどのように実装しますか?Apr 24, 2025 am 12:16 AM

PHPでのカスタムセッション処理の実装は、SessionHandlerInterfaceインターフェイスを実装することで実行できます。具体的な手順には、次のものが含まれます。1)CussentsessionHandlerなどのSessionHandlerInterfaceを実装するクラスの作成。 2)セッションデータのライフサイクルとストレージ方法を定義するためのインターフェイス(オープン、クローズ、読み取り、書き込み、破壊、GCなど)の書き換え方法。 3)PHPスクリプトでカスタムセッションプロセッサを登録し、セッションを開始します。これにより、データをMySQLやRedisなどのメディアに保存して、パフォーマンス、セキュリティ、スケーラビリティを改善できます。

セッションIDとは何ですか?セッションIDとは何ですか?Apr 24, 2025 am 12:13 AM

SessionIDは、ユーザーセッションのステータスを追跡するためにWebアプリケーションで使用されるメカニズムです。 1.ユーザーとサーバー間の複数のインタラクション中にユーザーのID情報を維持するために使用されるランダムに生成された文字列です。 2。サーバーは、ユーザーの複数のリクエストでこれらの要求を識別および関連付けるのに役立つCookieまたはURLパラメーターを介してクライアントに生成および送信します。 3.生成は通常、ランダムアルゴリズムを使用して、一意性と予測不可能性を確保します。 4.実際の開発では、Redisなどのメモリ内データベースを使用してセッションデータを保存してパフォーマンスとセキュリティを改善できます。

ステートレス環境(APIなど)でセッションをどのように処理しますか?ステートレス環境(APIなど)でセッションをどのように処理しますか?Apr 24, 2025 am 12:12 AM

APIなどのステートレス環境でのセッションの管理は、JWTまたはCookieを使用して達成できます。 1。JWTは、無国籍とスケーラビリティに適していますが、ビッグデータに関してはサイズが大きいです。 2.cookiesはより伝統的で実装が簡単ですが、セキュリティを確保するために慎重に構成する必要があります。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

mPDF

mPDF

mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

VSCode Windows 64 ビットのダウンロード

VSCode Windows 64 ビットのダウンロード

Microsoft によって発売された無料で強力な IDE エディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強力な PHP 統合開発環境