[インデックス 12299] ファイルの概要
このコミットは、Go言語のosパッケージにおける外部プロセス管理のセマンティクス、特にProcess.WaitメソッドとProcess.Releaseメソッドの挙動を修正するものです。主な目的は、Windows環境においてProcess.Waitが呼び出された際に、関連するプロセスハンドルが確実に解放されるようにすることです。これにより、リソースリークの可能性を排除し、APIの利用をより直感的かつ安全にします。
コミット
commit ed238ca4e5e94cbbc0b2d0922e5ae1df2247ca68
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu Mar 1 17:36:35 2012 +1100
os: release process handle at the end of windows (*Process).Wait
Fixes #3154.
R=golang-dev, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/5707052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ed238ca4e5e94cbbc0b2d0922e5ae1df2247ca68
元コミット内容
このコミットは、Go言語のosパッケージにおいて、Windows環境での(*Process).Waitメソッドの実行終了時にプロセスハンドルを解放するように変更します。これは、GoのIssue #3154で報告された問題を修正するためのものです。
変更の背景
Go言語のosパッケージは、外部プロセスの起動と管理のための機能を提供します。os.StartProcess関数でプロセスを起動すると、*os.Process型のオブジェクトが返されます。このオブジェクトは、起動されたプロセスに関する情報(PIDなど)と、そのプロセスを操作するためのメソッド(Wait, Signal, Releaseなど)を提供します。
このコミットが修正する問題(Issue #3154)は、Process.Releaseメソッドのセマンティクスと、Process.Waitメソッドとの関係に関するものでした。特にWindowsのようなOSでは、プロセスを起動すると、そのプロセスへの参照(ハンドル)がカーネルによって管理されます。このハンドルは、プロセスが終了した後も、その終了ステータスなどを取得するために保持されることがあります。しかし、このハンドルは有限のリソースであり、不要になったら明示的に解放する必要があります。解放を怠ると、リソースリークが発生し、システム全体のパフォーマンスに影響を与える可能性があります。
従来のGoのコードベースでは、Process.Release()を明示的に呼び出すことでプロセスハンドルを解放する設計になっていました。しかし、多くのユーザーはプロセスが終了するのを待つためにProcess.Wait()を呼び出すことが一般的であり、Wait()が完了した後にRelease()を別途呼び出す必要があるのか、あるいはWait()が自動的に解放するべきなのかという点で混乱が生じていました。特に、defer p.Release()のような記述がコードの様々な場所で見られましたが、これはWait()が既にリソースを解放している場合に冗長になったり、あるいはWait()が呼ばれないパスでRelease()が呼ばれない場合にリークを引き起こす可能性がありました。
このコミットは、この曖昧さを解消し、Process.Waitがプロセスリソースの解放も担当するように変更することで、APIの利用をより堅牢かつ直感的にすることを目的としています。
前提知識の解説
- Go言語の
osパッケージ: Goの標準ライブラリの一部で、オペレーティングシステムとの基本的な相互作用を提供します。ファイルシステム操作、環境変数、そして外部プロセスの起動と管理などが含まれます。 os.Process構造体:osパッケージで定義されている構造体で、起動された外部プロセスを表します。この構造体には、プロセスのPID(プロセスID)や、プロセスを操作するためのメソッドが含まれます。Process.Start():osパッケージの関数で、新しいプロセスを起動します。成功すると*os.Processオブジェクトとnilエラーを返します。Process.Wait():*os.Processオブジェクトのメソッドで、関連するプロセスが終了するまで待機し、その終了ステータスを返します。このメソッドは、プロセスが終了するまでブロックします。Process.Release():*os.Processオブジェクトのメソッドで、関連するプロセスが使用していたシステムリソース(特にWindowsにおけるプロセスハンドル)を解放します。このメソッドが呼び出された後、*os.Processオブジェクトは無効になります。- Windowsにおけるプロセスハンドル: Windowsオペレーティングシステムでは、プロセスやスレッドなどのカーネルオブジェクトは「ハンドル」と呼ばれる識別子を通じて参照されます。これらのハンドルは有限のリソースであり、不要になったら
CloseHandleなどのAPIを呼び出して明示的に解放する必要があります。解放を怠ると、ハンドルリークが発生し、システムリソースが枯渇する可能性があります。 deferキーワード: Go言語のキーワードで、その関数がリターンする直前に指定された関数呼び出しを遅延実行させます。リソースのクリーンアップ(ファイルのクローズ、ロックの解放など)によく使用されます。- エクスポートされた/されていないメソッド(大文字/小文字): Go言語では、識別子(変数名、関数名、メソッド名など)の最初の文字が大文字である場合、その識別子はパッケージ外からアクセス可能(エクスポートされている)になります。小文字で始まる場合は、パッケージ内でのみアクセス可能(エクスポートされていない)です。
技術的詳細
このコミットの核心は、os.Processのリソース管理モデルを変更することにあります。
-
Process.Waitの責任の拡張:- Windows環境の
src/pkg/os/exec_windows.goにおいて、(*Process).Waitメソッドの最後にdefer p.Release()が追加されました。これにより、Waitメソッドが完了する際に、関連するプロセスハンドルが自動的に解放されることが保証されます。 Process.Waitのドキュメントコメントも更新され、「Wait releases any resources associated with the Process.」(Waitはプロセスに関連付けられた全てのリソースを解放する)という記述が追加されました。これは、Waitがリソース解放の責任を持つことを明確に示しています。
- Windows環境の
-
Process.Releaseの内部化:src/pkg/os/exec_plan9.go,src/pkg/os/exec_unix.go,src/pkg/os/exec_windows.goの各ファイルで、エクスポートされていたfunc (p *Process) Release()メソッドが、アンエクスポートされたfunc (p *Process) release()メソッドにリネームされました。- これにより、
Releaseメソッドはパッケージ外部から直接呼び出すことができなくなり、Processオブジェクトのリソース解放はWaitメソッドを通じてのみ行われるべきであるという意図が強制されます。
-
既存の
defer p.Release()呼び出しの削除:src/cmd/cgo/util.go,src/cmd/godoc/main.go,src/pkg/net/http/triv.go,src/pkg/os/os_test.goといった複数のファイルから、以前に存在していたdefer p.Release()の呼び出しが削除されました。- これは、
Process.Waitがリソース解放の責任を持つようになったため、これらの場所で明示的にReleaseを呼び出す必要がなくなったためです。これにより、コードの冗長性が減り、APIの利用が簡素化されます。
-
os/doc.goのドキュメント更新:src/pkg/os/doc.goに、エクスポートされたProcess.Release()メソッドの新しいドキュメントが追加されました。このドキュメントには、「Release only needs to be called if Wait is not.」(ReleaseはWaitが呼び出されない場合にのみ呼び出す必要がある)と明記されており、Waitがリソース解放の主要なメカニズムであることを強調しています。
これらの変更により、Goのosパッケージにおけるプロセスリソース管理のセマンティクスが明確化され、特にWindows環境でのリソースリークのリスクが低減されました。ユーザーは通常、Process.Waitを呼び出すだけでよく、明示的なReleaseの呼び出しについて心配する必要がなくなります。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下の通りです。
-
src/pkg/os/exec_windows.go:func (p *Process) Wait()メソッド内にdefer p.Release()が追加されました。func (p *Process) Release()がfunc (p *Process) release()にリネームされました。Waitメソッドのコメントが更新され、「Wait releases any resources associated with the Process.」が追加されました。
--- a/src/pkg/os/exec_windows.go +++ b/src/pkg/os/exec_windows.go @@ -14,6 +14,7 @@ import ( // Wait waits for the Process to exit or stop, and then returns a // ProcessState describing its status and an error, if any. +// Wait releases any resources associated with the Process. func (p *Process) Wait() (ps *ProcessState, err error) { s, e := syscall.WaitForSingleObject(syscall.Handle(p.handle), syscall.INFINITE) switch s { @@ -30,6 +31,7 @@ func (p *Process) Wait() (ps *ProcessState, err error) { return nil, NewSyscallError("GetExitCodeProcess", e) } p.done = true + defer p.Release() return &ProcessState{p.Pid, syscall.WaitStatus{Status: s, ExitCode: ec}, new(syscall.Rusage)}, nil } @@ -46,8 +48,7 @@ func (p *Process) Signal(sig Signal) error { return syscall.Errno(syscall.EWINDOWS) } -// Release releases any resources associated with the Process. -func (p *Process) Release() error { +func (p *Process) release() error { if p.handle == uintptr(syscall.InvalidHandle) { return syscall.EINVAL } -
src/pkg/os/exec_plan9.goおよびsrc/pkg/os/exec_unix.go:func (p *Process) Release()がfunc (p *Process) release()にリネームされました。
--- a/src/pkg/os/exec_plan9.go +++ b/src/pkg/os/exec_plan9.go @@ -94,8 +94,7 @@ func (p *Process) Wait() (ps *ProcessState, err error) { return ps, nil } -// Release releases any resources associated with the Process. -func (p *Process) Release() error { +func (p *Process) release() error { // NOOP for Plan 9. p.Pid = -1 // no need for a finalizer anymore--- a/src/pkg/os/exec_unix.go +++ b/src/pkg/os/exec_unix.go @@ -51,8 +51,7 @@ func (p *Process) Signal(sig Signal) error { return nil } -// Release releases any resources associated with the Process. -func (p *Process) Release() error { +func (p *Process) release() error { // NOOP for unix. p.Pid = -1 // no need for a finalizer anymore -
src/cmd/cgo/util.go,src/cmd/godoc/main.go,src/pkg/net/http/triv.go,src/pkg/os/os_test.go:- これらのファイルから
defer p.Release()の呼び出しが削除されました。
例:
src/cmd/cgo/util.go--- a/src/cmd/cgo/util.go +++ b/src/cmd/cgo/util.go @@ -36,7 +36,6 @@ func run(stdin []byte, argv []string) (stdout, stderr []byte, ok bool) { if err != nil { fatalf("%s", err) } - defer p.Release() r0.Close() w1.Close() w2.Close() - これらのファイルから
-
src/pkg/os/doc.go:Process.Releaseのドキュメントが追加されました。
--- a/src/pkg/os/doc.go +++ b/src/pkg/os/doc.go @@ -11,6 +11,13 @@ func FindProcess(pid int) (p *Process, err error) { return findProcess(pid) } +// Release releases any resources associated with the Process p, +// rendering it unusable in the future. +// Release only needs to be called if Wait is not. +func (p *Process) Release() error { + return p.release() +} + // Hostname returns the host name reported by the kernel. func Hostname() (name string, err error) { return hostname()
コアとなるコードの解説
このコミットの最も重要な変更は、src/pkg/os/exec_windows.goにおけるProcess.Waitメソッドの挙動の変更です。
Windowsでは、os.StartProcessによって新しいプロセスが起動されると、そのプロセスへのハンドルが取得されます。このハンドルは、プロセスが終了した後も、その終了コードなどの情報を取得するために必要ですが、最終的にはシステムリソースを解放するために閉じられる必要があります。
変更前は、ユーザーがProcess.Wait()を呼び出した後、明示的にProcess.Release()を呼び出すか、defer p.Release()を使ってリソースを解放する必要がありました。しかし、これはAPIの利用を複雑にし、Release()の呼び出し忘れによるハンドルリークの可能性がありました。
このコミットでは、Process.Wait()の内部にdefer p.Release()を追加することで、この問題を解決しています。deferキーワードにより、Wait()メソッドが正常に終了するか、エラーで終了するかにかかわらず、必ずp.Release()が呼び出されることが保証されます。これにより、Wait()が完了した時点でプロセスハンドルが自動的に解放されるようになります。
また、Process.Release()メソッド自体がProcess.release()というアンエクスポートされた名前に変更されたことで、外部のコードからは直接このメソッドを呼び出すことができなくなりました。これは、Wait()がリソース解放の主要な経路となり、ユーザーがRelease()を明示的に呼び出す必要がほとんどなくなったことを意味します。os/doc.goの新しいドキュメントが示すように、Release()はWait()が呼び出されないような特殊なケースでのみ必要となります。
この変更は、Goのプロセス管理APIをより堅牢で使いやすくし、特にWindows環境でのリソースリークを防ぐ上で非常に重要です。
関連リンク
- Go Issue #3154: https://github.com/golang/go/issues/3154
- Go CL 5707052: https://golang.org/cl/5707052
参考にした情報源リンク
- Go言語の公式ドキュメント (
osパッケージ): https://pkg.go.dev/os - Go言語の
deferステートメントに関するドキュメント: https://go.dev/tour/flowcontrol/12 - Windowsにおけるプロセスとスレッドのハンドルに関するMicrosoftのドキュメント (一般的な概念理解のため): https://learn.microsoft.com/ja-jp/windows/win32/sysinfo/handles-and-objects
- Go言語のIssueトラッカーでの議論 (Issue #3154): 上記の関連リンクを参照。