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

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

このコミットは、Go言語の net パッケージにおけるUnixドメインソケットのオートバインド機能に関するバグ修正です。具体的には、オートバインドされたUnixリスナーを Close する際の挙動が修正されています。

コミット

commit 2be62360b2748636a2849e959dede7359383b200
Author: Albert Strasheim <fullung@gmail.com>
Date:   Mon Sep 23 22:33:42 2013 -0400

    net: fix Close of autobind unix listener
    
    Fixes #6455.
    
    R=mikioh.mikioh, rsc
    CC=golang-dev
    https://golang.org/cl/13457058

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

https://github.com/golang/go/commit/2be62360b2748636a2849e959dede7359383b200

元コミット内容

net: fix Close of autobind unix listener

Fixes #6455.

R=mikioh.mikioh, rsc
CC=golang-dev
https://golang.org/cl/13457058

変更の背景

このコミットは、Go言語の net パッケージにおいて、Unixドメインソケットのオートバインド機能を使用した場合に発生する Close 処理の不具合を修正するために行われました。

Unixドメインソケットでは、ソケットをファイルシステム上のパスにバインドします。通常、ユーザーは UnixAddr 構造体の Name フィールドに明示的なパスを指定してソケットをバインドします。しかし、Name フィールドを空文字列 ("") に設定すると、システムが一時的なユニークなパスを自動的に割り当ててソケットをバインドする「オートバインド」という機能があります。これは、特にクライアント側で一時的なソケットが必要な場合や、サーバー側で一時的な通信チャネルを確立する場合に便利です。

このオートバインドされたソケットを Close する際、Goの net パッケージの内部実装において、ソケットが実際にバインドされた一時的なパスが正しく認識されず、結果としてソケットファイルが適切にクリーンアップされないという問題がありました。これにより、一時ファイルが残り続け、リソースリークやファイルシステム上のゴミの蓄積につながる可能性がありました。

コミットメッセージにある "Fixes #6455" は、GoのIssueトラッカーにおける特定のバグ報告を指している可能性が高いですが、一般的なWeb検索ではRFC 6455 (WebSocket Protocol) がヒットするため、この番号がGoのIssue番号として直接参照できないか、あるいは内部的な参照である可能性があります。しかし、コミットの内容から、この問題がオートバインドされたUnixリスナーの Close 処理に関するものであることは明確です。

前提知識の解説

Unixドメインソケット (Unix Domain Sockets, UDS)

Unixドメインソケットは、同じホストマシン上で動作するプロセス間通信 (IPC) の一種です。TCP/IPソケットがネットワークを介した通信に使用されるのに対し、Unixドメインソケットはファイルシステム上のパス(通常はファイル)を介して通信を行います。これにより、ネットワークスタックを介さないため、TCP/IPソケットよりも高速で低レイテンシな通信が可能です。

Unixドメインソケットには、ストリーム型(TCPに類似)とデータグラム型(UDPに類似)があります。Goの net パッケージでは、ListenUnixDialUnix などの関数を通じてUnixドメインソケットを扱います。

オートバインド (Autobind)

Unixドメインソケットにおいて、ソケットを特定のファイルパスに明示的にバインドする代わりに、システムに一時的なユニークなファイルパスを自動的に割り当てさせる機能です。Goの net パッケージでは、UnixAddr 構造体の Name フィールドを空文字列 ("") に設定することでオートバインドが有効になります。

オートバインドされたソケットは、通常、そのソケットが閉じられたときに、システムによって自動的に作成された一時ファイルも削除されることが期待されます。

net パッケージ (Go言語)

Go言語の標準ライブラリである net パッケージは、ネットワークI/Oのプリミティブを提供します。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うためのインターフェースが含まれています。

net パッケージにおけるリスナー(net.Listener インターフェースを実装する型)は、通常、Close() メソッドを提供します。このメソッドは、リスナーを閉じ、関連するリソース(ソケットファイルなど)を解放する責任があります。Unixドメインソケットの場合、Close() はバインドされたソケットファイルを削除することも含みます。

UnixAddr 構造体

net パッケージにおける UnixAddr 構造体は、Unixドメインソケットのアドレスを表します。

type UnixAddr struct {
    Name string // ソケットのパス名
    Net  string // ネットワークタイプ ("unix", "unixgram", "unixpacket")
}

Name フィールドがソケットがバインドされるファイルパスを指定します。オートバインドの場合、このフィールドは初期化時に空文字列になります。

UnixListener 構造体

net パッケージにおける UnixListener 構造体は、Unixドメインソケットのリスナーを表します。

type UnixListener struct {
    fd *netFD // ファイルディスクリプタ
    // ...
    laddr string // リスナーのローカルアドレスの文字列表現
}

laddr フィールドは、リスナーのローカルアドレスの文字列表現を保持します。オートバインドの場合、この laddr にはシステムが割り当てた一時的なパスが格納されるべきです。

技術的詳細

このコミットの技術的な核心は、オートバインドされたUnixドメインソケットの UnixListener が、その laddr フィールドに正しいローカルアドレス(システムが割り当てた一時的なパス)を保持していなかった点にあります。

ListenUnix 関数は、Unixドメインソケットをリッスンするために使用されます。オートバインドが指定された場合(laddr.Name が空文字列の場合)、カーネルは一時的なユニークなパスを生成し、そのパスにソケットをバインドします。しかし、ListenUnix の以前の実装では、UnixListener 構造体の laddr フィールドに、初期の laddr.Name (つまり空文字列)がそのまま格納されていました。

