[インデックス 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向けの実装であることが示唆されました。)