目次
探讨php中error_log函数输出内容的原子性问题
ホームページ バックエンド開発 PHPチュートリアル PHP_PHP チュートリアルの error_log 関数の出力内容のアトミック性の問題について説明します。

PHP_PHP チュートリアルの error_log 関数の出力内容のアトミック性の問題について説明します。

Jul 13, 2016 am 09:44 AM
コンテンツ 関数

探讨php中error_log函数输出内容的原子性问题

前几天跟同事讨论如何在一次login php调用中保证error_log输出的日志都有唯一的标识头,结论是玩家帐号+当前时间+随机数,在我们当前用户量级条件下应该是满足需求的,当然,这并不是本文讨论的重点。

在确定这个简单的方案之后,我在想两个问题,也是今天本文讨论的重点:

1.error_log调用是否可以保证输出内容是完整的?

2.如果是完整的,那么是怎样保证的?

求证开始了,起初以为error_log调用中会对目标文件加文件锁,直接看源码(php5.6.12):

from: basic_functions.c

 

PHPAPI int _php_error_log_ex(int opt_err, char *message, int message_len, char *opt, char *headers TSRMLS_DC) /* {{{ */
{
	php_stream *stream = NULL;

	switch (opt_err)
	{
		case 1:		/*send an email */
			if (!php_mail(opt, PHP error_log message, message, headers, NULL TSRMLS_CC)) {
				return FAILURE;
			}
			break;

		case 2:		/*send to an address */
			php_error_docref(NULL TSRMLS_CC, E_WARNING, TCP/IP option not available!);
			return FAILURE;
			break;

		case 3:		/*save to a file */
			stream = php_stream_open_wrapper(opt, a, IGNORE_URL_WIN | REPORT_ERRORS, NULL);
			if (!stream) {
				return FAILURE;
			}
			php_stream_write(stream, message, message_len);
			php_stream_close(stream);
			break;

		case 4: /* send to SAPI */
			if (sapi_module.log_message) {
				sapi_module.log_message(message TSRMLS_CC);
			} else {
				return FAILURE;
			}
			break;

		default:
			php_log_err(message TSRMLS_CC);
			break;
	}
	return SUCCESS;
}
/* }}} */
ログイン後にコピー

from: streams.c

/* Writes a buffer directly to a stream, using multiple of the chunk size */
static size_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
{
	size_t didwrite = 0, towrite, justwrote;

 	/* if we have a seekable stream we need to ensure that data is written at the
 	 * current stream->position. This means invalidating the read buffer and then
	 * performing a low-level seek */
	if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && stream->readpos != stream->writepos) {
		stream->readpos = stream->writepos = 0;

		stream->ops->seek(stream, stream->position, SEEK_SET, &stream->position TSRMLS_CC);
	}


	while (count > 0) {
		towrite = count;
		if (towrite > stream->chunk_size)
			towrite = stream->chunk_size;

		justwrote = stream->ops->write(stream, buf, towrite TSRMLS_CC);

		/* convert justwrote to an integer, since normally it is unsigned */
		if ((int)justwrote > 0) {
			buf += justwrote;
			count -= justwrote;
			didwrite += justwrote;

			/* Only screw with the buffer if we can seek, otherwise we lose data
			 * buffered from fifos and sockets */
			if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
				stream->position += justwrote;
			}
		} else {
			break;
		}
	}
	return didwrite;

}
ログイン後にコピー

通过看源码文件,error_log只是以O_APPEND模式打开文件,然后就是write buf,好吧,没有看到文件锁的影子。而问题也很明显,write可能会调用多次,那么也就基本认定msg并不保证被原子输出,多次的write使得这个问题更严重。

多次write不能保证原子操作,那么单次呢?

1.from:man write

 If the file was open(2)ed with <strong>O_APPEND</strong>, the file
       offset is first set to the end of the file before writing.  The
       adjustment of the file offset and the write operation are performed
       as an atomic step.
ログイン後にコピー

2.from:man write

Atomic/non-atomic: A write is atomic if the whole amount written in one operation is not interleaved with data from any other process. This is useful when there are multiple writers sending data to a single reader. Applications need to know how large a write request can be expected to be performed atomically. This maximum is called {PIPE_BUF}. This volume of IEEE Std 1003.1-2001 does not say whether write requests for more than {PIPE_BUF} bytes are atomic, but requires that writes of {PIPE_BUF} or fewer bytes shall be atomic.
ログイン後にコピー

man说的大概意思就是如果是O_APPEND模式,文件尾的定位与write调用是原子操作,不会存在write时文件尾还需要再调整,导致错位。当前这个原子性,只保证open和之后的第一次write,后续的write就不保证了。像error_log那样如果buf过长导致了多次write肯定就不保证buf一次完整输出。

继续深究,那么单一次write是原子的吗?man也给出了答案,不是的,只有要写出的数据小于等于PIPE_BUF时才保证原子操作。

问题得到了理论上的解答,下面开始实验验证:

test_error_log.php

<!--?php

$str = ;
for($i = 0; $i < (int)$argv[2]; $i++)
{
    $str = $str.$argv[1];
}

for($i = 0; $i < (int)$argv[3]; $i++)
{
    error_log($str.
, 3, ./append.txt);
}

?-->
ログイン後にコピー
check_line.py

filename = ./append.txt

for line in open(filename):
    print len(line)
ログイン後にコピー
test.sh

#!/bin/bash 

rm -f append.txt

for ((counter=0; counter < 10; ++counter))
do
    php test_error_log.php $counter $1 $2 &
done

sleep 2

#python check_line.py > a.txt

