[インデックス 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
net
package documentation: https://pkg.go.dev/net - Go
syscall
package documentation: https://pkg.go.dev/syscall - Go
io
package documentation: https://pkg.go.dev/io - Go
runtime
package documentation: https://pkg.go.dev/runtime - Go
io/ioutil
package documentation: https://pkg.go.dev/io/ioutil (Note:io/ioutil
is deprecated in newer Go versions, but was relevant at the time of this commit.) - Go
os
package documentation: https://pkg.go.dev/os