[インデックス 13203] ファイルの概要
このコミットは、Go言語のnetパッケージにおいて、Unixドメインソケット接続を表すUnixConn型にCloseReadおよびCloseWriteメソッドを追加するものです。これにより、Unixドメインソケットの読み取り側または書き込み側のみを個別にシャットダウンする機能が提供されます。これは、TCPソケットにおけるshutdown(2)システムコールと同様の機能であり、特定の通信シナリオで必要となる場合があります。
コミット
commit 0ce90459e8654762d54b36d488f9ce0121589242
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed May 30 00:08:58 2012 +0200
net: add CloseRead, CloseWrite methods to UnixConn.
Fixes #3345.
R=golang-dev, r, rsc, dave
CC=golang-dev, remy
https://golang.org/cl/6214061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0ce90459e8654762d54b36d488f9ce0121589242
元コミット内容
net: add CloseRead, CloseWrite methods to UnixConn.
Fixes #3345.
R=golang-dev, r, rsc, dave
CC=golang-dev, remy
https://golang.org/cl/6214061
変更の背景
この変更は、Go言語のIssue 3345「net: add CloseRead/CloseWrite to UnixConn」に対応するものです。このIssueでは、TCP接続(TCPConn)にはCloseReadとCloseWriteメソッドが存在するにもかかわらず、Unixドメインソケット接続(UnixConn)には同等の機能がないことが指摘されていました。
ネットワークプログラミングにおいて、ソケットの読み取り側または書き込み側だけをシャットダウンする機能は、特定のプロトコルやアプリケーションロジックで重要になります。例えば、クライアントがデータの送信を完了したが、サーバーからの応答をまだ受信する必要がある場合、クライアントはソケットの書き込み側をシャットダウン(CloseWrite)して、サーバーにこれ以上データが送信されないことを通知できます。これにより、サーバーはデータの終端を検出し、応答の送信を開始できます。同様に、サーバーがクライアントへの応答を完了したが、クライアントからの追加のデータ受信を待つ必要がない場合、サーバーはソケットの書き込み側をシャットダウンできます。
UnixConnにこれらのメソッドがないことは、Unixドメインソケットを使用するアプリケーションの柔軟性を制限していました。このコミットは、この機能ギャップを埋め、UnixConnがTCPConnと同様に、より高度な半クローズ操作をサポートできるようにすることを目的としています。
前提知識の解説
- Unixドメインソケット (Unix Domain Sockets - UDS): 同じホスト上のプロセス間通信 (IPC) のためのメカニズムです。TCP/IPソケットがネットワークを介した通信に使用されるのに対し、UDSはファイルシステム上のパス名(ソケットファイル)を介して通信を行います。ネットワークオーバーヘッドがないため、同じホスト上でのIPCにおいて非常に効率的です。
net.Connインターフェース: Go言語のnetパッケージで定義されている基本的なネットワーク接続インターフェースです。Read、Write、Closeなどのメソッドを定義しています。net.UnixConn:net.Connインターフェースを実装する具体的な型の一つで、Unixドメインソケット接続を表します。CloseRead()/CloseWrite(): これらのメソッドは、ソケットの特定の方向(読み取りまたは書き込み)をシャットダウンするために使用されます。CloseRead(): ソケットの読み取り側をシャットダウンします。これにより、それ以降の読み取り操作はEOF(End Of File)を返します。CloseWrite(): ソケットの書き込み側をシャットダウンします。これにより、それ以降の書き込み操作はエラーを返します。リモートエンドポイントは、このソケットからのデータ受信が終了したことを通知されます。
shutdown(2)システムコール: POSIXシステムにおけるソケット操作のためのシステムコールで、ソケットの接続の一部または全体をシャットダウンするために使用されます。SHUT_RD(読み取り側をシャットダウン)、SHUT_WR(書き込み側をシャットダウン)、SHUT_RDWR(両方をシャットダウン)のオプションがあります。CloseReadとCloseWriteは、このシステムコールをGoの抽象化として提供します。io.EOF:ioパッケージで定義されているエラーで、入力の終わりに達したことを示します。CloseReadが呼び出された後、ソケットからの読み取り操作はio.EOFを返します。syscall.EINVAL: 無効な引数を示すシステムコールエラーです。syscall.EPLAN9: Plan 9オペレーティングシステム特有のエラーコードで、通常は「操作がサポートされていない」ことを示します。
技術的詳細
このコミットの主要な技術的変更点は、net.UnixConn型にCloseReadとCloseWriteメソッドを追加し、それらの実装を各OS(POSIX互換システムとPlan 9)のソケットAPIにマッピングしたことです。
-
net.UnixConnへのメソッド追加:UnixConn型にCloseRead() errorとCloseWrite() errorの2つの新しいメソッドが追加されました。これらのメソッドは、ソケットのファイルディスクリプタ(c.fd)に対して、基盤となるOSのソケットシャットダウン操作を呼び出します。 -
POSIXシステム (
unixsock_posix.go) での実装: POSIX互換システム(Linux, macOSなど)では、UnixConnのCloseReadとCloseWriteは、内部的にc.fd.CloseRead()とc.fd.CloseWrite()を呼び出します。これらの内部メソッドは、最終的にshutdown(2)システムコールを適切な引数(SHUT_RDまたはSHUT_WR)で呼び出すことになります。 実装では、c.ok()というチェックが行われ、接続が有効な状態であるかを確認しています。無効な場合はsyscall.EINVALエラーを返します。 -
Plan 9システム (
unixsock_plan9.go) での実装: Plan 9は、Go言語がサポートするOSの一つですが、そのシステムコールAPIはPOSIXとは異なります。このコミットの時点では、Plan 9のUnixドメインソケットには半クローズ機能が直接サポートされていなかったか、実装が複雑であったため、CloseReadとCloseWriteは単純にsyscall.EPLAN9エラーを返すように実装されています。これは、Plan 9ではこれらの操作がサポートされていないことを示します。 -
テストケースの追加 (
net_test.go): 新しいTestShutdownUnixテスト関数が追加されました。このテストは、UnixドメインソケットのCloseWrite機能が正しく動作することを確認します。- 一時的なUnixドメインソケットファイルを作成し、リスナーを起動します。
- ゴルーチン内でサーバー側が接続を受け入れ、クライアントからの書き込みシャットダウン(EOF)を待ち、その後応答を書き込みます。
- クライアント側はソケットに接続し、
CloseWrite()を呼び出して書き込み側をシャットダウンします。 - クライアントはサーバーからの応答を読み取り、期待される応答が返されることを確認します。
- このテストは、Plan 9ではスキップされます。
これらの変更により、Goのnetパッケージは、Unixドメインソケットにおいても、よりきめ細やかな接続制御を提供できるようになりました。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードブロックは以下の通りです。
-
src/pkg/net/net_test.go:import文にio/ioutilとosが追加されました。TestShutdownUnix関数が追加されました。この関数は、UnixConn.CloseWrite()の動作を検証するテストケースです。
-
src/pkg/net/unixsock_plan9.go:UnixConn型にCloseRead()メソッドとCloseWrite()メソッドが追加されました。- 両メソッドとも、Plan 9ではサポートされていないため、
syscall.EPLAN9を返します。
// CloseRead shuts down the reading side of the Unix domain connection. // Most callers should just use Close. func (c *UnixConn) CloseRead() error { return syscall.EPLAN9 } // CloseWrite shuts down the writing side of the Unix domain connection. // Most callers should just use Close. func (c *UnixConn) CloseWrite() error { return syscall.EPLAN9 } -
src/pkg/net/unixsock_posix.go:UnixConn型にCloseRead()メソッドとCloseWrite()メソッドが追加されました。- これらのメソッドは、内部の
c.fd.CloseRead()およびc.fd.CloseWrite()を呼び出します。これは、POSIXシステムコールshutdown(2)へのラッパーです。 - メソッドの冒頭で
c.ok()チェックが追加され、接続が有効であることを確認しています。
// CloseRead shuts down the reading side of the Unix domain connection. // Most callers should just use Close. func (c *UnixConn) CloseRead() error { if !c.ok() { return syscall.EINVAL } return c.fd.CloseRead() } // CloseWrite shuts down the writing side of the Unix domain connection. // Most callers should just use Close. func (c *UnixConn) CloseWrite() error { if !c.ok() { return syscall.EINVAL } return c.fd.CloseWrite() }
コアとなるコードの解説
このコミットの核心は、net.UnixConnに半クローズ機能を追加することです。
src/pkg/net/unixsock_posix.go の変更点:
// CloseRead shuts down the reading side of the Unix domain connection.
// Most callers should just use Close.
func (c *UnixConn) CloseRead() error {
if !c.ok() { // 接続が有効かどうかのチェック
return syscall.EINVAL // 無効な場合はエラーを返す
}
return c.fd.CloseRead() // 内部のファイルディスクリプタのCloseReadを呼び出す
}
// CloseWrite shuts down the writing side of the Unix domain connection.
// Most callers should just use Close.
func (c *UnixConn) CloseWrite() error {
if !c.ok() { // 接続が有効かどうかのチェック
return syscall.EINVAL // 無効な場合はエラーを返す
}
return c.fd.CloseWrite() // 内部のファイルディスクリプタのCloseWriteを呼び出す
}
このコードは、UnixConnがラップしている基盤となるファイルディスクリプタ(c.fd)に対して、読み取り側または書き込み側のシャットダウンを要求しています。c.fd.CloseRead()とc.fd.CloseWrite()は、Goの内部実装で、最終的にPOSIXのshutdown(2)システムコールをSHUT_RDまたはSHUT_WRオプションで呼び出すことになります。c.ok()チェックは、ソケットがまだ有効な状態であるかを確認し、無効なソケットに対する操作を防ぎます。
src/pkg/net/unixsock_plan9.go の変更点:
// CloseRead shuts down the reading side of the Unix domain connection.
// Most callers should just use Close.
func (c *UnixConn) CloseRead() error {
return syscall.EPLAN9 // Plan 9ではサポートされていないため、エラーを返す
}
// CloseWrite shuts down the writing side of the Unix domain connection.
// Most callers should just use Close.
func (c *UnixConn) CloseWrite() error {
return syscall.EPLAN9 // Plan 9ではサポートされていないため、エラーを返す
}
Plan 9では、この機能が直接サポートされていないため、これらのメソッドは常にsyscall.EPLAN9エラーを返します。これは、Goが異なるOSの特性を透過的に扱うための一般的なアプローチです。
src/pkg/net/net_test.go の変更点:
func TestShutdownUnix(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Logf("skipping test on %q", runtime.GOOS)
return
}
// ... (一時ファイルの作成とリスナーの起動) ...
go func() { // サーバー側のゴルーチン
c, err := ln.Accept() // 接続を受け入れる
if err != nil {
t.Fatalf("Accept: %v", err)
}
var buf [10]byte
n, err := c.Read(buf[:]) // クライアントからのEOFを待つ(CloseWriteによる)
if n != 0 || err != io.EOF {
t.Fatalf("server Read = %d, %v; want 0, io.EOF", n, err)
}
c.Write([]byte("response")) // 応答を書き込む
c.Close() // 接続を閉じる
}()
c, err := Dial("unix", tmpname) // クライアント側の接続
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer c.Close()
err = c.(*UnixConn).CloseWrite() // クライアント側で書き込みをシャットダウン
if err != nil {
t.Fatalf("CloseWrite: %v", err)
}
var buf [10]byte
n, err := c.Read(buf[:]) // サーバーからの応答を読み取る
if err != nil {
t.Fatalf("client Read: %d, %v", n, err)
}
got := string(buf[:n])
if got != "response" {
t.Errorf("read = %q, want \"response\"", got)
}
}
このテストケースは、UnixConn.CloseWrite()が正しく機能することを確認するためのものです。クライアントがCloseWrite()を呼び出すと、サーバー側はRead操作でio.EOFを受け取ります。これは、クライアントがこれ以上データを送信しないことを意味します。その後、サーバーは応答を送信し、クライアントはそれを受信できることを確認します。このテストは、半クローズ機能が期待通りに動作することを示す重要な検証です。
関連リンク
- Go Issue 3345: https://github.com/golang/go/issues/3345
- Go CL 6214061: https://golang.org/cl/6214061
参考にした情報源リンク
- Go Issue 3345 (上記に同じ)
- Go CL 6214061 (上記に同じ)
- Unix Domain Sockets (Wikipedia): https://en.wikipedia.org/wiki/Unix_domain_socket
- shutdown(2) man page (Linux): https://man7.org/linux/man-pages/man2/shutdown.2.html
- Go
netpackage documentation: https://pkg.go.dev/net - Go
syscallpackage documentation: https://pkg.go.dev/syscall - Go
iopackage documentation: https://pkg.go.dev/io - Go
runtimepackage documentation: https://pkg.go.dev/runtime - Go
io/ioutilpackage documentation: https://pkg.go.dev/io/ioutil (Note:io/ioutilis deprecated in newer Go versions, but was relevant at the time of this commit.) - Go
ospackage documentation: https://pkg.go.dev/os