Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 16324] ファイルの概要

このコミットは、Go言語のosパッケージにおけるWindowsコンソールからの読み取り処理を改善するものです。具体的には、syscall.ReadConsole関数が大きなバッファで呼び出された際に発生する可能性のある問題を回避するため、読み取りバッファのサイズを制限する変更が加えられています。これにより、Windows環境でのコンソールI/Oの安定性が向上します。

コミット

  • コミットハッシュ: a65f861bfa7a393e61cbe7aad0d1ecd42a0237be
  • 作者: Alex Brainman alex.brainman@gmail.com
  • コミット日時: 2013年5月16日 木曜日 17:20:13 +1000

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a65f861bfa7a393e61cbe7aad0d1ecd42a0237be

元コミット内容

os: use small buffer when reading from windows console

Fixes #5481.

R=golang-dev, dominik.honnef, bradfitz
CC=golang-dev
https://golang.org/cl/9437044

変更の背景

この変更は、Go言語のosパッケージがWindowsコンソールからデータを読み取る際に、特定の条件下で問題が発生するという報告(Issue #5481)に対応するために行われました。コミットメッセージによると、syscall.ReadConsole関数が非常に大きなバッファを渡された場合に、予期せぬ失敗を引き起こす可能性があったようです。

WindowsのコンソールAPIであるReadConsoleは、コンソール入力バッファから文字データを読み取るために使用されます。しかし、APIの内部的な実装や、特定のシステムリソースの制約により、一度に処理できるデータ量に暗黙的な上限が存在する場合があります。Goのosパッケージは、ユーザーが指定した任意のサイズのバッファでRead操作を試みるため、このAPIの潜在的な制限に抵触し、エラーや予期せぬ動作を引き起こす可能性がありました。

この問題に対処するため、開発者はReadConsoleに渡すバッファのサイズを意図的に小さく制限することで、APIの安定した動作を保証しようとしました。

前提知識の解説

  • Go言語のosパッケージ: Go言語の標準ライブラリの一部で、オペレーティングシステムとのインタラクション(ファイルI/O、プロセス管理、環境変数など)を提供します。os.File型はファイルやデバイス(コンソールを含む)の抽象化を提供し、Readメソッドを通じてデータの読み取りを行います。
  • Windows Console API (ReadConsole): Windowsオペレーティングシステムが提供するAPI群の一つで、コンソールアプリケーションがテキストベースの入出力を行うための機能を提供します。ReadConsole関数は、コンソール入力バッファからキーボード入力などの文字データを読み取るために使用されます。このAPIは、通常、UTF-16エンコーディング(uint16で表現されるワイド文字)でデータを扱います。
  • バッファリング: コンピュータシステムにおいて、データ転送の効率を高めるために一時的にデータを保持する領域(バッファ)を使用する技術です。I/O操作では、一度に少量のデータを頻繁にやり取りするよりも、ある程度の量をまとめてバッファに格納してから転送する方が効率的です。しかし、バッファが大きすぎると、システムリソースの消費や、APIの内部的な制約に抵触する可能性があります。
  • Go言語のsyscallパッケージ: オペレーティングシステムが提供する低レベルなシステムコールに直接アクセスするためのパッケージです。osパッケージのような高レベルな抽象化の背後で、実際のOSとのやり取りを行うために利用されます。syscall.ReadConsoleは、WindowsのReadConsole APIをGoから呼び出すためのラッパーです。
  • Issueトラッカー: ソフトウェア開発プロジェクトでバグ報告、機能要望、タスクなどを管理するためのシステムです。Go言語プロジェクトではGitHub Issuesが使用されており、Fixes #5481は、このコミットがIssue 5481を解決することを示します。

技術的詳細

このコミットの核心は、Windowsコンソールからの読み取り処理において、syscall.ReadConsoleに渡すバッファサイズを制限することです。

Goのos.FilereadConsoleメソッドは、ユーザーが指定したバイトスライスbにデータを読み込もうとします。しかし、syscall.ReadConsoleuint16のワイド文字配列を引数に取るため、内部的にはuint16のスライスwcharsを作成し、そこに読み込んだ後、b(バイトスライス)に変換してコピーします。

変更前のコードでは、wcharsスライスのサイズはlen(b)(バイトスライスの長さ)に基づいていました。これは、ReadConsoleがバイトではなく文字(uint16)を扱うため、len(b) / 2文字に相当します。もしbが非常に大きい場合、wcharsも非常に大きくなり、これがsyscall.ReadConsoleの内部的な問題を引き起こしていたと考えられます。

このコミットでは、readNという新しい変数を導入し、syscall.ReadConsoleに渡すwcharsスライスの最大サイズを16000文字に制限しています。

// syscall.ReadConsole seems to fail, if given large buffer.
// So limit the buffer to 16000 characters.
readN := 16000
if len(b) < readN { // len(b) はバイト数なので、uint16の文字数に換算すると len(b)/2
    readN = len(b) // ここは厳密には len(b)/2 とすべきだが、uint16の配列サイズとして利用されるため、結果的に問題ない
}
wchars := make([]uint16, readN)

ここでlen(b)readNの比較が行われていますが、len(b)はバイト数であり、readNuint16の文字数(2バイト/文字)を意図しているため、厳密にはlen(b)/2と比較すべきです。しかし、wchars := make([]uint16, readN)の行でreadNuint16の要素数として使われるため、結果的にlen(b)16000より小さい場合は、len(b)バイト分のバッファ(len(b)/2文字)が確保され、16000より大きい場合は16000文字分のバッファが確保されることになります。この実装は、len(b)が奇数の場合にlen(b)/2が切り捨てられることを考慮すると、少し曖昧ですが、一般的なコンソール入力では問題になりにくいと考えられます。

この16000という値は、おそらく経験的に、またはWindows APIのドキュメントや既知の制限から導き出されたものと推測されます。この制限により、ReadConsoleが処理できる範囲内でバッファが確保され、安定した動作が期待されます。

コアとなるコードの変更箇所

src/pkg/os/file_windows.go ファイルの (f *File) readConsole(b []byte) (n int, err error) メソッドが変更されました。

--- a/src/pkg/os/file_windows.go
+++ b/src/pkg/os/file_windows.go
@@ -251,8 +251,14 @@ func (f *File) readConsole(b []byte) (n int, err error) {
 		return 0, nil
 	}
 	if len(f.readbuf) == 0 {
+		// syscall.ReadConsole seems to fail, if given large buffer.
+		// So limit the buffer to 16000 characters.
+		readN := 16000
+		if len(b) < readN {
+			readN = len(b)
+		}
 		// get more input data from os
-		wchars := make([]uint16, len(b))
+		wchars := make([]uint16, readN)
 		var p *uint16
 		if len(b) > 0 {
 			p = &wchars[0]

コアとなるコードの解説

変更の核心は、wcharsスライスの作成方法にあります。

変更前: wchars := make([]uint16, len(b)) これは、入力バイトスライスbの長さと同じ要素数を持つuint16のスライスを作成していました。もしbが非常に大きい(例えば数MB)場合、wcharsも同様に非常に大きくなり、これがsyscall.ReadConsoleの不安定性の原因となっていました。

変更後:

readN := 16000
if len(b) < readN {
    readN = len(b)
}
wchars := make([]uint16, readN)

このコードは、まずreadNという変数を16000に初期化します。これは、syscall.ReadConsoleに渡すuint16の配列の最大サイズを16000文字に制限するという意図です。 次に、if len(b) < readNという条件で、ユーザーが要求した読み取りバッファbの長さが16000バイト(または8000文字)よりも小さいかどうかをチェックします。もし小さければ、readNlen(b)に設定します。これにより、ユーザーが小さなバッファを渡した場合でも、必要以上に大きなwcharsスライスが作成されるのを防ぎます。 最終的に、wchars := make([]uint16, readN)によって、readNで決定されたサイズのuint16スライスが作成されます。これにより、wcharsのサイズは最大で16000文字(32KB)に制限され、syscall.ReadConsoleがより安定して動作するようになります。

この変更は、ReadConsoleが一度に処理できる文字数に上限があるという仮定に基づいています。16000という具体的な数値は、Windows APIの内部的な制限や、過去の経験から導き出された「安全な」上限値である可能性が高いです。

関連リンク

  • Go CL 9437044: https://golang.org/cl/9437044
  • Go Issue 5481: (直接的なリンクは見つかりませんでしたが、コミットメッセージに記載されています)

参考にした情報源リンク

  • Go言語のosパッケージに関するドキュメント
  • Windows Console API (ReadConsole) に関するMicrosoftのドキュメント
  • Go言語のsyscallパッケージに関するドキュメント
  • Go言語のIssueトラッカー(GitHub Issues)
  • Web検索結果: golang #5481 syscall.ReadConsole large buffer (直接的なIssueは見つかりませんでしたが、Goにおける大規模バッファ処理の一般的なアプローチに関する情報が得られました。)
    • kgrz.io (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGYaw2dLSca_ekgYgn2uGBkBt09ZbeQ_EWvqbW28p_vjBp5IQoherJCx2pvyZ0tYooJEvfOl8Ad0jG1f6afpteYbjEeYs9mhZezql_Yb4b600qcBSshw6iXlIpJugRtrwzFIiTKLyAuGBXZ1CE=)
    • reddit.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHjUwnR2hIU_EqhkKzDarSAZGvSfAEdvBkAN9SlW8caSDqTu8V5sVeTx_zPNi-H6HrLCvMYqBDKTCVeoUZWB1_aRhJWKISlKTt0_XZqpI4IrBPqELe89BoqsuReoWebnUli9Wfh7Mo8RChgk6nk20zIFLhI6gmLoejtYKXwL-UTNCYS6TLsrXn-RTJkWzj4SgnrLeqN)
    • stackoverflow.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHcVW0xuzZ1UvUzt-pvrt7pf_CQPq9upK6hea8I-QtvlZQMa2-JI5X0278dktt6t1RrMyHi--LkQoyI93ofGJ3YyntcIdMnHlO5zMaQtlARtOyFz5rosdlnBEMgwvKXbw=)
    • github.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGIZaS2ZMg_p-WI1fGrv7eeFjHEU0L4hzm8t4n1QC428P3N7mOpHXYPnezjleilJGNMoKsaJaKktkkZgjC4XN3IB3vqJq17JLOe9x8uQtlARtOyFz5rosdlnBEMgwvKXbw=)
    • reddit.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF-Z0EpTf3TarzowQQnVSX1g41UVb0XkVlafHIXoafcpCTL_m1ou6yFWJFrAvFqvhK8rwExk5L5KjaYJRHthwkDenAldmp-UVLAmhCL8jMGY0XZbzHTZqDDcUqUUFhvvzl-6jcbpVnjmlD36i5pbdszt7in8u-9rn3HIGKdXgLszuzKkQZpRYDPw656czRkf8tXpRHpOJg=)
    • stackoverflow.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFUefZnBRdNnfLOyJlz4ZH6c8TNezEszDRD_XJgsjy8346YDS70xMGaji24c99V3lZtEF54XJgXPjtEjtAYboNpon9bWTi-vbRoTI99M1NS8Z-suKfP7Hn1cePE1dmGxZBwyCE4DSbWJvufkwUjVlkgS4g1d9cgfHZFiyuu--LeFlh3QREaiGI1m3LPLt736Khz_cp04Rr3Fy8=)