[インデックス 11362] ファイルの概要
コミット
net: actually reset deadline when time is zero
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fe30ed2dcf2392f50b9305863d73fe2909567b8d
元コミット内容
net: actually reset deadline when time is zero
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5570056
変更の背景
このコミットは、Go言語のnet
パッケージにおけるネットワーク接続のデッドライン(期限)設定に関するバグ修正です。以前のバージョンでは、net.Conn
インターフェースのSetReadDeadline
やSetWriteDeadline
メソッドにtime.Time{}
(ゼロ値のtime.Time
)を渡しても、期待通りにデッドラインがリセットされず、既存のデッドラインが有効なままになってしまう問題がありました。
Goのtime.Time
型において、ゼロ値は「時刻が設定されていない」状態、または「デッドラインがない」状態を意味することが一般的です。ネットワーク操作においてデッドラインをリセットするということは、その操作が完了するまでの時間制限をなくすことを意味します。しかし、このバグにより、ゼロ値のtime.Time
が渡された際に、内部的にデッドラインが正しく0
(無期限)に設定されず、結果としてデッドラインが意図せず残ってしまう可能性がありました。
このコミットは、この挙動を修正し、time.Time{}
が渡された場合には明示的にデッドラインをリセットするように変更することで、開発者が期待するデッドライン管理のセマンティクスを保証することを目的としています。
前提知識の解説
Go言語のnet
パッケージ
Go言語のnet
パッケージは、ネットワークI/Oプリミティブへのポータブルなインターフェースを提供します。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うための機能が含まれています。このパッケージの主要なインターフェースの一つにnet.Conn
があり、これはネットワーク接続の汎用的なインターフェースを定義しています。
ネットワークデッドライン(Deadlines)
ネットワークプログラミングにおいて、デッドラインはI/O操作(読み込み、書き込み、接続受け入れなど)が完了するまでの時間制限を設定するメカニズムです。デッドラインを設定することで、ネットワークの遅延や相手側の応答がない場合に、操作が無限にブロックされるのを防ぎ、アプリケーションの応答性を向上させることができます。
net.Conn
インターフェースには以下のデッドライン関連メソッドがあります。
SetReadDeadline(t time.Time) error
: 読み込み操作のデッドラインを設定します。SetWriteDeadline(t time.Time) error
: 書き込み操作のデッドラインを設定します。SetDeadline(t time.Time) error
: 読み込みと書き込みの両方のデッドラインを設定します。
これらのメソッドにtime.Time{}
(ゼロ値のtime.Time
)を渡すと、対応するデッドラインがリセットされ、操作は無期限にブロックされる可能性があります(ただし、他の要因によるタイムアウトは発生しうる)。
time.Time
型とIsZero()
メソッド
Go言語のtime
パッケージは、時刻と期間を扱うための機能を提供します。time.Time
型は特定の時点を表します。
time.Time
型のゼロ値は、time.Time{}
で表現され、これは「1年1月1日午前0時0分0秒 UTC」という特定の時刻を表します。しかし、文脈によっては「時刻が設定されていない」または「無効な時刻」として扱われることがあります。
time.Time
型にはIsZero()
メソッドがあり、これはそのtime.Time
がゼロ値である場合にtrue
を返します。このメソッドは、時刻が明示的に設定されているかどうかをチェックする際に便利です。
UnixNano()
メソッド
time.Time
型にはUnixNano()
メソッドがあり、これはその時刻をUnixエポック(1970年1月1日UTC)からの経過ナノ秒数として返します。内部的にデッドラインをナノ秒単位で管理する際に使用されます。デッドラインをリセットする際には、このナノ秒値が0
に設定されることが期待されます。
技術的詳細
このコミットの核心は、net
パッケージ内部でデッドラインを管理するsetReadDeadline
とsetWriteDeadline
関数が、time.Time
のゼロ値を正しく解釈し、デッドラインをリセットするように変更された点です。
以前の実装では、fd.rdeadline = t.UnixNano()
やfd.wdeadline = t.UnixNano()
のように、引数t
のUnixNano()
値を直接内部のデッドライン変数に代入していました。time.Time{}
のUnixNano()
値は0
ではない(実際には非常に小さい負の値、またはプラットフォームによって異なるが、通常は0
ではない)ため、この直接代入ではデッドラインが0
(無期限)に設定されず、以前のデッドラインがそのまま残ってしまうという問題がありました。
修正後のコードでは、t.IsZero()
を使って引数t
がゼロ値であるかを明示的にチェックしています。
- もし
t.IsZero()
がtrue
であれば、fd.rdeadline
またはfd.wdeadline
を明示的に0
に設定します。これにより、デッドラインが正しくリセットされ、I/O操作が無期限にブロックされるようになります(他の要因がない限り)。 - もし
t.IsZero()
がfalse
であれば、これまで通りt.UnixNano()
の値をデッドラインに設定します。
この変更により、SetReadDeadline(time.Time{})
やSetWriteDeadline(time.Time{})
を呼び出すことで、期待通りにデッドラインが解除されるようになりました。
また、この変更を検証するために、TestDeadlineReset
という新しいテストケースが追加されました。このテストは、TCPListener
のデッドラインを設定し、その後time.Time{}
でリセットし、Accept
操作がブロックされることを確認します。もしデッドラインが正しくリセットされていなければ、Accept
はタイムアウトしてエラーを返すはずですが、テストではタイムアウトしないことを期待しています。
コアとなるコードの変更箇所
変更は主に以下の2つのファイルで行われています。
-
src/pkg/net/sockopt.go
:setReadDeadline
関数とsetWriteDeadline
関数に、time.Time
のゼロ値チェックとデッドラインのリセットロジックが追加されました。
--- a/src/pkg/net/sockopt.go +++ b/src/pkg/net/sockopt.go @@ -117,12 +117,20 @@ func setWriteBuffer(fd *netFD, bytes int) error { }\n func setReadDeadline(fd *netFD, t time.Time) error { -\tfd.rdeadline = t.UnixNano()\n +\tif t.IsZero() {\n +\t\tfd.rdeadline = 0\n +\t} else {\n +\t\tfd.rdeadline = t.UnixNano()\n +\t}\n \treturn nil }\n func setWriteDeadline(fd *netFD, t time.Time) error { -\tfd.wdeadline = t.UnixNano()\n +\tif t.IsZero() {\n +\t\tfd.wdeadline = 0\n +\t} else {\n +\t\tfd.wdeadline = t.UnixNano()\n +\t}\n \treturn nil }\n
-
src/pkg/net/timeout_test.go
:TestDeadlineReset
という新しいテスト関数が追加されました。
--- a/src/pkg/net/timeout_test.go +++ b/src/pkg/net/timeout_test.go @@ -77,3 +77,30 @@ func TestTimeoutTCP(t *testing.T) { \ttestTimeout(t, \"tcp\", addr, false)\n \t<-done\n }\n +\n +func TestDeadlineReset(t *testing.T) {\n +\tif runtime.GOOS == \"plan9\" {\n +\t\treturn\n +\t}\n +\tln, err := Listen(\"tcp\", \"127.0.0.1:0\")\n +\tif err != nil {\n +\t\tt.Fatal(err)\n +\t}\n +\tdefer ln.Close()\n +\ttl := ln.(*TCPListener)\n +\ttl.SetDeadline(time.Now().Add(1 * time.Minute))\n +\ttl.SetDeadline(time.Time{}) // reset it\n +\terrc := make(chan error, 1)\n +\tgo func() {\n +\t\t_, err := ln.Accept()\n +\t\terrc <- err\n +\t}()\n +\tselect {\n +\tcase <-time.After(50 * time.Millisecond):\n +\t\t// Pass.\n +\tcase err := <-errc:\n +\t\t// Accept should never return; we never\n +\t\t// connected to it.\n +\t\tt.Errorf(\"unexpected return from Accept; err=%v\", err)\n +\t}\n +}\n
コアとなるコードの解説
src/pkg/net/sockopt.go
の変更
setReadDeadline
とsetWriteDeadline
関数は、それぞれ読み込みと書き込みのデッドラインを内部的に設定する役割を担っています。
変更前は、引数t
(time.Time
型)のUnixNano()
値をそのままfd.rdeadline
またはfd.wdeadline
に代入していました。しかし、time.Time{}
のUnixNano()
は0
ではないため、デッドラインをリセットする意図でtime.Time{}
が渡されても、内部のデッドライン値が0
にならず、結果としてデッドラインが解除されないという問題がありました。
修正後は、if t.IsZero()
という条件分岐が追加されました。
t.IsZero()
がtrue
の場合、つまりtime.Time{}
が渡された場合は、デッドラインを明示的に0
に設定します。内部的に0
は「デッドラインなし」を意味します。t.IsZero()
がfalse
の場合、つまり有効な時刻が渡された場合は、これまで通りt.UnixNano()
の値をデッドラインに設定します。
この変更により、time.Time{}
を渡すことでデッドラインが確実にリセットされるようになり、APIのセマンティクスが明確になりました。
src/pkg/net/timeout_test.go
の追加テスト
TestDeadlineReset
は、この修正が正しく機能することを検証するためのテストです。
- まず、
TCPListener
を作成し、一時的にデッドラインを設定します(time.Now().Add(1 * time.Minute)
)。 - 次に、
tl.SetDeadline(time.Time{})
を呼び出して、デッドラインをリセットします。これがこのテストの肝となる部分です。 - ゴルーチン内で
ln.Accept()
を呼び出します。もしデッドラインが正しくリセットされていれば、Accept
は接続が来るまでブロックされ続けるはずです。 - メインゴルーチンでは、
time.After(50 * time.Millisecond)
を使って短い時間待機します。 select
文を使って、Accept
がエラーを返さないこと、つまりブロックされ続けることを確認します。もしAccept
がタイムアウトしてエラーを返した場合(デッドラインがリセットされていない場合)、テストは失敗します。
このテストは、デッドラインがtime.Time{}
によって正しくリセットされ、ネットワーク操作が無期限にブロックされる(タイムアウトしない)という期待される挙動を保証します。
関連リンク
- Go CL (Code Review) 5570056: https://golang.org/cl/5570056
参考にした情報源リンク
- Go
net
package documentation: https://pkg.go.dev/net - Go
time
package documentation: https://pkg.go.dev/time - Go
time.Time.IsZero()
documentation: https://pkg.go.dev/time#Time.IsZero - Go
time.Time.UnixNano()
documentation: https://pkg.go.dev/time#Time.UnixNano - Stack Overflow discussions on Go network deadlines and
time.Time{}
(general knowledge, no specific link used but informed understanding). - Go issue tracker (general knowledge, no specific issue link used but informed understanding of common Go issues).# [インデックス 11362] ファイルの概要
コミット
net: actually reset deadline when time is zero
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fe30ed2dcf2392f50b9305863d73fe2909567b8d
元コミット内容
net: actually reset deadline when time is zero
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5570056
変更の背景
このコミットは、Go言語のnet
パッケージにおけるネットワーク接続のデッドライン(期限)設定に関するバグ修正です。以前のバージョンでは、net.Conn
インターフェースのSetReadDeadline
やSetWriteDeadline
メソッドにtime.Time{}
(ゼロ値のtime.Time
)を渡しても、期待通りにデッドラインがリセットされず、既存のデッドラインが有効なままになってしまう問題がありました。
Goのtime.Time
型において、ゼロ値は「時刻が設定されていない」状態、または「デッドラインがない」状態を意味することが一般的です。ネットワーク操作においてデッドラインをリセットするということは、その操作が完了するまでの時間制限をなくすことを意味します。しかし、このバグにより、ゼロ値のtime.Time
が渡された際に、内部的にデッドラインが正しく0
(無期限)に設定されず、結果としてデッドラインが意図せず残ってしまう可能性がありました。
このコミットは、この挙動を修正し、time.Time{}
が渡された場合には明示的にデッドラインをリセットするように変更することで、開発者が期待するデッドライン管理のセマンティクスを保証することを目的としています。
前提知識の解説
Go言語のnet
パッケージ
Go言語のnet
パッケージは、ネットワークI/Oプリミティブへのポータブルなインターフェースを提供します。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うための機能が含まれています。このパッケージの主要なインターフェースの一つにnet.Conn
があり、これはネットワーク接続の汎用的なインターフェースを定義しています。
ネットワークデッドライン(Deadlines)
ネットワークプログラミングにおいて、デッドラインはI/O操作(読み込み、書き込み、接続受け入れなど)が完了するまでの時間制限を設定するメカニズムです。デッドラインを設定することで、ネットワークの遅延や相手側の応答がない場合に、操作が無限にブロックされるのを防ぎ、アプリケーションの応答性を向上させることができます。
net.Conn
インターフェースには以下のデッドライン関連メソッドがあります。
SetReadDeadline(t time.Time) error
: 読み込み操作のデッドラインを設定します。SetWriteDeadline(t time.Time) error
: 書き込み操作のデッドラインを設定します。SetDeadline(t time.Time) error
: 読み込みと書き込みの両方のデッドラインを設定します。
これらのメソッドにtime.Time{}
(ゼロ値のtime.Time
)を渡すと、対応するデッドラインがリセットされ、操作は無期限にブロックされる可能性があります(ただし、他の要因によるタイムアウトは発生しうる)。
time.Time
型とIsZero()
メソッド
Go言語のtime
パッケージは、時刻と期間を扱うための機能を提供します。time.Time
型は特定の時点を表します。
time.Time
型のゼロ値は、time.Time{}
で表現され、これは「1年1月1日午前0時0分0秒 UTC」という特定の時刻を表します。しかし、文脈によっては「時刻が設定されていない」または「無効な時刻」として扱われることがあります。
time.Time
型にはIsZero()
メソッドがあり、これはそのtime.Time
がゼロ値である場合にtrue
を返します。このメソッドは、時刻が明示的に設定されているかどうかをチェックする際に便利です。
UnixNano()
メソッド
time.Time
型にはUnixNano()
メソッドがあり、これはその時刻をUnixエポック(1970年1月1日UTC)からの経過ナノ秒数として返します。内部的にデッドラインをナノ秒単位で管理する際に使用されます。デッドラインをリセットする際には、このナノ秒値が0
に設定されることが期待されます。
技術的詳細
このコミットの核心は、net
パッケージ内部でデッドラインを管理するsetReadDeadline
とsetWriteDeadline
関数が、time.Time
のゼロ値を正しく解釈し、デッドラインをリセットするように変更された点です。
以前の実装では、fd.rdeadline = t.UnixNano()
やfd.wdeadline = t.UnixNano()
のように、引数t
のUnixNano()
値を直接内部のデッドライン変数に代入していました。time.Time{}
のUnixNano()
値は0
ではない(実際には非常に小さい負の値、またはプラットフォームによって異なるが、通常は0
ではない)ため、この直接代入ではデッドラインが0
(無期限)に設定されず、以前のデッドラインがそのまま残ってしまうという問題がありました。
修正後のコードでは、t.IsZero()
を使って引数t
がゼロ値であるかを明示的にチェックしています。
- もし
t.IsZero()
がtrue
であれば、fd.rdeadline
またはfd.wdeadline
を明示的に0
に設定します。これにより、デッドラインが正しくリセットされ、I/O操作が無期限にブロックされるようになります(他の要因がない限り)。 - もし
t.IsZero()
がfalse
であれば、これまで通りt.UnixNano()
の値をデッドラインに設定します。
この変更により、SetReadDeadline(time.Time{})
やSetWriteDeadline(time.Time{})
を呼び出すことで、期待通りにデッドラインが解除されるようになりました。
また、この変更を検証するために、TestDeadlineReset
という新しいテストケースが追加されました。このテストは、TCPListener
のデッドラインを設定し、その後time.Time{}
でリセットし、Accept
操作がブロックされることを確認します。もしデッドラインが正しくリセットされていなければ、Accept
はタイムアウトしてエラーを返すはずですが、テストではタイムアウトしないことを期待しています。
コアとなるコードの変更箇所
変更は主に以下の2つのファイルで行われています。
-
src/pkg/net/sockopt.go
:setReadDeadline
関数とsetWriteDeadline
関数に、time.Time
のゼロ値チェックとデッドラインのリセットロジックが追加されました。
--- a/src/pkg/net/sockopt.go +++ b/src/pkg/net/sockopt.go @@ -117,12 +117,20 @@ func setWriteBuffer(fd *netFD, bytes int) error { } func setReadDeadline(fd *netFD, t time.Time) error { - fd.rdeadline = t.UnixNano() + if t.IsZero() { + fd.rdeadline = 0 + } else { + fd.rdeadline = t.UnixNano() + } return nil } func setWriteDeadline(fd *netFD, t time.Time) error { - fd.wdeadline = t.UnixNano() + if t.IsZero() { + fd.wdeadline = 0 + } else { + fd.wdeadline = t.UnixNano() + } return nil }
-
src/pkg/net/timeout_test.go
:TestDeadlineReset
という新しいテスト関数が追加されました。
--- a/src/pkg/net/timeout_test.go +++ b/src/pkg/net/timeout_test.go @@ -77,3 +77,30 @@ func TestTimeoutTCP(t *testing.T) { testTimeout(t, "tcp", addr, false) <-done } + +func TestDeadlineReset(t *testing.T) { + if runtime.GOOS == "plan9" { + return + } + ln, err := Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + tl := ln.(*TCPListener) + tl.SetDeadline(time.Now().Add(1 * time.Minute)) + tl.SetDeadline(time.Time{}) // reset it + errc := make(chan error, 1) + go func() { + _, err := ln.Accept() + errc <- err + }() + select { + case <-time.After(50 * time.Millisecond): + // Pass. + case err := <-errc: + // Accept should never return; we never + // connected to it. + t.Errorf("unexpected return from Accept; err=%v", err) + } +}
コアとなるコードの解説
src/pkg/net/sockopt.go
の変更
setReadDeadline
とsetWriteDeadline
関数は、それぞれ読み込みと書き込みのデッドラインを内部的に設定する役割を担っています。
変更前は、引数t
(time.Time
型)のUnixNano()
値をそのままfd.rdeadline
またはfd.wdeadline
に代入していました。しかし、time.Time{}
のUnixNano()
は0
ではないため、デッドラインをリセットする意図でtime.Time{}
が渡されても、内部のデッドライン値が0
にならず、結果としてデッドラインが解除されないという問題がありました。
修正後は、if t.IsZero()
という条件分岐が追加されました。
t.IsZero()
がtrue
の場合、つまりtime.Time{}
が渡された場合は、デッドラインを明示的に0
に設定します。内部的に0
は「デッドラインなし」を意味します。t.IsZero()
がfalse
の場合、つまり有効な時刻が渡された場合は、これまで通りt.UnixNano()
の値をデッドラインに設定します。
この変更により、time.Time{}
を渡すことでデッドラインが確実にリセットされるようになり、APIのセマンティクスが明確になりました。
src/pkg/net/timeout_test.go
の追加テスト
TestDeadlineReset
は、この修正が正しく機能することを検証するためのテストです。
- まず、
TCPListener
を作成し、一時的にデッドラインを設定します(time.Now().Add(1 * time.Minute)
)。 - 次に、
tl.SetDeadline(time.Time{})
を呼び出して、デッドラインをリセットします。これがこのテストの肝となる部分です。 - ゴルーチン内で
ln.Accept()
を呼び出します。もしデッドラインが正しくリセットされていれば、Accept
は接続が来るまでブロックされ続けるはずです。 - メインゴルーチンでは、
time.After(50 * time.Millisecond)
を使って短い時間待機します。 select
文を使って、Accept
がエラーを返さないこと、つまりブロックされ続けることを確認します。もしAccept
がタイムアウトしてエラーを返した場合(デッドラインがリセットされていない場合)、テストは失敗します。
このテストは、デッドラインがtime.Time{}
によって正しくリセットされ、ネットワーク操作が無期限にブロックされる(タイムアウトしない)という期待される挙動を保証します。
関連リンク
- Go CL (Code Review) 5570056: https://golang.org/cl/5570056
参考にした情報源リンク
- Go
net
package documentation: https://pkg.go.dev/net - Go
time
package documentation: https://pkg.go.dev/time - Go
time.Time.IsZero()
documentation: https://pkg.go.dev/time#Time.IsZero - Go
time.Time.UnixNano()
documentation: https://pkg.go.dev/time#Time.UnixNano - Stack Overflow discussions on Go network deadlines and
time.Time{}
(general knowledge, no specific link used but informed understanding). - Go issue tracker (general knowledge, no specific issue link used but informed understanding of common Go issues).