[インデックス 15039] ファイルの概要
このコミットは、Go言語のos
パッケージにおけるPlan 9固有のファイル操作に関する変更です。具体的には、os.OpenFile
関数がsyscall.ForkLock
を保持しないように修正されています。これにより、Plan 9環境でのファイルオープン処理がブロックされた際に、同時に発生するfork-exec
操作がハングアップする問題を回避します。
コミット
commit fb451490ec646dba0d8fe9c6d0291c40e7631533
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date: Wed Jan 30 09:41:16 2013 -0800
os: don't hold ForkLock across opens on Plan 9
If os.OpenFile holds ForkLock on files that block opens,
then threads that simultaneously try to do fork-exec will
get hung up (until the open succeeds). Blocked opens are
common enough on Plan 9 that protecting against fd leaks
into fork-execs means not being able to do fork-execs
properly in the general case. Thus, we forgo taking the
lock.
R=rsc, ality
CC=golang-dev
https://golang.org/cl/7235066
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fb451490ec646dba0d8fe9c6d0291c40e7631533
元コミット内容
os: don't hold ForkLock across opens on Plan 9
このコミットは、Plan 9オペレーティングシステムにおいて、os.OpenFile
関数がファイルを開く際にForkLock
を保持しないように変更します。その理由は、もしos.OpenFile
がオープンをブロックするファイルに対してForkLock
を保持すると、同時にfork-exec
を実行しようとするスレッドが(オープンが成功するまで)ハングアップしてしまうためです。Plan 9ではブロックされるオープンが十分に頻繁に発生するため、ファイルディスクリプタ(fd)がfork-exec
にリークするのを防ぐことは、一般的なケースでfork-exec
を適切に実行できないことを意味します。したがって、このロックの取得を放棄します。
変更の背景
この変更の背景には、Plan 9オペレーティングシステムの特性と、Go言語のプロセス管理におけるfork-exec
の挙動が深く関わっています。
Go言語では、新しいプロセスを生成する際に、Unix系システムでは通常fork
とexec
の組み合わせが用いられます。fork
は現在のプロセスを複製し、exec
は複製されたプロセスで新しいプログラムを実行します。この際、親プロセスが持つファイルディスクリプタ(fd)が子プロセスに引き継がれることがあります。意図しないfdのリークは、セキュリティ上の問題やリソースリークを引き起こす可能性があるため、Goランタイムはこれを防ぐためのメカニズムを持っています。
ForkLock
は、このfork-exec
操作中にファイルディスクリプタの状態を保護するためのロックです。通常、fork
が実行される前に、開かれているファイルディスクリプタのリストをスナップショットし、子プロセスに引き継がれるべきでないfdをクローズするなどの処理が行われます。この処理中に、他のスレッドが新しいfdを開いたり閉じたりするのを防ぐためにForkLock
が使用されます。
しかし、Plan 9においては、ファイルを開く操作(open
システムコール)自体がブロックされる(つまり、すぐに完了せずに待機状態になる)ことが比較的頻繁に発生します。例えば、ネットワークファイルシステム上のファイルや、特定のデバイスファイルなどがこれに該当します。
元の実装では、os.OpenFile
がForkLock
を保持したままopen
システムコールを呼び出していました。このため、もしopen
がブロックされると、ForkLock
もブロックされたまま保持され続けます。その結果、他のスレッドがfork-exec
を実行しようとしても、ForkLock
が解放されるのを待つことになり、全体がハングアップしてしまうという問題が発生していました。
このコミットは、この特定のシナリオ、すなわちPlan 9におけるos.OpenFile
とfork-exec
の間のデッドロックまたはハングアップの問題を解決するために導入されました。開発者は、fdリークを防ぐことよりも、fork-exec
が適切に機能することの重要性を優先し、os.OpenFile
におけるForkLock
の取得を放棄するという判断を下しました。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
-
Plan 9 (プラン・ナイン):
- ベル研究所で開発された分散オペレーティングシステムです。Unixの後継として設計され、すべてのリソース(ファイル、デバイス、ネットワーク接続など)をファイルシステムとして表現するという強力な原則に基づいています。
- Unixとは異なる独自のシステムコールインターフェースを持ち、特にファイル操作やプロセス間通信においてその哲学が色濃く反映されています。
- Go言語は、その設計思想の一部をPlan 9から継承しており、特に並行処理やシステムプログラミングの側面で影響を受けています。
-
fork-exec
:- Unix系OSで新しいプロセスを生成する際の一般的なパターンです。
fork()
: 現在のプロセス(親プロセス)のほぼ完全なコピーである新しいプロセス(子プロセス)を作成します。子プロセスは親プロセスのメモリ空間、ファイルディスクリプタ、その他の状態を継承します。exec()
(例:execve()
): 現在のプロセスイメージを、指定された新しいプログラムで置き換えます。exec
が成功すると、元のプログラムは実行を停止し、新しいプログラムがそのプロセスのコンテキストで実行を開始します。- Go言語の
os/exec
パッケージは、内部的にこれらのシステムコールを利用して外部コマンドを実行します。
-
ファイルディスクリプタ (File Descriptor, fd):
- Unix系OSにおいて、開かれたファイルやソケット、パイプなどのI/Oリソースを参照するためにカーネルがプロセスに割り当てる非負の整数です。
- プロセスが
fork
されると、通常、開かれているすべてのfdが子プロセスに継承されます。
-
ForkLock
(Go言語の内部ロック):- Goランタイム内部で使用されるミューテックス(排他ロック)の一種です。
- 主に
fork-exec
操作の整合性を保つために導入されています。fork
が実行される直前と直後に、ファイルディスクリプタの状態が安定していることを保証するために使用されます。 - 具体的には、
fork
中に新しいfdが開かれたり閉じられたりするのを防ぎ、子プロセスに引き継がれるべきでないfdが確実にクローズされるようにします。これにより、子プロセスが意図しないリソースにアクセスしたり、リソースリークが発生したりするのを防ぎます。
-
ブロックするオープン (Blocked Opens):
- ファイルを開く操作(
open
システムコール)が、すぐに完了せずに、何らかの条件が満たされるまで実行を一時停止する状態を指します。 - 一般的な例としては、ネットワークファイルシステム上のファイルがネットワークの遅延によってすぐに開けない場合や、特定のデバイスファイルが準備できるまで待機する場合などがあります。
- Plan 9では、その「すべてがファイル」という哲学により、通常のファイルだけでなく、プロセス間通信やデバイスアクセスなどもファイルとして扱われるため、ブロックするオープンがより頻繁に発生する可能性があります。
- ファイルを開く操作(
これらの概念を理解することで、ForkLock
がなぜ存在し、なぜPlan 9の特定の状況でその保持が問題となるのか、そしてこのコミットがその問題をどのように解決しようとしているのかが明確になります。
技術的詳細
このコミットの技術的詳細は、GoランタイムがPlan 9上でどのようにプロセスを生成し、ファイルディスクリプタを管理しているか、そしてその中でForkLock
がどのような役割を果たしているかを理解することに集約されます。
Go言語のos
パッケージは、オペレーティングシステムとのインタフェースを提供します。os.OpenFile
は、指定された名前、フラグ、パーミッションでファイルを開くための関数です。内部的には、対応するOSのシステムコール(Plan 9の場合はsyscall.Create
やsyscall.Open
)を呼び出します。
元のコードでは、os.OpenFile
の内部でsyscall.ForkLock.RLock()
とsyscall.ForkLock.RUnlock()
が呼び出されていました。これは、OpenFile
がファイルディスクリプタを操作する際に、ForkLock
を読み取りロック(Rlock)として取得していたことを意味します。読み取りロックは、複数の読み取り操作が同時に行われることを許可しますが、書き込みロック(Wlock)や排他ロック(Lock)とは同時に取得できません。fork-exec
操作は、ForkLock
を書き込みロックとして取得し、ファイルディスクリプタの状態を排他的に操作します。
問題は、Plan 9のopen
システムコールがブロックされる可能性がある点にありました。os.OpenFile
がForkLock.RLock()
を取得した後、syscall.Create
またはsyscall.Open
を呼び出し、このシステムコールがブロックされると、ForkLock
はブロックされたまま保持され続けます。
// 変更前の擬似コード
func OpenFile(...) {
syscall.ForkLock.RLock() // ここでロックを取得
// syscall.Create または syscall.Open を呼び出す
// もしここでシステムコールがブロックされると、ForkLockは解放されない
syscall.ForkLock.RUnlock() // ここでロックを解放
}
一方で、Goプログラム内で別のゴルーチンがos/exec
パッケージなどを介してfork-exec
操作を実行しようとすると、その操作はForkLock
を排他的に取得しようとします。しかし、os.OpenFile
が既に読み取りロックを保持しているため、fork-exec
はForkLock
が解放されるまで待機することになります。結果として、os.OpenFile
のブロックされたopen
操作が完了するまで、fork-exec
操作もブロックされ、アプリケーション全体がハングアップするという状況が発生していました。
このコミットでは、src/pkg/os/file_plan9.go
ファイルからsyscall.ForkLock.RLock()
とsyscall.ForkLock.RUnlock()
の呼び出しを単純に削除しています。
// 変更後の擬似コード
func OpenFile(...) {
// syscall.ForkLock.RLock() は削除された
// syscall.Create または syscall.Open を呼び出す
// システムコールがブロックされても、ForkLockは保持されない
// syscall.ForkLock.RUnlock() は削除された
}
この変更により、os.OpenFile
がファイルを開く際にForkLock
を保持しなくなるため、open
システムコールがブロックされても、ForkLock
は他のfork-exec
操作のために解放されたままになります。これにより、fork-exec
操作がos.OpenFile
のブロックに引きずられてハングアップする問題が解消されます。
この解決策は、ファイルディスクリプタのリーク防止というForkLock
の本来の目的の一部を犠牲にしている可能性があります。コミットメッセージにも「protecting against fd leaks into fork-execs means not being able to do fork-execs properly in the general case. Thus, we forgo taking the lock.」(fork-exec
へのfdリークを防ぐことは、一般的なケースでfork-exec
を適切に実行できないことを意味する。したがって、ロックの取得を放棄する。)と明記されています。これは、Plan 9の環境では、fork-exec
の機能性を確保することの方が、厳密なfdリーク防止よりも優先されるという判断が下されたことを示しています。
コアとなるコードの変更箇所
変更はsrc/pkg/os/file_plan9.go
ファイルに限定されています。
--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -104,7 +104,6 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
append = true
}
- syscall.ForkLock.RLock()
if (create && trunc) || excl {
fd, e = syscall.Create(name, flag, syscallMode(perm))
} else {
@@ -117,7 +116,6 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
}
}
}\n- syscall.ForkLock.RUnlock()\n
if e != nil {
return nil, &PathError{"open", name, e}
具体的には、OpenFile
関数の内部で、以下の2行が削除されています。
syscall.ForkLock.RLock()
syscall.ForkLock.RUnlock()
コアとなるコードの解説
削除された2行は、os.OpenFile
関数がファイルを開くシステムコール(syscall.Create
またはsyscall.Open
)を呼び出す前後に、syscall.ForkLock
というグローバルな読み取りロックを取得・解放する役割を担っていました。
syscall.ForkLock.RLock()
: この行は、ファイルオープン操作を開始する前に、ForkLock
の読み取りロックを取得していました。これにより、他のfork-exec
操作が同時に発生した場合に、ファイルディスクリプタの状態が不整合になるのを防ぐ意図がありました。syscall.ForkLock.RUnlock()
: この行は、ファイルオープン操作が完了した後(またはエラーが発生した後)に、取得した読み取りロックを解放していました。
これらの行が削除されたことにより、os.OpenFile
はファイルを開く際にForkLock
を一切使用しなくなりました。結果として、open
システムコールがブロックされたとしても、ForkLock
は他のfork-exec
操作のために自由に利用可能となり、fork-exec
がハングアップする問題が解消されます。
この変更は、Plan 9の特定の挙動(open
がブロックされる可能性が高いこと)に対応するためのトレードオフであり、ForkLock
の目的である「fork-exec
中のファイルディスクリプタの整合性保証」の一部を緩和しています。しかし、コミットメッセージが示唆するように、Plan 9においては、この緩和がfork-exec
の機能性を確保するために必要不可欠であると判断されました。
関連リンク
- Go言語の
os
パッケージドキュメント: https://pkg.go.dev/os - Go言語の
syscall
パッケージドキュメント: https://pkg.go.dev/syscall - Go言語の
os/exec
パッケージドキュメント: https://pkg.go.dev/os/exec - Plan 9 from Bell Labs (Wikipedia): https://ja.wikipedia.org/wiki/Plan_9_from_Bell_Labs
- Go言語の
ForkLock
に関する議論(GoのIssueトラッカーやメーリングリストで検索すると、より詳細な背景が見つかる可能性があります)
参考にした情報源リンク
- Go言語のソースコード(特に
src/pkg/os/file_plan9.go
の変更履歴) - Go言語の公式ドキュメント
- コミットメッセージに記載されているGo CL (Code Review) リンク: https://golang.org/cl/7235066 (このリンクは古い形式のため、現在のGo GerritのURLにリダイレクトされるか、アーカイブされている可能性があります。正確なCLはGerritで検索する必要があります。)
- Plan 9オペレーティングシステムに関する一般的な情報源(書籍、オンラインドキュメントなど)
- Unix/Linuxの
fork()
とexec()
に関するドキュメントや解説 - ミューテックスや排他制御に関する一般的なコンピュータサイエンスの知識
[インデックス 15039] ファイルの概要
このコミットは、Go言語のos
パッケージにおけるPlan 9固有のファイル操作に関する重要な修正です。具体的には、os.OpenFile
関数がファイルを開く際にsyscall.ForkLock
を保持しないように変更されています。この変更の目的は、Plan 9環境でファイルオープン操作がブロックされた際に、同時に発生するfork-exec
操作がハングアップする問題を回避することにあります。
コミット
commit fb451490ec646dba0d8fe9c6d0291c40e7631533
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date: Wed Jan 30 09:41:16 2013 -0800
os: don't hold ForkLock across opens on Plan 9
If os.OpenFile holds ForkLock on files that block opens,
then threads that simultaneously try to do fork-exec will
get hung up (until the open succeeds). Blocked opens are
common enough on Plan 9 that protecting against fd leaks
into fork-execs means not being able to do fork-execs
properly in the general case. Thus, we forgo taking the
lock.
R=rsc, ality
CC=golang-dev
https://golang.org/cl/7235066
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fb451490ec646dba0d8fe9c6d0291c40e7631533
元コミット内容
os: don't hold ForkLock across opens on Plan 9
このコミットは、Plan 9オペレーティングシステムにおいて、os.OpenFile
関数がファイルを開く際にForkLock
を保持しないように変更します。その理由は、もしos.OpenFile
がオープンをブロックするファイルに対してForkLock
を保持すると、同時にfork-exec
を実行しようとするスレッドが(オープンが成功するまで)ハングアップしてしまうためです。Plan 9ではブロックされるオープンが十分に頻繁に発生するため、ファイルディスクリプタ(fd)がfork-exec
にリークするのを防ぐことは、一般的なケースでfork-exec
を適切に実行できないことを意味します。したがって、このロックの取得を放棄します。
変更の背景
この変更の背景には、Plan 9オペレーティングシステムの独特な特性と、Go言語のプロセス生成メカニズムにおけるForkLock
の役割が深く関わっています。
Go言語では、新しいプロセスを生成する際に、Unix系システムと同様にfork
とexec
の概念が内部的に利用されます。fork
は現在のプロセスを複製し、exec
は複製されたプロセスで新しいプログラムを実行します。このプロセスにおいて、親プロセスが持つファイルディスクリプタ(fd)が子プロセスに意図せず引き継がれる「fdリーク」は、セキュリティリスクやリソース枯渇の原因となる可能性があります。Goランタイムは、このようなfdリークを防ぐために、ForkLock
という内部的な排他制御メカニズムを使用しています。ForkLock
は、fork-exec
操作中にファイルディスクリプタの状態が安定していることを保証し、不要なfdが子プロセスに渡されないように管理します。
しかし、Plan 9オペレーティングシステムでは、ファイルを開く操作(open
システムコール)が、ネットワークファイルシステム上のファイルや特定のデバイスファイルなど、様々な理由でブロックされる(つまり、すぐに完了せずに待機状態になる)ことが比較的頻繁に発生します。
元のGo言語の実装では、os.OpenFile
関数がファイルを開く際に、このForkLock
を読み取りロックとして取得していました。問題は、もしopen
システムコールがブロックされた場合、ForkLock
もその間ずっと保持され続けてしまう点にありました。その結果、もし別のゴルーチンが同時にfork-exec
操作(これはForkLock
を排他的に取得しようとします)を実行しようとすると、os.OpenFile
がForkLock
を保持しているために、fork-exec
操作もブロックされ、アプリケーション全体がハングアップしてしまうというデッドロックに近い状況が発生していました。
このコミットは、このPlan 9特有のハングアップ問題を解決するために導入されました。開発チームは、Plan 9におけるfork-exec
の機能性を確保することの重要性が、厳密なfdリーク防止よりも優先されると判断し、os.OpenFile
におけるForkLock
の取得を放棄するという決断を下しました。これにより、os.OpenFile
がブロックされてもForkLock
は解放されたままとなり、fork-exec
操作が妨げられることがなくなります。
前提知識の解説
このコミットの意図と影響を完全に理解するためには、以下の技術的概念を把握しておく必要があります。
-
Plan 9 (プラン・ナイン):
- ベル研究所で開発された分散オペレーティングシステムで、Unixの設計思想をさらに推し進めたものです。
- 「すべてがファイルである」という強力な原則に基づいており、プロセス、ネットワーク接続、デバイスなど、システム内のあらゆるリソースがファイルシステムのエントリとして表現されます。これにより、一貫したインターフェースを通じて様々なリソースにアクセスできます。
- Go言語の設計者の一部はPlan 9の開発にも携わっており、Go言語の設計思想、特に並行処理やシステムプログラミングの側面において、Plan 9の影響が色濃く見られます。
- Plan 9のシステムコールはUnixとは異なる独自のインターフェースを持ち、ファイル操作のセマンティクスも一部異なります。特に、ファイルオープン操作がブロックされる可能性がUnix系OSよりも高いという特性があります。
-
fork-exec
:- Unix系オペレーティングシステムで新しいプロセスを生成する際の標準的なパターンです。
fork()
: 呼び出し元のプロセス(親プロセス)のほぼ完全なコピーである新しいプロセス(子プロセス)を作成します。子プロセスは親プロセスのメモリ空間、開いているファイルディスクリプタ、環境変数などの状態を継承します。exec()
(例:execve()
): 現在のプロセスイメージを、指定された新しいプログラムで置き換えます。exec
が成功すると、元のプログラムは実行を停止し、新しいプログラムがそのプロセスのコンテキストで実行を開始します。- Go言語の
os/exec
パッケージは、内部的にこれらのシステムコールを利用して外部コマンドを実行します。
-
ファイルディスクリプタ (File Descriptor, fd):
- Unix系OSにおいて、開かれたファイル、ソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数です。
fork
によって子プロセスが生成されると、通常、親プロセスが保持していたすべての開かれたfdが子プロセスに継承されます。
-
syscall.ForkLock
(Go言語の内部ロック):- Goランタイム内部で使用されるミューテックス(排他ロック)の一種で、
sync.RWMutex
のインスタンスです。 - その主な目的は、
fork-exec
操作の整合性を保証することです。fork
が実行される直前と直後に、ファイルディスクリプタの状態が安定していることを保証するために使用されます。 - 具体的には、
fork
中に新しいfdが開かれたり閉じられたりするのを防ぎ、子プロセスに引き継がれるべきでないfdが確実にクローズされるようにします。これにより、子プロセスが意図しないリソースにアクセスしたり、リソースリークが発生したりするのを防ぎます。 ForkLock
は、fork-exec
操作時には書き込みロック(Lock()
)として取得され、他のファイル操作時には読み取りロック(RLock()
)として取得されることがあります。
- Goランタイム内部で使用されるミューテックス(排他ロック)の一種で、
-
ブロックするオープン (Blocked Opens):
- ファイルを開く操作(
open
システムコール)が、すぐに完了せずに、何らかの外部条件が満たされるまで実行を一時停止する状態を指します。 - 例えば、ネットワーク越しにアクセスするファイルがネットワークの遅延によってすぐに開けない場合や、特定のデバイスファイルが準備できるまで待機する場合などがこれに該当します。
- Plan 9では、「すべてがファイル」という設計思想により、通常のファイルだけでなく、プロセス間通信やデバイスアクセスなどもファイルとして扱われるため、ブロックするオープンがより頻繁に発生する可能性があります。
- ファイルを開く操作(
これらの概念を理解することで、ForkLock
がなぜ存在し、なぜPlan 9の特定の状況でその保持が問題となるのか、そしてこのコミットがその問題をどのように解決しようとしているのかが明確になります。
技術的詳細
このコミットの技術的核は、Go言語のos.OpenFile
関数がPlan 9上でどのように動作し、syscall.ForkLock
との相互作用がどのようにデッドロックを引き起こしていたかを理解することにあります。
Go言語のos
パッケージは、オペレーティングシステムとのインタフェースを提供し、ファイル操作などのシステムレベルの機能を提供します。os.OpenFile
は、指定されたパス、フラグ、パーミッションでファイルを開くためのGo言語の標準関数です。この関数は内部的に、対応するOSのシステムコールを呼び出します。Plan 9の場合、これはsyscall.Create
(ファイル作成時)やsyscall.Open
(ファイルオープン時)といったシステムコールにマッピングされます。
変更前のsrc/pkg/os/file_plan9.go
内のOpenFile
関数の実装では、ファイルを開くシステムコールを呼び出す直前にsyscall.ForkLock.RLock()
を呼び出し、システムコールが完了した後にsyscall.ForkLock.RUnlock()
を呼び出していました。これは、OpenFile
がファイルディスクリプタを操作する際に、ForkLock
を読み取りロックとして取得していたことを意味します。読み取りロックは、複数の読み取り操作が同時に行われることを許可しますが、書き込みロック(Lock()
)とは同時に取得できません。
問題は、Plan 9のopen
システムコールがブロックされる可能性があるという特性にありました。os.OpenFile
がForkLock.RLock()
を取得した後、syscall.Create
またはsyscall.Open
を呼び出し、このシステムコールが何らかの理由でブロックされると、ForkLock
はブロックされたまま保持され続けます。
// 変更前の os.OpenFile (擬似コード)
func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
syscall.ForkLock.RLock() // ここで読み取りロックを取得
// ... ファイルオープンシステムコール (syscall.Create または syscall.Open) を呼び出す ...
// もしこのシステムコールがブロックされると、ForkLockは解放されないままになる
syscall.ForkLock.RUnlock() // ここで読み取りロックを解放
// ...
}
一方で、Goプログラム内で別のゴルーチンがos/exec
パッケージなどを介して新しいプロセスを生成しようとすると、そのfork-exec
操作はsyscall.ForkLock
を排他的な書き込みロック(Lock()
)として取得しようとします。しかし、os.OpenFile
が既に読み取りロックを保持しているため、fork-exec
操作はForkLock
が解放されるまで待機することになります。結果として、os.OpenFile
のブロックされたopen
操作が完了するまで、fork-exec
操作もブロックされ、アプリケーション全体がハングアップするという状況が発生していました。
このコミットでは、src/pkg/os/file_plan9.go
ファイル内のOpenFile
関数から、以下の2行を単純に削除しています。
syscall.ForkLock.RLock()
syscall.ForkLock.RUnlock()
この変更により、os.OpenFile
はファイルを開く際にForkLock
を一切使用しなくなります。したがって、open
システムコールがブロックされても、ForkLock
は他のfork-exec
操作のために解放されたままになります。これにより、fork-exec
操作がos.OpenFile
のブロックに引きずられてハングアップする問題が解消されます。
この解決策は、ファイルディスクリプタのリーク防止というForkLock
の本来の目的の一部を、Plan 9の特定の状況下で緩和することを意味します。コミットメッセージにも「protecting against fd leaks into fork-execs means not being able to do fork-execs properly in the general case. Thus, we forgo taking the lock.」(fork-exec
へのfdリークを防ぐことは、一般的なケースでfork-exec
を適切に実行できないことを意味する。したがって、ロックの取得を放棄する。)と明記されています。これは、Plan 9の環境では、fork-exec
の機能性を確保することの方が、厳密なfdリーク防止よりも優先されるという、実用的な判断が下されたことを示しています。
コアとなるコードの変更箇所
変更はsrc/pkg/os/file_plan9.go
ファイルに限定されています。
--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -104,7 +104,6 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
append = true
}
- syscall.ForkLock.RLock()
if (create && trunc) || excl {
fd, e = syscall.Create(name, flag, syscallMode(perm))
} else {
@@ -117,7 +116,6 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
}
}
}\n- syscall.ForkLock.RUnlock()\n
if e != nil {
return nil, &PathError{"open", name, e}
具体的には、OpenFile
関数の内部で、以下の2行が削除されています。
syscall.ForkLock.RLock()
syscall.ForkLock.RUnlock()
コアとなるコードの解説
削除された2行は、os.OpenFile
関数がファイルを開くシステムコール(syscall.Create
またはsyscall.Open
)を呼び出す前後に、syscall.ForkLock
というグローバルな読み取りロックを取得・解放する役割を担っていました。
syscall.ForkLock.RLock()
: この行は、ファイルオープン操作を開始する前に、ForkLock
の読み取りロックを取得していました。これにより、他のfork-exec
操作が同時に発生した場合に、ファイルディスクリプタの状態が不整合になるのを防ぐ意図がありました。syscall.ForkLock.RUnlock()
: この行は、ファイルオープン操作が完了した後(またはエラーが発生した後)に、取得した読み取りロックを解放していました。
これらの行が削除されたことにより、os.OpenFile
はファイルを開く際にForkLock
を一切使用しなくなりました。結果として、open
システムコールがブロックされたとしても、ForkLock
は他のfork-exec
操作のために自由に利用可能となり、fork-exec
がハングアップする問題が解消されます。
この変更は、Plan 9の特定の挙動(open
がブロックされる可能性が高いこと)に対応するためのトレードオフであり、ForkLock
の目的である「fork-exec
中のファイルディスクリプタの整合性保証」の一部を緩和しています。しかし、コミットメッセージが示唆するように、Plan 9においては、この緩和がfork-exec
の機能性を確保するために必要不可欠であると判断されました。
関連リンク
- Go言語の
os
パッケージドキュメント: https://pkg.go.dev/os - Go言語の
syscall
パッケージドキュメント: https://pkg.go.dev/syscall - Go言語の
os/exec
パッケージドキュメント: https://pkg.go.dev/os/exec - Plan 9 from Bell Labs (Wikipedia): https://ja.wikipedia.org/wiki/Plan_9_from_Bell_Labs
- Go言語の
ForkLock
に関する議論(GoのIssueトラッカーやメーリングリストで検索すると、より詳細な背景が見つかる可能性があります)
参考にした情報源リンク
- Go言語のソースコード(特に
src/pkg/os/file_plan9.go
の変更履歴) - Go言語の公式ドキュメント
- コミットメッセージに記載されているGo CL (Code Review) リンク: https://golang.org/cl/7235066 (このリンクは古い形式のため、現在のGo GerritのURLにリダイレクトされるか、アーカイブされている可能性があります。正確なCLはGerritで検索する必要があります。)
- Plan 9オペレーティングシステムに関する一般的な情報源(書籍、オンラインドキュメントなど)
- Unix/Linuxの
fork()
とexec()
に関するドキュメントや解説 - ミューテックスや排他制御に関する一般的なコンピュータサイエンスの知識
- Web検索: "Go language ForkLock Plan 9" (この検索結果から、
ForkLock
がGo言語の標準的な概念ではなく、syscall
パッケージ内の特定のOS向けの実装であることが示唆されました。)