UnixListener.Close() メソッドが呼び出された際、この laddr フィールドの値を使用して、バインドされたソケットファイルを削除しようとします。しかし、オートバインドの場合、laddr が空文字列であるため、Close() メソッドは正しい一時ファイルパスを特定できず、結果としてソケットファイルが削除されずに残ってしまうというバグがありました。

このコミットは、ListenUnix 関数内で UnixListener を初期化する際に、laddr.Name の代わりに fd.laddr.String() を使用するように変更することで、この問題を解決しています。fd.laddr.String() は、ソケットが実際にバインドされた後の、カーネルによって割り当てられた実際のローカルアドレスの文字列表現を返します。これにより、UnixListenerladdr フィールドには常に正しいパスが格納されるようになり、Close() メソッドが呼び出された際に、そのパスにある一時ファイルが適切に削除されるようになります。

また、この修正を検証するために、src/pkg/net/unix_test.goTestUnixAutobindClose という新しいテストケースが追加されました。このテストは、オートバインドされたUnixリスナーを作成し、すぐに Close() を呼び出すことで、ソケットファイルが正しくクリーンアップされることを確認します。

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

src/pkg/net/unix_test.go

--- a/src/pkg/net/unix_test.go
+++ b/src/pkg/net/unix_test.go
@@ -107,7 +107,7 @@ func TestReadUnixgramWithZeroBytesBuffer(t *testing.T) {
 	}
 }
 
-func TestUnixAutobind(t *testing.T) {
+func TestUnixgramAutobind(t *testing.T) {
 	if runtime.GOOS != "linux" {
 		t.Skip("skipping: autobind is linux only")
 	}
@@ -139,6 +139,18 @@ func TestUnixAutobind(t *testing.T) {
 	}
 }
 
+func TestUnixAutobindClose(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("skipping: autobind is linux only")
+	}
+	laddr := &UnixAddr{Name: "", Net: "unix"}
+	ln, err := ListenUnix("unix", laddr)
+	if err != nil {
+		t.Fatalf("ListenUnix failed: %v", err)
+	}
+	ln.Close()
+}
+
 func TestUnixConnLocalAndRemoteNames(t *testing.T) {
 	for _, laddr := range []string{"", testUnixAddr()} {
 	\tladdr := laddr
  • TestUnixAutobindTestUnixgramAutobind にリネームされました。
  • TestUnixAutobindClose という新しいテスト関数が追加されました。このテストは、オートバインドされたUnixリスナーを作成し、すぐに Close() を呼び出すことで、ソケットファイルが正しくクリーンアップされることを検証します。

src/pkg/net/unixsock_posix.go

--- a/src/pkg/net/unixsock_posix.go
+++ b/src/pkg/net/unixsock_posix.go
@@ -271,7 +271,7 @@ func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) {
 	if err != nil {
 		return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: err}
 	}
-	return &UnixListener{fd, laddr.Name}, nil
+	return &UnixListener{fd, fd.laddr.String()}, nil
 }
 
 // AcceptUnix accepts the next incoming call and returns the new
  • ListenUnix 関数内で UnixListener を初期化する行が変更されました。
  • 以前は laddr.Name を使用していましたが、fd.laddr.String() を使用するように変更されました。

コアとなるコードの解説

このコミットの核心的な変更は、src/pkg/net/unixsock_posix.go ファイルの ListenUnix 関数にあります。

ListenUnix 関数は、Unixドメインソケットのリスナーを作成する役割を担っています。この関数内で、ソケットが作成され、指定されたアドレスにバインドされます。オートバインドの場合、laddr.Name は空文字列ですが、ソケットが実際にバインドされると、カーネルは一時的なユニークなファイルパスを割り当てます。

変更前のコードでは、UnixListener 構造体を初期化する際に、リスナーのローカルアドレスを保持する laddr フィールドに、引数として渡された laddr.Name の値(オートバインドの場合は空文字列)をそのまま格納していました。

// 変更前
return &UnixListener{fd, laddr.Name}, nil

このため、UnixListenerladdr フィールドには、ソケットが実際にバインドされた一時的なパスではなく、初期の空文字列が格納されていました。

変更後のコードでは、fd.laddr.String() を使用して UnixListenerladdr フィールドを初期化しています。

// 変更後
return &UnixListener{fd, fd.laddr.String()}, nil

ここで fd は、ソケットのファイルディスクリプタをラップする内部構造体 netFD のインスタンスです。fd.laddr は、ソケットが実際にバインドされた後のローカルアドレス情報を保持しており、fd.laddr.String() はそのアドレスの文字列表現(つまり、カーネルが割り当てた一時的なファイルパス)を返します。

この変更により、UnixListenerladdr フィールドには、オートバインドされたソケットの実際のファイルパスが正確に格納されるようになります。その結果、UnixListener.Close() メソッドが呼び出された際に、この laddr フィールドの値を使用して、バインドされた一時ファイルが正しく特定され、削除されるようになります。これにより、オートバインドされたUnixリスナーを閉じた際に、一時ファイルが残り続けるというバグが解消されます。

src/pkg/net/unix_test.go に追加された TestUnixAutobindClose テストは、この修正が正しく機能することを確認するためのものです。このテストは、オートバインドされたリスナーを作成し、すぐに Close() を呼び出すというシンプルなシナリオで、問題が解決されたことを検証しています。

関連リンク

参考にした情報源リンク