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

[インデックス 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インターフェースのSetReadDeadlineSetWriteDeadlineメソッドに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パッケージ内部でデッドラインを管理するsetReadDeadlinesetWriteDeadline関数が、time.Timeのゼロ値を正しく解釈し、デッドラインをリセットするように変更された点です。

以前の実装では、fd.rdeadline = t.UnixNano()fd.wdeadline = t.UnixNano()のように、引数tUnixNano()値を直接内部のデッドライン変数に代入していました。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つのファイルで行われています。

  1. 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
    
  2. 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の変更

setReadDeadlinesetWriteDeadline関数は、それぞれ読み込みと書き込みのデッドラインを内部的に設定する役割を担っています。 変更前は、引数ttime.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は、この修正が正しく機能することを検証するためのテストです。

  1. まず、TCPListenerを作成し、一時的にデッドラインを設定します(time.Now().Add(1 * time.Minute))。
  2. 次に、tl.SetDeadline(time.Time{})を呼び出して、デッドラインをリセットします。これがこのテストの肝となる部分です。
  3. ゴルーチン内でln.Accept()を呼び出します。もしデッドラインが正しくリセットされていれば、Acceptは接続が来るまでブロックされ続けるはずです。
  4. メインゴルーチンでは、time.After(50 * time.Millisecond)を使って短い時間待機します。
  5. select文を使って、Acceptがエラーを返さないこと、つまりブロックされ続けることを確認します。もしAcceptがタイムアウトしてエラーを返した場合(デッドラインがリセットされていない場合)、テストは失敗します。

このテストは、デッドラインがtime.Time{}によって正しくリセットされ、ネットワーク操作が無期限にブロックされる(タイムアウトしない)という期待される挙動を保証します。

関連リンク

  • Go CL (Code Review) 5570056: https://golang.org/cl/5570056

参考にした情報源リンク

コミット

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インターフェースのSetReadDeadlineSetWriteDeadlineメソッドに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パッケージ内部でデッドラインを管理するsetReadDeadlinesetWriteDeadline関数が、time.Timeのゼロ値を正しく解釈し、デッドラインをリセットするように変更された点です。

以前の実装では、fd.rdeadline = t.UnixNano()fd.wdeadline = t.UnixNano()のように、引数tUnixNano()値を直接内部のデッドライン変数に代入していました。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つのファイルで行われています。

  1. 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
     }
    
  2. 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の変更

setReadDeadlinesetWriteDeadline関数は、それぞれ読み込みと書き込みのデッドラインを内部的に設定する役割を担っています。 変更前は、引数ttime.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は、この修正が正しく機能することを検証するためのテストです。

  1. まず、TCPListenerを作成し、一時的にデッドラインを設定します(time.Now().Add(1 * time.Minute))。
  2. 次に、tl.SetDeadline(time.Time{})を呼び出して、デッドラインをリセットします。これがこのテストの肝となる部分です。
  3. ゴルーチン内でln.Accept()を呼び出します。もしデッドラインが正しくリセットされていれば、Acceptは接続が来るまでブロックされ続けるはずです。
  4. メインゴルーチンでは、time.After(50 * time.Millisecond)を使って短い時間待機します。
  5. 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).