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

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

このコミットは、Go言語の標準ライブラリ net パッケージのテスト用サンプルコード example_test.go において、TCPListener のクローズ処理を追加するものです。具体的には、ExampleListener 関数内で作成されたネットワークリスナーが適切に閉じられるように defer l.Close() が追加されています。これにより、サンプルコードが実行された際にリソースリークが発生するのを防ぎ、クリーンな終了を保証します。

コミット

commit 66b797a4f93903d00862298f9f4b80f2d1f20f41
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Fri Mar 29 15:07:10 2013 +0900

    net: close TCPListener in example
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/8073044

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

https://github.com/golang/go/commit/66b797a4f93903d00862298f9f4b80f2d1f20f41

元コミット内容

--- a/src/pkg/net/example_test.go
+++ b/src/pkg/net/example_test.go
@@ -16,6 +16,7 @@ func ExampleListener() {
  	if err != nil {
  		log.Fatal(err)
  	}\n
+\tdefer l.Close()\n
  	for {
  		// Wait for a connection.\n
  		conn, err := l.Accept()\n

変更の背景

この変更の背景には、Go言語におけるリソース管理のベストプラクティスがあります。特にネットワーク接続やファイルハンドルなどのシステムリソースは、使用後に明示的に解放する必要があります。解放を怠ると、リソースリークが発生し、システムのリソースが枯渇したり、ポートが占有されたままになったりする可能性があります。

example_test.go は、net パッケージの機能を示すためのサンプルコードであり、通常はテストスイートの一部として実行されます。このようなサンプルコードであっても、リソース管理の正しい方法を示すべきであり、また、テスト実行環境に不要なリソースを残さないようにすることが重要です。

このコミット以前の ExampleListener 関数では、net.Listen で作成された TCPListener オブジェクト l が、関数終了時に明示的にクローズされていませんでした。Goのガベージコレクタはメモリを管理しますが、OSレベルのリソース(ファイルディスクリプタ、ネットワークポートなど)は自動的には解放しません。そのため、l.Close() の呼び出しが必須となります。この修正は、この重要なクリーンアップ処理を追加することで、サンプルコードの堅牢性と正確性を向上させるものです。

前提知識の解説

Go言語の defer ステートメント

defer ステートメントは、Go言語の非常に強力な機能の一つで、関数の実行が終了する直前に、指定された関数呼び出し(または式)を遅延実行させるために使用されます。defer された関数は、その関数が return ステートメントによって正常に終了する場合でも、パニック(ランタイムエラー)によって異常終了する場合でも、必ず実行されます。

defer の主な用途は以下の通りです。

  • リソースの解放: ファイル、ネットワーク接続、ロックなどのリソースを、関数が終了する際に確実にクローズまたは解放するために使用されます。これにより、リソースリークを防ぎ、コードの可読性を向上させます。
  • エラーハンドリングの簡素化: 複雑なエラーパスを持つ関数で、クリーンアップ処理をまとめて記述できます。
  • トレース/ロギング: 関数の開始と終了をログに記録する際などに利用されます。

defer ステートメントはスタックのように動作します。つまり、複数の defer がある場合、最後に defer されたものが最初に実行されます。

net.Listener インターフェースと Close() メソッド

Go言語の net パッケージは、ネットワークプログラミングのための基本的なインターフェースと実装を提供します。 net.Listener は、着信ネットワーク接続をリッスンするための汎用インターフェースです。このインターフェースは以下のメソッドを定義しています。

  • Accept() (Conn, error): 次の着信接続を待機し、それを返します。
  • Addr() Addr: リスナーのネットワークアドレスを返します。
  • Close() error: リスナーを閉じます。これにより、新しい着信接続の受け入れが停止され、リスナーに関連付けられたすべてのリソースが解放されます。

TCPListenernet.Listener インターフェースを実装しており、TCPネットワーク接続をリッスンするために使用されます。Close() メソッドは、リスナーが使用していたポートを解放し、OSがそのポートを再利用できるようにするために不可欠です。

リソースリーク

リソースリークとは、プログラムが使用したシステムリソース(メモリ、ファイルハンドル、ネットワークソケット、データベース接続など)を適切に解放しないために、それらのリソースがシステム内に残り続ける現象を指します。長期間実行されるサーバーアプリケーションなどでは、リソースリークが発生すると、最終的にシステムのリソースが枯渇し、アプリケーションのパフォーマンス低下やクラッシュにつながる可能性があります。ネットワークリスナーの場合、ポートが解放されないと、同じポートで別のアプリケーションを起動できなくなったり、OSが利用可能なポートを使い果たしたりする原因となります。

技術的詳細

このコミットは、Go言語の net パッケージ内の example_test.go ファイルにある ExampleListener 関数に、defer l.Close() という一行を追加するものです。

ExampleListener 関数は、net.Listen("tcp", "127.0.0.1:0") を呼び出してTCPリスナーを作成します。ここで "127.0.0.1:0" は、ループバックアドレスの任意の利用可能なポートでリッスンすることを意味します。net.Listen が成功すると、l という名前の net.Listener インターフェースを実装するオブジェクトが返されます。

元のコードでは、l が作成された後、for {} ループで無限に接続を待ち受ける構造になっていました。これはサンプルコードの性質上、通常はテストフレームワークによって実行が中断されるか、あるいは手動で停止されることを想定している可能性があります。しかし、Goのテストフレームワークが関数を終了させた場合でも、l.Close() が明示的に呼び出されない限り、OSが割り当てたネットワークポートや関連するファイルディスクリプタは解放されません。

追加された defer l.Close() は、l が正常に作成された直後に配置されています。defer のセマンティクスにより、この l.Close() 呼び出しは ExampleListener 関数が終了する直前(return ステートメントが実行される直前、またはパニックが発生して関数がアンワインドされる直前)に実行されることが保証されます。これにより、ExampleListener がどのように終了しても、リスナーが使用していたリソースが確実に解放され、リソースリークが防止されます。

この変更は、Go言語のイディオムとベストプラクティスに則ったものであり、堅牢なアプリケーション開発においてリソース管理がいかに重要であるかを示しています。特に、テストやサンプルコードであっても、本番環境のコードと同様にリソース管理に注意を払うべきであるというメッセージを伝えています。

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

変更は src/pkg/net/example_test.go ファイルの ExampleListener 関数内、具体的には net.Listen の呼び出し直後に行われています。

 // src/pkg/net/example_test.go
 func ExampleListener() {
 	l, err := net.Listen("tcp", "127.0.0.1:0")
 	if err != nil {
 		log.Fatal(err)
 	}
+	defer l.Close() // この行が追加された
 	for {
 		// Wait for a connection.
 		conn, err := l.Accept()
 		if err != nil {
 			log.Fatal(err)
 		}
 		// Handle the connection in a new goroutine.
 		// Close the connection when we're done with it.
 		go func(c net.Conn) {
 			defer c.Close()
 			// Echo all incoming data.
 			io.Copy(c, c)
 		}(conn)
 	}
 }

コアとなるコードの解説

追加された defer l.Close() は、ExampleListener 関数が終了する際に、変数 l が参照する net.Listener オブジェクトの Close() メソッドを呼び出すことを保証します。

  • l: net.Listen 関数によって返される net.Listener インターフェース型の変数です。このインターフェースは、ネットワーク接続をリッスンするためのメソッドを提供し、その中にはリソースを解放するための Close() メソッドが含まれます。
  • .Close(): net.Listener インターフェースのメソッドで、リスナーを閉じ、関連するネットワークポートやシステムリソースを解放します。
  • defer: Go言語のキーワードで、それに続く関数呼び出しを、現在の関数が終了する直前まで遅延させます。これにより、関数の正常終了時でも、パニックによる異常終了時でも、確実にリソースが解放されることが保証されます。

この一行の追加により、ExampleListener 関数がどのように終了しても(例えば、テストフレームワークによって中断されたり、何らかのエラーで log.Fatal が呼び出されたりしても)、リスナーが占有していたポートが適切に解放されるようになります。これは、特にテストスイートを繰り返し実行するような環境において、ポートの競合やリソース枯渇を防ぐ上で非常に重要です。

関連リンク

  • Go言語の defer ステートメントに関する公式ドキュメントやチュートリアル
  • Go言語の net パッケージに関する公式ドキュメント
  • TCP/IPネットワークプログラミングの基本概念(ソケット、ポートなど)

参考にした情報源リンク