[インデックス 1779] ファイルの概要
このコミットは、Go言語のネットワークパッケージ(net
)における重要な改善を含んでいます。主に、ネットワークI/O操作におけるタイムアウト機能の導入と、並行書き込み時のデータ破損を防ぐためのロック機構の追加、そしてConn
インターフェースのドキュメント強化に焦点を当てています。これにより、ネットワーク通信の信頼性と堅牢性が向上し、開発者がより予測可能なネットワークアプリケーションを構築できるようになります。
コミット
commit 1e37e8a417dc36bc6da6828cd7c20dd53d4ba6a9
Author: Russ Cox <rsc@golang.org>
Date: Fri Mar 6 17:51:31 2009 -0800
document Conn interface better, in preparation
for per-method interface documentation
by mkdoc.pl.
implement timeouts on network reads
and use them in dns client.
also added locks on i/o to ensure writes
are not interlaced.
R=r
DELTA=340 (272 added, 25 deleted, 43 changed)
OCL=25799
CL=25874
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1e37e8a417dc36bc6da6828cd7c20dd53d4ba6a9
元コミット内容
このコミットの元の内容は以下の通りです。
Conn
インターフェースのドキュメントを改善し、mkdoc.pl
によるメソッドごとのインターフェースドキュメント生成に備える。- ネットワーク読み込みにタイムアウトを実装し、DNSクライアントでそれを使用する。
- 書き込みが混在しないように、I/Oにロックを追加する。
変更の背景
このコミットが行われた2009年3月は、Go言語がまだ公開されて間もない、非常に初期の段階でした。当時のGoのネットワークスタックは基本的な機能を提供していましたが、実用的なアプリケーション開発にはいくつかの課題がありました。
- タイムアウト機能の欠如: ネットワークI/O操作(特に読み込み)にタイムアウトが設定されていない場合、ネットワークの遅延や相手からの応答がない場合に、アプリケーションが無限にブロックされる可能性がありました。これは、応答性の高いサービスや堅牢なクライアントを構築する上で大きな問題となります。DNSクライアントのような、外部サービスとの通信を伴うコンポーネントでは、タイムアウトは必須の機能です。
- 並行書き込み時のデータ破損: 複数のゴルーチンが同時に同じネットワーク接続に書き込みを行う場合、書き込み操作がインターリーブ(混在)し、データが破損する可能性がありました。これは、TCPのようなストリーム指向のプロトコルでは特に問題となり、アプリケーションレベルでの同期が必要とされます。
- ドキュメントの不足:
Conn
インターフェースはGoのネットワークプログラミングの根幹をなすものであり、その各メソッドの振る舞いや期待される動作について、より詳細なドキュメントが必要とされていました。これは、Goの標準ライブラリの品質向上と、開発者による適切な利用を促進するために不可欠でした。
これらの課題に対処するため、このコミットではネットワークI/Oの信頼性、堅牢性、および使いやすさを向上させるための重要な変更が導入されました。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念について基本的な知識が必要です。
1. ネットワークI/Oとブロッキング/ノンブロッキングI/O
- ブロッキングI/O: I/O操作が完了するまで、呼び出し元のスレッド(またはゴルーチン)がブロックされる(待機する)方式です。シンプルですが、応答性が低下する可能性があります。
- ノンブロッキングI/O: I/O操作がすぐに戻り、データが利用可能でない場合や書き込みバッファが満杯の場合にはエラー(例:
EAGAIN
やEWOULDBLOCK
)を返します。これにより、アプリケーションはI/O操作の完了を待つ間に他の処理を行うことができます。Goのネットワークパッケージは内部的にノンブロッキングI/Oとイベント通知メカニズム(kqueue
やepoll
)を組み合わせて、効率的な並行I/Oを実現しています。
2. タイムアウト
ネットワーク通信において、特定の操作(読み込み、書き込み、接続確立など)が指定された時間内に完了しない場合に、その操作を中断しエラーを返す機能です。これにより、アプリケーションが無限に待機するのを防ぎ、リソースの枯渇や応答性の低下を防ぐことができます。
3. kqueue
とepoll
これらは、Unix系OSにおける効率的なI/Oイベント通知メカニズムです。
kqueue
(FreeBSD, macOSなど): 多数のファイルディスクリプタ(ソケットなど)からのイベント(読み込み可能、書き込み可能など)を効率的に監視するためのシステムコールです。epoll
(Linux):kqueue
と同様に、多数のファイルディスクリプタからのイベントを効率的に監視するためのLinux固有のシステムコールです。 Goのネットワークパッケージは、これらのOS固有のメカニズムを抽象化し、クロスプラットフォームで動作するノンブロッキングI/Oを提供しています。
4. ミューテックス (sync.Mutex
)
並行プログラミングにおいて、共有リソースへのアクセスを同期するためのメカニズムです。ミューテックスは、一度に一つのゴルーチンだけが特定のコードセクション(クリティカルセクション)を実行できるようにすることで、データ競合を防ぎます。このコミットでは、ネットワーク書き込み操作がインターリーブされるのを防ぐために使用されています。
5. os.EAGAIN
Unix系システムコールがノンブロッキングモードで実行され、要求された操作(読み込みや書き込み)がすぐに完了できない場合に返されるエラーコードです。これは、操作が失敗したことを意味するのではなく、後で再試行する必要があることを示します。Goのネットワークパッケージでは、このエラーを受け取った際に、pollServer
を通じてイベントの発生を待機し、I/O操作を再試行します。
技術的詳細
このコミットの技術的な変更は、主に以下の3つの柱に基づいています。
1. ネットワークI/Oタイムアウトの実装
netFD
構造体の拡張:src/lib/net/fd.go
において、netFD
構造体にrdeadline_delta
(読み込みタイムアウト期間)、rdeadline
(読み込み期限時刻)、wdeadline_delta
(書き込みタイムアウト期間)、wdeadline
(書き込み期限時刻)が追加されました。これらはナノ秒単位で管理されます。pollServer
のタイムアウト処理:pollServer
は、ノンブロッキングI/Oのイベントを監視し、I/O操作が準備できた際にゴルーチンを再開する役割を担っています。このコミットでは、pollServer
にdeadline
フィールドが追加され、監視対象のファイルディスクリプタの中で最も近い期限時刻が設定されます。pollServer.Now()
: 現在時刻をナノ秒単位で取得するヘルパー関数が追加されました。pollServer.CheckDeadlines()
:pollServer
のRun
ループ内で定期的に呼び出され、期限切れのI/O操作をチェックし、該当するnetFD
のrdeadline
またはwdeadline
を-1
に設定してタイムアウト状態を示します。pollster.WaitFD(nsec int64)
: OS固有のI/O多重化メカニズム(kqueue
やepoll
)の待機関数にタイムアウト引数(nsec
)が追加されました。これにより、指定された時間内にイベントが発生しない場合、待機が中断されます。
netFD.Read
とnetFD.Write
の変更:- これらのメソッドは、
rdeadline_delta
やwdeadline_delta
が設定されている場合、現在の時刻にそのデルタを加算してrdeadline
やwdeadline
を設定します。 os.EAGAIN
エラーが返された場合、pollserver.WaitRead(fd)
またはpollserver.WaitWrite(fd)
を呼び出してイベントの発生を待機しますが、この待機は設定された期限時刻までとなります。期限が切れた場合、fd.rdeadline >= 0
またはfd.wdeadline >= 0
の条件が満たされなくなり、ループを抜けてos.EAGAIN
エラーを返します。
- これらのメソッドは、
Conn
インターフェースのSetReadTimeout
とSetWriteTimeout
: これらのメソッドは、以前はOSのソケットオプション(SO_RCVTIMEO
,SO_SNDTIMEO
)を直接設定していましたが、このコミットからはnetFD
のrdeadline_delta
とwdeadline_delta
を設定するように変更されました。これにより、Goランタイムがタイムアウトをより細かく制御できるようになります。
2. 並行書き込み時のロック機構
netFD
構造体へのミューテックス追加:src/lib/net/fd.go
のnetFD
構造体にrio sync.Mutex
とwio sync.Mutex
が追加されました。これらはそれぞれ読み込みと書き込み操作を保護するためのミューテックスです。netFD.Read
とnetFD.Write
でのロック:netFD.Read
とnetFD.Write
メソッドの冒頭でそれぞれのミューテックスをロックし、メソッドの終了時にdefer
を使ってアンロックするように変更されました。これにより、複数のゴルーチンが同時に同じnetFD
に対して読み込みや書き込みを行おうとした場合でも、操作が直列化され、データがインターリーブされるのを防ぎます。
3. Conn
インターフェースのドキュメント強化
src/lib/net/net.go
のConn
インターフェースの定義において、各メソッド(Read
,Write
,Close
,ReadFrom
,WriteTo
,SetReadBuffer
,SetWriteBuffer
,SetTimeout
,SetReadTimeout
,SetWriteTimeout
,SetLinger
,SetReuseAddr
,SetDontRoute
,SetKeepAlive
,BindToDevice
)に対して、その機能、引数、戻り値、および特定の振る舞いに関する詳細なコメントが追加されました。これは、Goのドキュメント生成ツールmkdoc.pl
が各メソッドのドキュメントを自動生成できるようにするための準備でもありました。
4. syscall
パッケージの変更
src/lib/syscall/socket_darwin.go
とsrc/lib/syscall/socket_linux.go
において、Setsockopt_linger
関数のロジックが変更されました。以前はsec != 0
の場合にLinger
構造体のYes
フィールドを1
に設定していましたが、このコミットからはsec >= 0
の場合に設定するように変更されました。これにより、sec == 0
の場合(即座に接続を閉じて未送信データを破棄する)も適切にSO_LINGER
オプションが設定されるようになります。
コアとなるコードの変更箇所
src/lib/net/fd.go
type netFD struct {
// ... 既存のフィールド ...
// owned by client
rdeadline_delta int64;
rdeadline int64;
rio sync.Mutex; // 読み込み操作を保護するミューテックス
wdeadline_delta int64;
wdeadline int64;
wio sync.Mutex; // 書き込み操作を保護するミューテックス
// ... 既存のフィールド ...
}
// Make reads/writes blocking; last gasp, so no error checking.
func setBlock(fd int64) {
flags, e := syscall.Fcntl(fd, syscall.F_GETFL, 0);
if e != 0 {
return;
}
syscall.Fcntl(fd, syscall.F_SETFL, flags & ^syscall.O_NONBLOCK);
}
type pollServer struct {
// ... 既存のフィールド ...
deadline int64; // next deadline (nsec since 1970)
}
func (s *pollServer) Now() int64 {
sec, nsec, err := os.Time();
if err != nil {
panic("net: os.Time: ", err.String());
}
nsec += sec * 1e9;
return nsec;
}
func (s *pollServer) CheckDeadlines() {
now := s.Now();
// ... 期限切れのFDをチェックし、WakeFDを呼び出すロジック ...
}
func (s *pollServer) Run() {
var scratch [100]byte;
for {
var t = s.deadline;
if t > 0 {
t = t - s.Now();
if t < 0 {
s.CheckDeadlines();
continue;
}
}
fd, mode, err := s.poll.WaitFD(t); // タイムアウト引数を渡す
// ... イベント処理ロジック ...
}
}
func (fd *netFD) Read(p []byte) (n int, err *os.Error) {
if fd == nil || fd.osfd == nil {
return -1, os.EINVAL
}
fd.rio.Lock(); // 読み込みロック
defer fd.rio.Unlock(); // 読み込みアンロック
if fd.rdeadline_delta > 0 {
fd.rdeadline = pollserver.Now() + fd.rdeadline_delta;
} else {
fd.rdeadline = 0;
}
n, err = fd.osfd.Read(p);
for err == os.EAGAIN && fd.rdeadline >= 0 { // タイムアウトチェックを追加
pollserver.WaitRead(fd);
n, err = fd.osfd.Read(p)
}
return n, err
}
func (fd *netFD) Write(p []byte) (n int, err *os.Error) {
if fd == nil || fd.osfd == nil {
return -1, os.EINVAL
}
fd.wio.Lock(); // 書き込みロック
defer fd.wio.Unlock(); // 書き込みアンロック
if fd.wdeadline_delta > 0 {
fd.wdeadline = pollserver.Now() + fd.wdeadline_delta;
} else {
fd.wdeadline = 0;
}
err = nil;
nn := 0;
for nn < len(p) {
n, err = fd.osfd.Write(p[nn:len(p)]);
if n > 0 {
nn += n
}
if nn == len(p) {
break;
}
if err == os.EAGAIN && fd.wdeadline >= 0 { // タイムアウトチェックを追加
pollserver.WaitWrite(fd);
continue;
}
if n == 0 || err != nil {
break;
}
}
return nn, err
}
src/lib/net/net.go
type Conn interface {
// Read blocks until data is ready from the connection
// and then reads into b. It returns the number
// of bytes read, or 0 if the connection has been closed.
Read(b []byte) (n int, err *os.Error);
// Write writes the data in b to the connection.
Write(b []byte) (n int, err *os.Error);
// Close closes the connection.
Close() *os.Error;
// For packet-based protocols such as UDP,
// ReadFrom reads the next packet from the network,
// returning the number of bytes read and the remote
// address that sent them.
ReadFrom(b []byte) (n int, addr string, err *os.Error);
// For packet-based protocols such as UDP,
// WriteTo writes the byte buffer b to the network
// as a single payload, sending it to the target address.
WriteTo(addr string, b []byte) (n int, err *os.Error);
// SetReadBuffer sets the size of the operating system's
// receive buffer associated with the connection.
SetReadBuffer(bytes int) *os.Error;
// SetReadBuffer sets the size of the operating system's
// transmit buffer associated with the connection.
SetWriteBuffer(bytes int) *os.Error;
// SetTimeout sets the read and write deadlines associated
// with the connection.
SetTimeout(nsec int64) *os.Error;
// SetReadTimeout sets the time (in nanoseconds) that
// Read will wait for data before returning os.EAGAIN.
// Setting nsec == 0 (the default) disables the deadline.
SetReadTimeout(nsec int64) *os.Error;
// SetWriteTimeout sets the time (in nanoseconds) that
// Write will wait to send its data before returning os.EAGAIN.
// Setting nsec == 0 (the default) disables the deadline.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
SetWriteTimeout(nsec int64) *os.Error;
// SetLinger sets the behavior of Close() on a connection
// which still has data waiting to be sent or to be acknowledged.
//
// If sec < 0 (the default), Close returns immediately and
// the operating system finishes sending the data in the background.
//
// If sec == 0, Close returns immediately and the operating system
// discards any unsent or unacknowledged data.
//
// If sec > 0, Close blocks for at most sec seconds waiting for
// data to be sent and acknowledged.
SetLinger(sec int) *os.Error;
// SetReuseAddr sets whether it is okay to reuse addresses
// from recent connections that were not properly closed.
SetReuseAddr(reuseaddr bool) *os.Error;
// SetDontRoute sets whether outgoing messages should
// bypass the system routing tables.
SetDontRoute(dontroute bool) *os.Error;
// SetKeepAlive sets whether the operating system should send
// keepalive messages on the connection.
SetKeepAlive(keepalive bool) *os.Error;
// BindToDevice binds a connection to a particular network device.
BindToDevice(dev string) *os.Error;
}
コアとなるコードの解説
タイムアウトの実装
GoのネットワークI/Oは、OSのノンブロッキングI/Oとイベント通知メカニズム(kqueue
やepoll
)を組み合わせて実装されています。以前は、SetReadTimeout
やSetWriteTimeout
がOSのソケットオプション(SO_RCVTIMEO
, SO_SNDTIMEO
)を直接設定していましたが、これにはいくつかの制限がありました。例えば、OSによってはミリ秒単位の精度しかなかったり、読み込みと書き込みで異なるタイムアウトを設定するのが難しかったりする場合があります。
このコミットでは、Goランタイム自身がタイムアウトを管理するようになりました。
netFD
構造体にrdeadline_delta
とwdeadline_delta
が追加され、これはユーザーが設定したタイムアウト期間(ナノ秒)を保持します。netFD.Read
やnetFD.Write
が呼び出されると、pollserver.Now()
で現在の時刻を取得し、rdeadline_delta
またはwdeadline_delta
を加算して、I/O操作の期限時刻(rdeadline
またはwdeadline
)を計算します。- I/O操作が
os.EAGAIN
(ノンブロッキングI/Oでデータがまだ準備できていないことを示す)を返した場合、pollserver.WaitRead(fd)
またはpollserver.WaitWrite(fd)
が呼び出されます。これらの関数は、内部的にpollServer
にI/Oイベントの待機を登録します。 pollServer
のRun
メソッドは、pollster.WaitFD(t)
を呼び出してOSのイベント通知メカニズムを待機します。ここでt
は、現在登録されているすべてのI/O操作の中で最も近い期限時刻までの残り時間です。これにより、OSレベルでタイムアウトを効率的に処理できます。pollServer.CheckDeadlines()
は、pollServer
のループ内で定期的に実行され、期限切れのI/O操作を特定し、それらのnetFD
のrdeadline
またはwdeadline
を-1
に設定します。これにより、netFD.Read
やnetFD.Write
のループが終了し、os.EAGAIN
エラーが返されることで、アプリケーションにタイムアウトが通知されます。
このアプローチにより、GoはOSのソケットオプションに依存することなく、より高精度で柔軟なタイムアウト制御を実現しています。
並行書き込み時のロック
netFD
構造体に追加されたsync.Mutex
型のrio
とwio
は、それぞれ読み込みと書き込み操作を保護するためのものです。
netFD.Read
メソッドの冒頭でfd.rio.Lock()
が呼び出され、読み込み操作が完了するまで他のゴルーチンが同じnetFD
に対して読み込みを行うのをブロックします。- 同様に、
netFD.Write
メソッドの冒頭でfd.wio.Lock()
が呼び出され、書き込み操作が完了するまで他のゴルーチンが同じnetFD
に対して書き込みを行うのをブロックします。
これにより、複数のゴルーチンが同時に同じネットワーク接続に対して書き込みを行っても、それらの書き込みがインターリーブされることなく、データが正しく送信されることが保証されます。これは、TCPのようなストリーム指向のプロトコルにおいて、アプリケーションレベルでのデータ整合性を保つ上で非常に重要です。
Conn
インターフェースのドキュメント強化
src/lib/net/net.go
におけるConn
インターフェースの各メソッドへの詳細なコメント追加は、Goの標準ライブラリの品質と使いやすさを向上させるための重要なステップです。これらのコメントは、各メソッドの目的、引数、戻り値、および特定の振る舞いについて明確な説明を提供します。これにより、開発者はConn
インターフェースをより正確に理解し、適切に使用できるようになります。また、これはGoのドキュメント生成ツールが自動的に高品質なAPIドキュメントを生成するための基盤となります。
関連リンク
- Go言語の初期のネットワークパッケージに関する議論や設計ドキュメント(当時のGoコミュニティのメーリングリストやデザインドキュメントを検索すると、より深い背景情報が見つかる可能性があります)。
kqueue
とepoll
に関するOSのドキュメントや解説記事。- Go言語の
sync
パッケージとミューテックスに関する公式ドキュメント。
参考にした情報源リンク
- Go言語のソースコード(特に
src/net
およびsrc/syscall
パッケージの関連ファイル)。 - Go言語の公式ドキュメント(当時のバージョンに遡って確認できる場合)。
- Unix系OSのシステムコールに関するドキュメント(
fcntl
,kqueue
,epoll
など)。 - 並行プログラミングにおけるミューテックスの概念に関する一般的な情報源。
- Go言語の初期のコミット履歴と関連するコードレビュー。
[インデックス 1779] ファイルの概要
このコミットは、Go言語のネットワークパッケージ(net
)における重要な改善を含んでいます。主に、ネットワークI/O操作におけるタイムアウト機能の導入と、並行書き込み時のデータ破損を防ぐためのロック機構の追加、そしてConn
インターフェースのドキュメント強化に焦点を当てています。これにより、ネットワーク通信の信頼性と堅牢性が向上し、開発者がより予測可能なネットワークアプリケーションを構築できるようになります。
コミット
commit 1e37e8a417dc36bc6da6828cd7c20dd53d4ba6a9
Author: Russ Cox <rsc@golang.org>
Date: Fri Mar 6 17:51:31 2009 -0800
document Conn interface better, in preparation
for per-method interface documentation
by mkdoc.pl.
implement timeouts on network reads
and use them in dns client.
also added locks on i/o to ensure writes
are not interlaced.
R=r
DELTA=340 (272 added, 25 deleted, 43 changed)
OCL=25799
CL=25874
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1e37e8a417dc36bc6da6828cd7c20dd53d4ba6a9
元コミット内容
このコミットの元の内容は以下の通りです。
Conn
インターフェースのドキュメントを改善し、mkdoc.pl
によるメソッドごとのインターフェースドキュメント生成に備える。- ネットワーク読み込みにタイムアウトを実装し、DNSクライアントでそれを使用する。
- 書き込みが混在しないように、I/Oにロックを追加する。
変更の背景
このコミットが行われた2009年3月は、Go言語がまだ公開されて間もない、非常に初期の段階でした。当時のGoのネットワークスタックは基本的な機能を提供していましたが、実用的なアプリケーション開発にはいくつかの課題がありました。
- タイムアウト機能の欠如: ネットワークI/O操作(特に読み込み)にタイムアウトが設定されていない場合、ネットワークの遅延や相手からの応答がない場合に、アプリケーションが無限にブロックされる可能性がありました。これは、応答性の高いサービスや堅牢なクライアントを構築する上で大きな問題となります。DNSクライアントのような、外部サービスとの通信を伴うコンポーネントでは、タイムアウトは必須の機能です。
- 並行書き込み時のデータ破損: 複数のゴルーチンが同時に同じネットワーク接続に書き込みを行う場合、書き込み操作がインターリーブ(混在)し、データが破損する可能性がありました。これは、TCPのようなストリーム指向のプロトコルでは特に問題となり、アプリケーションレベルでの同期が必要とされます。
- ドキュメントの不足:
Conn
インターフェースはGoのネットワークプログラミングの根幹をなすものであり、その各メソッドの振る舞いや期待される動作について、より詳細なドキュメントが必要とされていました。これは、Goの標準ライブラリの品質向上と、開発者による適切な利用を促進するために不可欠でした。
これらの課題に対処するため、このコミットではネットワークI/Oの信頼性、堅牢性、および使いやすさを向上させるための重要な変更が導入されました。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念について基本的な知識が必要です。
1. ネットワークI/Oとブロッキング/ノンブロッキングI/O
- ブロッキングI/O: I/O操作が完了するまで、呼び出し元のスレッド(またはゴルーチン)がブロックされる(待機する)方式です。シンプルですが、応答性が低下する可能性があります。
- ノンブロッキングI/O: I/O操作がすぐに戻り、データが利用可能でない場合や書き込みバッファが満杯の場合にはエラー(例:
EAGAIN
やEWOULDBLOCK
)を返します。これにより、アプリケーションはI/O操作の完了を待つ間に他の処理を行うことができます。Goのネットワークパッケージは内部的にノンブロッキングI/Oとイベント通知メカニズム(kqueue
やepoll
)を組み合わせて、効率的な並行I/Oを実現しています。
2. タイムアウト
ネットワーク通信において、特定の操作(読み込み、書き込み、接続確立など)が指定された時間内に完了しない場合に、その操作を中断しエラーを返す機能です。これにより、アプリケーションが無限に待機するのを防ぎ、リソースの枯渇や応答性の低下を防ぐことができます。
3. kqueue
とepoll
これらは、Unix系OSにおける効率的なI/Oイベント通知メカニズムです。
kqueue
(FreeBSD, macOSなど): 多数のファイルディスクリプタ(ソケットなど)からのイベント(読み込み可能、書き込み可能など)を効率的に監視するためのシステムコールです。epoll
(Linux):kqueue
と同様に、多数のファイルディスクリプタからのイベントを効率的に監視するためのLinux固有のシステムコールです。 Goのネットワークパッケージは、これらのOS固有のメカニズムを抽象化し、クロスプラットフォームで動作するノンブロッキングI/Oを提供しています。
4. ミューテックス (sync.Mutex
)
並行プログラミングにおいて、共有リソースへのアクセスを同期するためのメカニズムです。ミューテックスは、一度に一つのゴルーチンだけが特定のコードセクション(クリティカルセクション)を実行できるようにすることで、データ競合を防ぎます。このコミットでは、ネットワーク書き込み操作がインターリーブされるのを防ぐために使用されています。
5. os.EAGAIN
Unix系システムコールがノンブロッキングモードで実行され、要求された操作(読み込みや書き込み)がすぐに完了できない場合に返されるエラーコードです。これは、操作が失敗したことを意味するのではなく、後で再試行する必要があることを示します。Goのネットワークパッケージでは、このエラーを受け取った際に、pollServer
を通じてイベントの発生を待機し、I/O操作を再試行します。
技術的詳細
このコミットの技術的な変更は、主に以下の3つの柱に基づいています。
1. ネットワークI/Oタイムアウトの実装
netFD
構造体の拡張:src/lib/net/fd.go
において、netFD
構造体にrdeadline_delta
(読み込みタイムアウト期間)、rdeadline
(読み込み期限時刻)、wdeadline_delta
(書き込みタイムアウト期間)、wdeadline
(書き込み期限時刻)が追加されました。これらはナノ秒単位で管理されます。pollServer
のタイムアウト処理:pollServer
は、ノンブロッキングI/Oのイベントを監視し、I/O操作が準備できた際にゴルーチンを再開する役割を担っています。このコミットでは、pollServer
にdeadline
フィールドが追加され、監視対象のファイルディスクリプタの中で最も近い期限時刻が設定されます。pollServer.Now()
: 現在時刻をナノ秒単位で取得するヘルパー関数が追加されました。pollServer.CheckDeadlines()
:pollServer
のRun
ループ内で定期的に呼び出され、期限切れのI/O操作をチェックし、該当するnetFD
のrdeadline
またはwdeadline
を-1
に設定してタイムアウト状態を示します。pollster.WaitFD(nsec int64)
: OS固有のI/O多重化メカニズム(kqueue
やepoll
)の待機関数にタイムアウト引数(nsec
)が追加されました。これにより、指定された時間内にイベントが発生しない場合、待機が中断されます。
netFD.Read
とnetFD.Write
の変更:- これらのメソッドは、
rdeadline_delta
やwdeadline_delta
が設定されている場合、現在の時刻にそのデルタを加算してrdeadline
やwdeadline
を設定します。 os.EAGAIN
エラーが返された場合、pollserver.WaitRead(fd)
またはpollserver.WaitWrite(fd)
を呼び出してイベントの発生を待機しますが、この待機は設定された期限時刻までとなります。期限が切れた場合、fd.rdeadline >= 0
またはfd.wdeadline >= 0
の条件が満たされなくなり、ループを抜けてos.EAGAIN
エラーを返します。
- これらのメソッドは、
Conn
インターフェースのSetReadTimeout
とSetWriteTimeout
: これらのメソッドは、以前はOSのソケットオプション(SO_RCVTIMEO
,SO_SNDTIMEO
)を直接設定していましたが、このコミットからはnetFD
のrdeadline_delta
とwdeadline_delta
を設定するように変更されました。これにより、Goランタイムがタイムアウトをより細かく制御できるようになります。
2. 並行書き込み時のロック機構
netFD
構造体へのミューテックス追加:src/lib/net/fd.go
のnetFD
構造体にrio sync.Mutex
とwio sync.Mutex
が追加されました。これらはそれぞれ読み込みと書き込み操作を保護するためのミューテックスです。netFD.Read
とnetFD.Write
でのロック:netFD.Read
とnetFD.Write
メソッドの冒頭でそれぞれのミューテックスをロックし、メソッドの終了時にdefer
を使ってアンロックするように変更されました。これにより、複数のゴルーチンが同時に同じnetFD
に対して読み込みや書き込みを行おうとした場合でも、操作が直列化され、データがインターリーブされるのを防ぎます。
3. Conn
インターフェースのドキュメント強化
src/lib/net/net.go
のConn
インターフェースの定義において、各メソッド(Read
,Write
,Close
,ReadFrom
,WriteTo
,SetReadBuffer
,SetWriteBuffer
,SetTimeout
,SetReadTimeout
,SetWriteTimeout
,SetLinger
,SetReuseAddr
,SetDontRoute
,SetKeepAlive
,BindToDevice
)に対して、その機能、引数、戻り値、および特定の振る舞いに関する詳細なコメントが追加されました。これは、Goのドキュメント生成ツールmkdoc.pl
が各メソッドのドキュメントを自動生成できるようにするための準備でもありました。
4. syscall
パッケージの変更
src/lib/syscall/socket_darwin.go
とsrc/lib/syscall/socket_linux.go
において、Setsockopt_linger
関数のロジックが変更されました。以前はsec != 0
の場合にLinger
構造体のYes
フィールドを1
に設定していましたが、このコミットからはsec >= 0
の場合に設定するように変更されました。これにより、sec == 0
の場合(即座に接続を閉じて未送信データを破棄する)も適切にSO_LINGER
オプションが設定されるようになります。
コアとなるコードの変更箇所
src/lib/net/fd.go
type netFD struct {
// ... 既存のフィールド ...
// owned by client
rdeadline_delta int64;
rdeadline int64;
rio sync.Mutex; // 読み込み操作を保護するミューテックス
wdeadline_delta int64;
wdeadline int64;
wio sync.Mutex; // 書き込み操作を保護するミューテックス
// ... 既存のフィールド ...
}
// Make reads/writes blocking; last gasp, so no error checking.
func setBlock(fd int64) {
flags, e := syscall.Fcntl(fd, syscall.F_GETFL, 0);
if e != 0 {
return;
}
syscall.Fcntl(fd, syscall.F_SETFL, flags & ^syscall.O_NONBLOCK);
}
type pollServer struct {
// ... 既存のフィールド ...
deadline int64; // next deadline (nsec since 1970)
}
func (s *pollServer) Now() int64 {
sec, nsec, err := os.Time();
if err != nil {
panic("net: os.Time: ", err.String());
}
nsec += sec * 1e9;
return nsec;
}
func (s *pollServer) CheckDeadlines() {
now := s.Now();
// ... 期限切れのFDをチェックし、WakeFDを呼び出すロジック ...
}
func (s *pollServer) Run() {
var scratch [100]byte;
for {
var t = s.deadline;
if t > 0 {
t = t - s.Now();
if t < 0 {
s.CheckDeadlines();
continue;
}
}
fd, mode, err := s.poll.WaitFD(t); // タイムアウト引数を渡す
// ... イベント処理ロジック ...
}
}
func (fd *netFD) Read(p []byte) (n int, err *os.Error) {
if fd == nil || fd.osfd == nil {
return -1, os.EINVAL
}
fd.rio.Lock(); // 読み込みロック
defer fd.rio.Unlock(); // 読み込みアンロック
if fd.rdeadline_delta > 0 {
fd.rdeadline = pollserver.Now() + fd.rdeadline_delta;
} else {
fd.rdeadline = 0;
}
n, err = fd.osfd.Read(p);
for err == os.EAGAIN && fd.rdeadline >= 0 { // タイムアウトチェックを追加
pollserver.WaitRead(fd);
n, err = fd.osfd.Read(p)
}
return n, err
}
func (fd *netFD) Write(p []byte) (n int, err *os.Error) {
if fd == nil || fd.osfd == nil {
return -1, os.EINVAL
}
fd.wio.Lock(); // 書き込みロック
defer fd.wio.Unlock(); // 書き込みアンロック
if fd.wdeadline_delta > 0 {
fd.wdeadline = pollserver.Now() + fd.wdeadline_delta;
} else {
fd.wdeadline = 0;
}
err = nil;
nn := 0;
for nn < len(p) {
n, err = fd.osfd.Write(p[nn:len(p)]);
if n > 0 {
nn += n
}
if nn == len(p) {
break;
}
if err == os.EAGAIN && fd.wdeadline >= 0 { // タイムアウトチェックを追加
pollserver.WaitWrite(fd);
continue;
}
if n == 0 || err != nil {
break;
}
}
return nn, err
}
src/lib/net/net.go
type Conn interface {
// Read blocks until data is ready from the connection
// and then reads into b. It returns the number
// of bytes read, or 0 if the connection has been closed.
Read(b []byte) (n int, err *os.Error);
// Write writes the data in b to the connection.
Write(b []byte) (n int, err *os.Error);
// Close closes the connection.
Close() *os.Error;
// For packet-based protocols such as UDP,
// ReadFrom reads the next packet from the network,
// returning the number of bytes read and the remote
// address that sent them.
ReadFrom(b []byte) (n int, addr string, err *os.Error);
// For packet-based protocols such as UDP,
// WriteTo writes the byte buffer b to the network
// as a single payload, sending it to the target address.
WriteTo(addr string, b []byte) (n int, err *os.Error);
// SetReadBuffer sets the size of the operating system's
// receive buffer associated with the connection.
SetReadBuffer(bytes int) *os.Error;
// SetReadBuffer sets the size of the operating system's
// transmit buffer associated with the connection.
SetWriteBuffer(bytes int) *os.Error;
// SetTimeout sets the read and write deadlines associated
// with the connection.
SetTimeout(nsec int64) *os.Error;
// SetReadTimeout sets the time (in nanoseconds) that
// Read will wait for data before returning os.EAGAIN.
// Setting nsec == 0 (the default) disables the deadline.
SetReadTimeout(nsec int64) *os.Error;
// SetWriteTimeout sets the time (in nanoseconds) that
// Write will wait to send its data before returning os.EAGAIN.
// Setting nsec == 0 (the default) disables the deadline.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
SetWriteTimeout(nsec int64) *os.Error;
// SetLinger sets the behavior of Close() on a connection
// which still has data waiting to be sent or to be acknowledged.
//
// If sec < 0 (the default), Close returns immediately and
// the operating system finishes sending the data in the background.
//
// If sec == 0, Close returns immediately and the operating system
// discards any unsent or unacknowledged data.
//
// If sec > 0, Close blocks for at most sec seconds waiting for
// data to be sent and acknowledged.
SetLinger(sec int) *os.Error;
// SetReuseAddr sets whether it is okay to reuse addresses
// from recent connections that were not properly closed.
SetReuseAddr(reuseaddr bool) *os.Error;
// SetDontRoute sets whether outgoing messages should
// bypass the system routing tables.
SetDontRoute(dontroute bool) *os.Error;
// SetKeepAlive sets whether the operating system should send
// keepalive messages on the connection.
SetKeepAlive(keepalive bool) *os.Error;
// BindToDevice binds a connection to a particular network device.
BindToDevice(dev string) *os.Error;
}
コアとなるコードの解説
タイムアウトの実装
GoのネットワークI/Oは、OSのノンブロッキングI/Oとイベント通知メカニズム(kqueue
やepoll
)を組み合わせて実装されています。以前は、SetReadTimeout
やSetWriteTimeout
がOSのソケットオプション(SO_RCVTIMEO
, SO_SNDTIMEO
)を直接設定していましたが、これにはいくつかの制限がありました。例えば、OSによってはミリ秒単位の精度しかなかったり、読み込みと書き込みで異なるタイムアウトを設定するのが難しかったりする場合があります。
このコミットでは、Goランタイム自身がタイムアウトを管理するようになりました。
netFD
構造体にrdeadline_delta
とwdeadline_delta
が追加され、これはユーザーが設定したタイムアウト期間(ナノ秒)を保持します。netFD.Read
やnetFD.Write
が呼び出されると、pollserver.Now()
で現在の時刻を取得し、rdeadline_delta
またはwdeadline_delta
を加算して、I/O操作の期限時刻(rdeadline
またはwdeadline
)を計算します。- I/O操作が
os.EAGAIN
(ノンブロッキングI/Oでデータがまだ準備できていないことを示す)を返した場合、pollserver.WaitRead(fd)
またはpollserver.WaitWrite(fd)
が呼び出されます。これらの関数は、内部的にpollServer
にI/Oイベントの待機を登録します。 pollServer
のRun
メソッドは、pollster.WaitFD(t)
を呼び出してOSのイベント通知メカニズムを待機します。ここでt
は、現在登録されているすべてのI/O操作の中で最も近い期限時刻までの残り時間です。これにより、OSレベルでタイムアウトを効率的に処理できます。pollServer.CheckDeadlines()
は、pollServer
のループ内で定期的に実行され、期限切れのI/O操作を特定し、それらのnetFD
のrdeadline
またはwdeadline
を-1
に設定します。これにより、netFD.Read
やnetFD.Write
のループが終了し、os.EAGAIN
エラーが返されることで、アプリケーションにタイムアウトが通知されます。
このアプローチにより、GoはOSのソケットオプションに依存することなく、より高精度で柔軟なタイムアウト制御を実現しています。
並行書き込み時のロック
netFD
構造体に追加されたsync.Mutex
型のrio
とwio
は、それぞれ読み込みと書き込み操作を保護するためのものです。
netFD.Read
メソッドの冒頭でfd.rio.Lock()
が呼び出され、読み込み操作が完了するまで他のゴルーチンが同じnetFD
に対して読み込みを行うのをブロックします。- 同様に、
netFD.Write
メソッドの冒頭でfd.wio.Lock()
が呼び出され、書き込み操作が完了するまで他のゴルーチンが同じnetFD
に対して書き込みを行うのをブロックします。
これにより、複数のゴルーチンが同時に同じネットワーク接続に対して書き込みを行っても、それらの書き込みがインターリーブされることなく、データが正しく送信されることが保証されます。これは、TCPのようなストリーム指向のプロトコルにおいて、アプリケーションレベルでのデータ整合性を保つ上で非常に重要です。
Conn
インターフェースのドキュメント強化
src/lib/net/net.go
におけるConn
インターフェースの各メソッドへの詳細なコメント追加は、Goの標準ライブラリの品質と使いやすさを向上させるための重要なステップです。これらのコメントは、各メソッドの目的、引数、戻り値、および特定の振る舞いについて明確な説明を提供します。これにより、開発者はConn
インターフェースをより正確に理解し、適切に使用できるようになります。また、これはGoのドキュメント生成ツールが自動的に高品質なAPIドキュメントを生成するための基盤となります。
関連リンク
- Go言語の初期のネットワークパッケージに関する議論や設計ドキュメント(当時のGoコミュニティのメーリングリストやデザインドキュメントを検索すると、より深い背景情報が見つかる可能性があります)。
kqueue
とepoll
に関するOSのドキュメントや解説記事。- Go言語の
sync
パッケージとミューテックスに関する公式ドキュメント。
参考にした情報源リンク
- Go言語のソースコード(特に
src/net
およびsrc/syscall
パッケージの関連ファイル)。 - Go言語の公式ドキュメント(当時のバージョンに遡って確認できる場合)。
- Unix系OSのシステムコールに関するドキュメント(
fcntl
,kqueue
,epoll
など)。 - 並行プログラミングにおけるミューテックスの概念に関する一般的な情報源。
- Go言語の初期のコミット履歴と関連するコードレビュー。
- Go言語のネットワークI/Oタイムアウトに関する初期の実装と進化についての情報:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEXgMDoyn8V9mpDnHajTa9AG1MxJCq8S0PbBgB8_EPwsJwzduIpAZo8qZjBQW5MOA1BT2BunX3w8MpdTVvnE5w0mWEtShqqHFx_WlPGvXWsWlrtmDVH3Ohy0iCRQXBqXwDUb1N-2BW0UgFUyFaJBnsYFfGwT_u3L3trJND5hgHAd6OLQFrhfQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHiiozN-Kc5yGUxxmiB6NcUngPTkGf1LRAE0Ew6Xj1TdDpHJ3F5WoUZgtiXE0YF3-TgJBCK0CrVtOuQNYdDM0vyr4ObD4MRMSsvQsEPydl-ds_2IuIw6Nc747RgjiWuDxT7gU0jdjh-fKNbbGy7JFh2alviXeJI_v2T8GjJmVCmANBPmFA1wZN7
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEervIa3JqVeuPFSXo8MlBTAdrTphuOO5POUwBLYQx49dlK6eOr-BVHg6livR-61y02Gr1aMJThq67-FP9H_CbGASPkLtjRQ3D8h1btKmrGtAK1FKcI1hQQal4lESo92yl5vAwyaM7hBJmdHZu80NVZsVxY45QTJxLmwGjexXu4XDVljiVMunFxNg==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEnNnUlPYunvBfiIfxsJX6KuI4Fyb4__oM21W0cT6tr9WE9YFuhoW5z9wEC0r-4R1jHcbeU9hO7mUZ08T6gH_BREp0rtOXmuUvldIE7HZVi7d8Gq9zEJJ4XjYdf7lLh-3RpBA1uoGlLZYVT3awAcxre4rHqq_m55hV-QsbvN1nO