python check_line.py | sort | uniq -c
ログイン後にコピー

PHP_PHP チュートリアルの error_log 関数の出力内容のアトミック性の問題について説明します。

验证思路:在输出msg最后加换行符,如果输出的每行与初始buf大小相同,说明本次error_log完整输出,经验证,内核3.5.0-23上PIPE_BUF是8K,8K以内的error_log输出都可以保证完整,否则会存在错乱的风险

 

www.bkjia.comtruehttp://www.bkjia.com/PHPjc/1050842.htmlTechArticle探讨php中error_log函数输出内容的原子性问题 前几天跟同事讨论如何在一次login php调用中保证error_log输出的日志都有唯一的标识头,结论是玩...
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホット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 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

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

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

golang 関数で新しい関数を動的に作成するためのヒント golang 関数で新しい関数を動的に作成するためのヒント Apr 25, 2024 pm 02:39 PM

Go 言語は、クロージャとリフレクションという 2 つの動的関数作成テクノロジを提供します。クロージャを使用すると、クロージャ スコープ内の変数にアクセスでき、リフレクションでは FuncOf 関数を使用して新しい関数を作成できます。これらのテクノロジーは、HTTP ルーターのカスタマイズ、高度にカスタマイズ可能なシステムの実装、プラグイン可能なコンポーネントの構築に役立ちます。

C++ 関数の命名におけるパラメーターの順序に関する考慮事項 C++ 関数の命名におけるパラメーターの順序に関する考慮事項 Apr 24, 2024 pm 04:21 PM

C++ 関数の名前付けでは、読みやすさを向上させ、エラーを減らし、リファクタリングを容易にするために、パラメーターの順序を考慮することが重要です。一般的なパラメータの順序規則には、アクション-オブジェクト、オブジェクト-アクション、意味論的な意味、および標準ライブラリへの準拠が含まれます。最適な順序は、関数の目的、パラメーターの種類、潜在的な混乱、および言語規約によって異なります。

Excel関数の公式の完全なコレクション Excel関数の公式の完全なコレクション May 07, 2024 pm 12:04 PM

1. SUM 関数は、列またはセルのグループ内の数値を合計するために使用されます (例: =SUM(A1:J10))。 2. AVERAGE 関数は、列またはセルのグループ内の数値の平均を計算するために使用されます (例: =AVERAGE(A1:A10))。 3. COUNT 関数。列またはセルのグループ内の数値またはテキストの数をカウントするために使用されます。例: =COUNT(A1:A10)。 4. IF 関数。指定された条件に基づいて論理的な判断を行い、結果を返すために使用されます。対応する結果。

C++関数のデフォルトパラメータと可変パラメータの長所と短所の比較 C++関数のデフォルトパラメータと可変パラメータの長所と短所の比較 Apr 21, 2024 am 10:21 AM

C++ 関数のデフォルト パラメーターの利点には、呼び出しの簡素化、可読性の向上、エラーの回避などがあります。欠点は、柔軟性が限られていることと、名前の制限があることです。可変引数パラメーターの利点には、無制限の柔軟性と動的バインディングが含まれます。欠点としては、複雑さの増大、暗黙的な型変換、デバッグの難しさなどが挙げられます。

参照型を返す C++ 関数の利点は何ですか? 参照型を返す C++ 関数の利点は何ですか? Apr 20, 2024 pm 09:12 PM

C++ で参照型を返す関数の利点は次のとおりです。 パフォーマンスの向上: 参照による受け渡しによりオブジェクトのコピーが回避され、メモリと時間が節約されます。直接変更: 呼び出し元は、返された参照オブジェクトを再割り当てせずに直接変更できます。コードの簡素化: 参照渡しによりコードが簡素化され、追加の代入操作は必要ありません。

Java で効率的で保守しやすい関数を記述するにはどうすればよいでしょうか? Java で効率的で保守しやすい関数を記述するにはどうすればよいでしょうか? Apr 24, 2024 am 11:33 AM

効率的で保守しやすい Java 関数を作成するための鍵は、シンプルに保つことです。意味のある名前を付けてください。特殊な状況に対処します。適切な可視性を使用してください。

カスタム PHP 関数と定義済み関数の違いは何ですか? カスタム PHP 関数と定義済み関数の違いは何ですか? Apr 22, 2024 pm 02:21 PM

カスタム PHP 関数と定義済み関数の違いは次のとおりです。 スコープ: カスタム関数はその定義のスコープに限定されますが、事前定義関数はスクリプト全体からアクセスできます。定義方法: カスタム関数は function キーワードを使用して定義されますが、事前定義関数は PHP カーネルによって定義されます。パラメータの受け渡し: カスタム関数はパラメータを受け取りますが、事前定義された関数はパラメータを必要としない場合があります。拡張性: カスタム関数は必要に応じて作成できますが、事前定義された関数は組み込みで変更できません。

C++ 関数例外の詳細: カスタマイズされたエラー処理 C++ 関数例外の詳細: カスタマイズされたエラー処理 May 01, 2024 pm 06:39 PM

C++ の例外処理は、特定のエラー メッセージ、コンテキスト情報を提供し、エラーの種類に基づいてカスタム アクションを実行するカスタム例外クラスを通じて強化できます。 std::Exception から継承した例外クラスを定義して、特定のエラー情報を提供します。カスタム例外をスローするには、throw キーワードを使用します。 try-catch ブロックでdynamic_castを使用して、キャッチされた例外をカスタム例外タイプに変換します。実際の場合、open_file 関数は FileNotFoundException 例外をスローします。例外をキャッチして処理すると、より具体的なエラー メッセージが表示されます。

See all articles