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

[インデックス 16771] ファイルの概要

このコミットは、Go言語の標準ライブラリnetパッケージに、TCP接続のKeep-Alive機能をより細かく制御するためのSetKeepAlivePeriodメソッドを追加するものです。これにより、アプリケーション開発者はTCP Keep-Aliveパケットの送信間隔をプログラムから設定できるようになります。

コミット

commit 918922cf850b13fd8bfe9aacb4e595bcd067c656
Author: David Presotto <presotto@gmail.com>
Date:   Mon Jul 15 18:40:55 2013 -0400

    net: add SetKeepAlivePeriod
    
    Sets both the duration from the last data packet to the first
    keep alive packet and the duration between keep alive packets to be
    the passed duration.
    
    I've tested the function on both Darwin (10.8.4) and 4.2 Linux.
    I've compiled (make.bash) for all the OS's and tested (all.bash)
    on Darwin and Linux.
    
    R=golang-dev, dave, rsc, dvyukov, presotto+facebook, nick
    CC=golang-dev, veyron-team
    https://golang.org/cl/11130044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/918922cf850b13fd8bfe9aacb4e595bcd067c656

元コミット内容

net: add SetKeepAlivePeriod

このコミットは、最後のデータパケットから最初のKeep-Aliveパケットまでの期間と、Keep-Aliveパケット間の期間の両方を、渡された期間に設定するSetKeepAlivePeriod関数を追加します。

この機能は、Darwin (10.8.4) と 4.2 Linux の両方でテストされています。すべてのOS向けにコンパイル (make.bash) し、Darwin と Linux でテスト (all.bash) 済みです。

変更の背景

TCP Keep-Aliveは、アイドル状態のTCP接続がまだ有効であるかどうかを確認するために使用されるメカニズムです。これは、ネットワークの途中でルーターやファイアウォールがアイドル接続をタイムアウトさせてしまうことを防いだり、接続の片側がクラッシュした場合に他方がそれを検出し、リソースを解放できるようにするために重要です。

Go言語のnetパッケージには、すでにSetKeepAlive(bool)メソッドが存在し、Keep-Alive機能を有効/無効にすることができました。しかし、Keep-Aliveパケットの送信間隔を細かく制御する手段がありませんでした。多くのオペレーティングシステム(OS)では、TCP Keep-Aliveの動作を制御するためのソケットオプション(例: TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT)を提供しています。これらのオプションをGoアプリケーションから直接設定できるようにすることで、開発者は特定のネットワーク環境やアプリケーションの要件に合わせて、Keep-Aliveの動作を最適化できるようになります。

このコミットは、特に長時間アイドル状態になる可能性のある接続(例: データベース接続、WebSocket接続、ストリーミングサービスなど)において、接続の健全性を維持し、不要なタイムアウトやリソースリークを防ぐことを目的としています。

前提知識の解説

TCP Keep-Alive

TCP Keep-Aliveは、TCP/IPプロトコルスタックの一部として提供される機能です。これは、アプリケーションがデータを送信していないアイドル状態のTCP接続が、まだ有効であるかどうかを定期的に確認するために使用されます。

  • 目的:

    • アイドル接続の検出: ネットワークの途中でルーターやファイアウォールがアイドル接続をタイムアウトさせてしまうことを防ぎます。
    • ピアのクラッシュ検出: 接続の片側(ピア)がクラッシュしたり、ネットワークから切断されたりした場合に、他方がそれを検出し、関連するリソースを解放できるようにします。これにより、半開きの接続(half-open connection)がシステムリソースを無駄に消費し続けることを防ぎます。
    • NATタイムアウトの回避: ネットワークアドレス変換(NAT)デバイスが、一定期間データが流れない接続をタイムアウトさせるのを防ぐのに役立ちます。
  • 動作:

    1. TCP接続が一定期間アイドル状態になると、Keep-Aliveプローブパケットが送信されます。
    2. ピアが応答すれば、接続はまだ有効であると判断され、タイマーがリセットされます。
    3. ピアが応答しない場合、プローブは複数回再送信されます。
    4. 設定された再試行回数を超えても応答がない場合、接続は切断されたと判断され、アプリケーションにエラーが通知されます。

TCP Keep-Aliveに関連するソケットオプション

オペレーティングシステムによって、TCP Keep-Aliveの動作を制御するためのソケットオプションが提供されています。主なものは以下の通りです。

  • TCP_KEEPALIVE (macOS/BSD系):

    • macOSやBSD系のシステムでは、TCP_KEEPALIVEオプションがアイドルタイムアウト期間を設定するために使用されます。これは、接続がアイドル状態になってから最初のKeep-Aliveプローブを送信するまでの時間を秒単位で指定します。このオプションは、TCP_KEEPIDLETCP_KEEPINTVLの両方の役割を兼ねる場合があります。
  • TCP_KEEPIDLE (Linux/POSIX系):

    • 接続がアイドル状態になってから、最初のKeep-Aliveプローブを送信するまでの時間を秒単位で指定します。
  • TCP_KEEPINTVL (Linux/POSIX系):

    • Keep-Aliveプローブが応答されなかった場合に、次のプローブを送信するまでの間隔を秒単位で指定します。
  • TCP_KEEPCNT (Linux/POSIX系):

    • 接続が切断されたと判断するまでに、Keep-Aliveプローブを再送信する最大回数を指定します。

これらのオプションは、setsockoptシステムコールを通じて設定されます。Go言語のnetパッケージは、これらのOS固有のシステムコールを抽象化し、クロスプラットフォームで利用できるAPIを提供します。

Go言語のnetパッケージとOS固有の実装

Go言語のnetパッケージは、ネットワークプログラミングのためのプラットフォーム非依存のインターフェースを提供しますが、その内部では各OSのシステムコールを呼び出しています。そのため、TCPソケットオプションのようなOS固有の機能については、OSごとに異なる実装を持つファイル(例: tcpsockopt_darwin.go, tcpsockopt_linux.go, tcpsockopt_windows.goなど)が存在します。これらのファイルは、ビルドタグ(+buildディレクティブ)によって、特定のOSでのみコンパイルされるように制御されています。

技術的詳細

このコミットは、net.TCPConn型にSetKeepAlivePeriod(d time.Duration)メソッドを追加し、TCP Keep-Aliveのアイドル期間とプローブ間隔を設定できるようにします。この機能は、OSごとに異なるソケットオプションの挙動を吸収し、統一されたAPIを提供します。

net.TCPConn.SetKeepAlivePeriodの追加

src/pkg/net/tcpsock_posix.goに、TCPConnの新しいメソッドとしてSetKeepAlivePeriodが追加されます。このメソッドは、内部的にOS固有のsetKeepAlivePeriod関数を呼び出します。

// src/pkg/net/tcpsock_posix.go
func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setKeepAlivePeriod(c.fd, d)
}

このメソッドは、TCPConnが有効な状態であるかを確認し、問題がなければnetFD(ファイルディスクリプタをラップする内部構造体)と指定された期間dを引数としてsetKeepAlivePeriodを呼び出します。

OS固有のsetKeepAlivePeriodの実装

このコミットの主要な変更は、各OS向けにsetKeepAlivePeriod関数を実装する新しいファイルが追加されたことです。

  1. src/pkg/net/tcpsockopt_darwin.go (macOS)

    • このファイルは、macOS (Darwin) 向けのsetKeepAlivePeriodを実装します。
    • macOSでは、syscall.TCP_KEEPALIVEソケットオプションを使用してKeep-Aliveのアイドル期間を設定します。
    • Goのtime.Durationを秒単位の整数に変換し、setsockoptシステムコールを呼び出します。
    • d += (time.Second - time.Nanosecond)という行は、期間を秒に切り上げるための処理です。例えば、1秒未満の期間が指定された場合でも、少なくとも1秒として扱われるようにします。
    // src/pkg/net/tcpsockopt_darwin.go
    func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
        // ... (incref/decref for fd)
        d += (time.Second - time.Nanosecond) // Round up to next highest second
        secs := int(d.Seconds())
        return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs))
    }
    
  2. src/pkg/net/tcpsockopt_openbsd.go (OpenBSD)

    • OpenBSD向けの実装も、macOSと同様にsyscall.TCP_KEEPALIVEを使用します。コードはtcpsockopt_darwin.goとほぼ同じです。
    // src/pkg/net/tcpsockopt_openbsd.go
    func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
        // ... (incref/decref for fd)
        d += (time.Second - time.Nanosecond) // Round up to next highest second
        secs := int(d.Seconds())
        return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs))
    }
    
  3. src/pkg/net/tcpsockopt_posix.go (FreeBSD, Linux, NetBSD)

    • このファイルは、Linux、FreeBSD、NetBSDなどのPOSIX準拠システム向けの実装です。
    • これらのシステムでは、TCP_KEEPINTVL(Keep-Aliveプローブ間の間隔)とTCP_KEEPIDLE(アイドル期間)の2つのソケットオプションを設定します。
    • SetKeepAlivePeriodに渡された期間dが、これら両方のオプションに適用されます。
    • ここでも、期間を秒に切り上げる処理が行われます。
    // src/pkg/net/tcpsockopt_posix.go
    // +build freebsd linux netbsd
    func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
        // ... (incref/decref for fd)
        d += (time.Second - time.Nanosecond) // Round up to next highest second
        secs := int(d.Seconds())
    
        err := os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs))
        if err != nil {
            return err
        }
        return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs))
    }
    
  4. src/pkg/net/tcpsockopt_windows.go (Windows)

    • Windows向けの実装は、他のOSとは異なり、SetKeepAlivePeriodが実質的に何もしない(no-op)実装となっています。
    • これは、WindowsのTCP Keep-Alive設定がソケット単位ではなく、システム全体の設定に依存しているため、GoのAPIから個々の接続に対して期間を設定することが困難であるためです。エラーを返すのではなく、単にnilを返すことで、呼び出し元がエラー処理を簡略化できるようにしています。
    // src/pkg/net/tcpsockopt_windows.go
    func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
        // ... (incref/decref for fd)
        // We can't actually set this per connection.  Act as a noop rather than an error.
        return nil
    }
    

クロスプラットフォーム対応の考慮

このコミットは、Go言語が異なるOS環境で動作することを考慮し、各OSのTCP Keep-Alive実装の差異を吸収しています。特に、Windowsでのno-op実装は、プラットフォームの制約を認識しつつ、可能な限り一貫したAPIを提供しようとするGoの設計哲学を反映しています。

コアとなるコードの変更箇所

このコミットによる主要なコード変更は以下のファイルに集中しています。

  1. src/pkg/net/tcpsock_posix.go:

    • TCPConn構造体にSetKeepAlivePeriod(d time.Duration) errorメソッドが追加されました。このメソッドは、内部的にOS固有のsetKeepAlivePeriod関数を呼び出すためのエントリポイントとなります。
  2. src/pkg/net/tcpsockopt_darwin.go (新規ファイル):

    • macOS (Darwin) 向けのsetKeepAlivePeriod関数が実装されました。syscall.TCP_KEEPALIVEソケットオプションを使用してKeep-Alive期間を設定します。
  3. src/pkg/net/tcpsockopt_openbsd.go (新規ファイル):

    • OpenBSD向けのsetKeepAlivePeriod関数が実装されました。macOSと同様にsyscall.TCP_KEEPALIVEを使用します。
  4. src/pkg/net/tcpsockopt_posix.go (新規ファイル):

    • Linux, FreeBSD, NetBSDなどのPOSIX準拠システム向けのsetKeepAlivePeriod関数が実装されました。syscall.TCP_KEEPINTVLsyscall.TCP_KEEPIDLEの2つのソケットオプションを設定します。
  5. src/pkg/net/tcpsockopt_windows.go (新規ファイル):

    • Windows向けのsetKeepAlivePeriod関数が実装されました。この実装は、Windowsの制約により、実質的に何もしない(no-op)動作となります。

コアとなるコードの解説

src/pkg/net/tcpsock_posix.go の変更

// ...
// SetKeepAliveIdlePeriod sets period between keep alives.
func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setKeepAlivePeriod(c.fd, d)
}
// ...

このコードは、TCPConn型に新しいパブリックメソッドSetKeepAlivePeriodを追加しています。

  • c.ok(): TCPConnが有効な状態(閉じられていないなど)であるかを確認します。無効な場合はsyscall.EINVAL(無効な引数)エラーを返します。
  • c.fd: netFD型の内部フィールドで、ネットワークファイルディスクリプタを抽象化したものです。
  • setKeepAlivePeriod(c.fd, d): 実際のOS固有のソケットオプション設定を行うプライベート関数を呼び出します。この関数は、ビルドタグによって各OSの実装にディスパッチされます。

OS固有の tcpsockopt_*.go ファイルの解説

これらのファイルは、Goのビルドシステムにおける「ビルドタグ」の概念を利用しています。ファイルの先頭にある// +build freebsd linux netbsdのようなコメントは、そのファイルが特定のOSでのみコンパイルされることを示します。これにより、Goは異なるOSのシステムコールやソケットオプションの差異を透過的に扱うことができます。

src/pkg/net/tcpsockopt_darwin.go および src/pkg/net/tcpsockopt_openbsd.go

// ...
// Set keep alive period.
func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
	if err := fd.incref(false); err != nil {
		return err
	}
	defer fd.decref()

	// The kernel expects seconds so round to next highest second.
	d += (time.Second - time.Nanosecond)
	secs := int(d.Seconds())

	return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs))
}
  • fd.incref(false) / fd.decref(): netFDの参照カウントを管理し、ファイルディスクリプタが安全に使用されていることを保証します。これはGoの内部的なリソース管理メカニズムです。
  • d += (time.Second - time.Nanosecond): time.Durationはナノ秒単位で時間を表しますが、カーネルのsetsockoptは通常秒単位の整数を期待します。この行は、指定された期間を最も近い秒に切り上げるためのトリックです。例えば、1s + (1s - 1ns)2s - 1nsとなり、整数にキャストすると1ではなく2になります。これにより、0.1sのような短い期間でも1sとして扱われます。
  • secs := int(d.Seconds()): time.Durationを秒単位の浮動小数点数に変換し、それを整数にキャストします。
  • syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs): 実際のシステムコール呼び出しです。
    • fd.sysfd: 基となるOSのファイルディスクリプタ。
    • syscall.IPPROTO_TCP: TCPプロトコルレベルのオプションを指定。
    • syscall.TCP_KEEPALIVE: 設定するソケットオプション。macOS/OpenBSDでは、これがKeep-Aliveのアイドル期間を制御します。
    • secs: 設定する期間(秒)。
  • os.NewSyscallError("setsockopt", ...): システムコールがエラーを返した場合に、Goのエラー型にラップして返します。

src/pkg/net/tcpsockopt_posix.go

// +build freebsd linux netbsd

// ...
func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
	// ... (incref/decref for fd)
	d += (time.Second - time.Nanosecond)
	secs := int(d.Seconds())

	err := os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs))
	if err != nil {
		return err
	}
	return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs))
}

このファイルは、LinuxなどのPOSIXシステム向けです。

  • syscall.TCP_KEEPINTVL: Keep-Aliveプローブ間の間隔を設定します。
  • syscall.TCP_KEEPIDLE: 接続がアイドル状態になってから最初のKeep-Aliveプローブを送信するまでの時間を設定します。
  • この実装では、SetKeepAlivePeriodに渡された単一の期間dを、TCP_KEEPINTVLTCP_KEEPIDLEの両方に設定しています。これは、コミットメッセージにある「Sets both the duration from the last data packet to the first keep alive packet and the duration between keep alive packets to be the passed duration.」という説明と一致します。

src/pkg/net/tcpsockopt_windows.go

// ...
func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
	if err := fd.incref(false); err != nil {
		return err
	}
	defer fd.decref()

	// We can't actually set this per connection.  Act as a noop rather than an error.
	return nil
}

Windowsでは、TCP Keep-Aliveの期間を個々のソケットに対して設定する直接的なAPIが提供されていないため、この関数は単にnil(エラーなし)を返します。これは、Goがプラットフォームの制約を認識し、可能な限り一貫したAPIを提供しようとするアプローチを示しています。アプリケーション開発者は、SetKeepAlivePeriodを呼び出してもWindows上では効果がないことを認識しておく必要があります。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特にsrc/pkg/netディレクトリ)
  • TCP Keep-Aliveに関する一般的なネットワークプロトコルの知識
  • 各OSのsetsockoptに関するドキュメント (例: Linux man pages, Apple Developer Documentation)
  • Go言語のコードレビューシステム (Gerrit) の変更セット: https://golang.org/cl/11130044

[インデックス 16771] ファイルの概要

このコミットは、Go言語の標準ライブラリnetパッケージに、TCP接続のKeep-Alive機能をより細かく制御するためのSetKeepAlivePeriodメソッドを追加するものです。これにより、アプリケーション開発者はTCP Keep-Aliveパケットの送信間隔をプログラムから設定できるようになります。

コミット

commit 918922cf850b13fd8bfe9aacb4e595bcd067c656
Author: David Presotto <presotto@gmail.com>
Date:   Mon Jul 15 18:40:55 2013 -0400

    net: add SetKeepAlivePeriod
    
    Sets both the duration from the last data packet to the first
    keep alive packet and the duration between keep alive packets to be
    the passed duration.
    
    I've tested the function on both Darwin (10.8.4) and 4.2 Linux.
    I've compiled (make.bash) for all the OS's and tested (all.bash)
    on Darwin and Linux.
    
    R=golang-dev, dave, rsc, dvyukov, presotto+facebook, nick
    CC=golang-dev, veyron-team
    https://golang.org/cl/11130044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/918922cf850b13fd8bfe9aacb4e595bcd067c656

元コミット内容

net: add SetKeepAlivePeriod

このコミットは、最後のデータパケットから最初のKeep-Aliveパケットまでの期間と、Keep-Aliveパケット間の期間の両方を、渡された期間に設定するSetKeepAlivePeriod関数を追加します。

この機能は、Darwin (10.8.4) と 4.2 Linux の両方でテストされています。すべてのOS向けにコンパイル (make.bash) し、Darwin と Linux でテスト (all.bash) 済みです。

変更の背景

TCP Keep-Aliveは、アイドル状態のTCP接続がまだ有効であるかどうかを確認するために使用されるメカニズムです。これは、ネットワークの途中でルーターやファイアウォールがアイドル接続をタイムアウトさせてしまうことを防いだり、接続の片側がクラッシュした場合に他方がそれを検出し、リソースを解放できるようにするために重要です。

Go言語のnetパッケージには、すでにSetKeepAlive(bool)メソッドが存在し、Keep-Alive機能を有効/無効にすることができました。しかし、Keep-Aliveパケットの送信間隔を細かく制御する手段がありませんでした。多くのオペレーティングシステム(OS)では、TCP Keep-Aliveの動作を制御するためのソケットオプション(例: TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT)を提供しています。これらのオプションをGoアプリケーションから直接設定できるようにすることで、開発者は特定のネットワーク環境やアプリケーションの要件に合わせて、Keep-Aliveの動作を最適化できるようになります。

このコミットは、特に長時間アイドル状態になる可能性のある接続(例: データベース接続、WebSocket接続、ストリーミングサービスなど)において、接続の健全性を維持し、不要なタイムアウトやリソースリークを防ぐことを目的としています。

前提知識の解説

TCP Keep-Alive

TCP Keep-Aliveは、TCP/IPプロトコルスタックの一部として提供される機能です。これは、アプリケーションがデータを送信していないアイドル状態のTCP接続が、まだ有効であるかどうかを定期的に確認するために使用されます。

  • 目的:

    • アイドル接続の検出: ネットワークの途中でルーターやファイアウォールがアイドル接続をタイムアウトさせてしまうことを防ぎます。
    • ピアのクラッシュ検出: 接続の片側(ピア)がクラッシュしたり、ネットワークから切断されたりした場合に、他方がそれを検出し、関連するリソースを解放できるようにします。これにより、半開きの接続(half-open connection)がシステムリソースを無駄に消費し続けることを防ぎます。
    • NATタイムアウトの回避: ネットワークアドレス変換(NAT)デバイスが、一定期間データが流れない接続をタイムアウトさせるのを防ぐのに役立ちます。
  • 動作:

    1. TCP接続が一定期間アイドル状態になると、Keep-Aliveプローブパケットが送信されます。
    2. ピアが応答すれば、接続はまだ有効であると判断され、タイマーがリセットされます。
    3. ピアが応答しない場合、プローブは複数回再送信されます。
    4. 設定された再試行回数を超えても応答がない場合、接続は切断されたと判断され、アプリケーションにエラーが通知されます。

TCP Keep-Aliveに関連するソケットオプション

オペレーティングシステムによって、TCP Keep-Aliveの動作を制御するためのソケットオプションが提供されています。主なものは以下の通りです。

  • TCP_KEEPALIVE (macOS/BSD系):

    • macOSやBSD系のシステムでは、TCP_KEEPALIVEオプションがアイドルタイムアウト期間を設定するために使用されます。これは、接続がアイドル状態になってから最初のKeep-Aliveプローブを送信するまでの時間を秒単位で指定します。このオプションは、TCP_KEEPIDLETCP_KEEPINTVLの両方の役割を兼ねる場合があります。
  • TCP_KEEPIDLE (Linux/POSIX系):

    • 接続がアイドル状態になってから、最初のKeep-Aliveプローブを送信するまでの時間を秒単位で指定します。
  • TCP_KEEPINTVL (Linux/POSIX系):

    • Keep-Aliveプローブが応答されなかった場合に、次のプローブを送信するまでの間隔を秒単位で指定します。
  • TCP_KEEPCNT (Linux/POSIX系):

    • 接続が切断されたと判断するまでに、Keep-Aliveプローブを再送信する最大回数を指定します。

これらのオプションは、setsockoptシステムコールを通じて設定されます。Go言語のnetパッケージは、これらのOS固有のシステムコールを抽象化し、クロスプラットフォームで利用できるAPIを提供します。

Go言語のnetパッケージとOS固有の実装

Go言語のnetパッケージは、ネットワークプログラミングのためのプラットフォーム非依存のインターフェースを提供しますが、その内部では各OSのシステムコールを呼び出しています。そのため、TCPソケットオプションのようなOS固有の機能については、OSごとに異なる実装を持つファイル(例: tcpsockopt_darwin.go, tcpsockopt_linux.go, tcpsockopt_windows.goなど)が存在します。これらのファイルは、ビルドタグ(+buildディレクティブ)によって、特定のOSでのみコンパイルされるように制御されています。

技術的詳細

このコミットは、net.TCPConn型にSetKeepAlivePeriod(d time.Duration)メソッドを追加し、TCP Keep-Aliveのアイドル期間とプローブ間隔を設定できるようにします。この機能は、OSごとに異なるソケットオプションの挙動を吸収し、統一されたAPIを提供します。

net.TCPConn.SetKeepAlivePeriodの追加

src/pkg/net/tcpsock_posix.goに、TCPConnの新しいメソッドとしてSetKeepAlivePeriodが追加されます。このメソッドは、内部的にOS固有のsetKeepAlivePeriod関数を呼び出します。

// src/pkg/net/tcpsock_posix.go
func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setKeepAlivePeriod(c.fd, d)
}

このメソッドは、TCPConnが有効な状態であるかを確認し、問題がなければnetFD(ファイルディスクリプタをラップする内部構造体)と指定された期間dを引数としてsetKeepAlivePeriodを呼び出します。

OS固有のsetKeepAlivePeriodの実装

このコミットの主要な変更は、各OS向けにsetKeepAlivePeriod関数を実装する新しいファイルが追加されたことです。

  1. src/pkg/net/tcpsockopt_darwin.go (macOS)

    • このファイルは、macOS (Darwin) 向けのsetKeepAlivePeriodを実装します。
    • macOSでは、syscall.TCP_KEEPALIVEソケットオプションを使用してKeep-Aliveのアイドル期間を設定します。
    • Goのtime.Durationを秒単位の整数に変換し、setsockoptシステムコールを呼び出します。
    • d += (time.Second - time.Nanosecond)という行は、期間を秒に切り上げるための処理です。例えば、1秒未満の期間が指定された場合でも、少なくとも1秒として扱われるようにします。
    // src/pkg/net/tcpsockopt_darwin.go
    func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
        // ... (incref/decref for fd)
        d += (time.Second - time.Nanosecond) // Round up to next highest second
        secs := int(d.Seconds())
        return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs))
    }
    
  2. src/pkg/net/tcpsockopt_openbsd.go (OpenBSD)

    • OpenBSD向けの実装も、macOSと同様にsyscall.TCP_KEEPALIVEを使用します。コードはtcpsockopt_darwin.goとほぼ同じです。
    // src/pkg/net/tcpsockopt_openbsd.go
    func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
        // ... (incref/decref for fd)
        d += (time.Second - time.Nanosecond) // Round up to next highest second
        secs := int(d.Seconds())
        return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs))
    }
    
  3. src/pkg/net/tcpsockopt_posix.go (FreeBSD, Linux, NetBSD)

    • このファイルは、Linux、FreeBSD、NetBSDなどのPOSIX準拠システム向けの実装です。
    • これらのシステムでは、TCP_KEEPINTVL(Keep-Aliveプローブ間の間隔)とTCP_KEEPIDLE(アイドル期間)の2つのソケットオプションを設定します。
    • SetKeepAlivePeriodに渡された期間dが、これら両方のオプションに適用されます。
    • ここでも、期間を秒に切り上げる処理が行われます。
    // src/pkg/net/tcpsockopt_posix.go
    // +build freebsd linux netbsd
    func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
        // ... (incref/decref for fd)
        d += (time.Second - time.Nanosecond) // Round up to next highest second
        secs := int(d.Seconds())
    
        err := os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs))
        if err != nil {
            return err
        }
        return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs))
    }
    
  4. src/pkg/net/tcpsockopt_windows.go (Windows)

    • Windows向けの実装は、他のOSとは異なり、SetKeepAlivePeriodが実質的に何もしない(no-op)実装となっています。
    • これは、WindowsのTCP Keep-Alive設定がソケット単位ではなく、システム全体の設定に依存しているため、GoのAPIから個々の接続に対して期間を設定することが困難であるためです。エラーを返すのではなく、単にnilを返すことで、呼び出し元がエラー処理を簡略化できるようにしています。
    // src/pkg/net/tcpsockopt_windows.go
    func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
        // ... (incref/decref for fd)
        // We can't actually set this per connection.  Act as a noop rather than an error.
        return nil
    }
    

クロスプラットフォーム対応の考慮

このコミットは、Go言語が異なるOS環境で動作することを考慮し、各OSのTCP Keep-Alive実装の差異を吸収しています。特に、Windowsでのno-op実装は、プラットフォームの制約を認識しつつ、可能な限り一貫したAPIを提供しようとするGoの設計哲学を反映しています。

コアとなるコードの変更箇所

このコミットによる主要なコード変更は以下のファイルに集中しています。

  1. src/pkg/net/tcpsock_posix.go:

    • TCPConn構造体にSetKeepAlivePeriod(d time.Duration) errorメソッドが追加されました。このメソッドは、内部的にOS固有のsetKeepAlivePeriod関数を呼び出すためのエントリポイントとなります。
  2. src/pkg/net/tcpsockopt_darwin.go (新規ファイル):

    • macOS (Darwin) 向けのsetKeepAlivePeriod関数が実装されました。syscall.TCP_KEEPALIVEソケットオプションを使用してKeep-Alive期間を設定します。
  3. src/pkg/net/tcpsockopt_openbsd.go (新規ファイル):

    • OpenBSD向けのsetKeepAlivePeriod関数が実装されました。macOSと同様にsyscall.TCP_KEEPALIVEを使用します。
  4. src/pkg/net/tcpsockopt_posix.go (新規ファイル):

    • Linux, FreeBSD, NetBSDなどのPOSIX準拠システム向けのsetKeepAlivePeriod関数が実装されました。syscall.TCP_KEEPINTVLsyscall.TCP_KEEPIDLEの2つのソケットオプションを設定します。
  5. src/pkg/net/tcpsockopt_windows.go (新規ファイル):

    • Windows向けのsetKeepAlivePeriod関数が実装されました。この実装は、Windowsの制約により、実質的に何もしない(no-op)動作となります。

コアとなるコードの解説

src/pkg/net/tcpsock_posix.go の変更

// ...
// SetKeepAliveIdlePeriod sets period between keep alives.
func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setKeepAlivePeriod(c.fd, d)
}
// ...

このコードは、TCPConn型に新しいパブリックメソッドSetKeepAlivePeriodを追加しています。

  • c.ok(): TCPConnが有効な状態(閉じられていないなど)であるかを確認します。無効な場合はsyscall.EINVAL(無効な引数)エラーを返します。
  • c.fd: netFD型の内部フィールドで、ネットワークファイルディスクリプタを抽象化したものです。
  • setKeepAlivePeriod(c.fd, d): 実際のOS固有のソケットオプション設定を行うプライベート関数を呼び出します。この関数は、ビルドタグによって各OSの実装にディスパッチされます。

OS固有の tcpsockopt_*.go ファイルの解説

これらのファイルは、Goのビルドシステムにおける「ビルドタグ」の概念を利用しています。ファイルの先頭にある// +build freebsd linux netbsdのようなコメントは、そのファイルが特定のOSでのみコンパイルされることを示します。これにより、Goは異なるOSのシステムコールやソケットオプションの差異を透過的に扱うことができます。

src/pkg/net/tcpsockopt_darwin.go および src/pkg/net/tcpsockopt_openbsd.go

// ...
// Set keep alive period.
func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
	if err := fd.incref(false); err != nil {
		return err
	}
	defer fd.decref()

	// The kernel expects seconds so round to next highest second.
	d += (time.Second - time.Nanosecond)
	secs := int(d.Seconds())

	return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs))
}
  • fd.incref(false) / fd.decref(): netFDの参照カウントを管理し、ファイルディスクリプタが安全に使用されていることを保証します。これはGoの内部的なリソース管理メカニズムです。
  • d += (time.Second - time.Nanosecond): time.Durationはナノ秒単位で時間を表しますが、カーネルのsetsockoptは通常秒単位の整数を期待します。この行は、指定された期間を最も近い秒に切り上げるためのトリックです。例えば、1s + (1s - 1ns)2s - 1nsとなり、整数にキャストすると1ではなく2になります。これにより、0.1sのような短い期間でも1sとして扱われます。
  • secs := int(d.Seconds()): time.Durationを秒単位の浮動小数点数に変換し、それを整数にキャストします。
  • syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs): 実際のシステムコール呼び出しです。
    • fd.sysfd: 基となるOSのファイルディスクリプタ。
    • syscall.IPPROTO_TCP: TCPプロトコルレベルのオプションを指定。
    • syscall.TCP_KEEPALIVE: 設定するソケットオプション。macOS/OpenBSDでは、これがKeep-Aliveのアイドル期間を制御します。
    • secs: 設定する期間(秒)。
  • os.NewSyscallError("setsockopt", ...): システムコールがエラーを返した場合に、Goのエラー型にラップして返します。

src/pkg/net/tcpsockopt_posix.go

// +build freebsd linux netbsd

// ...
func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
	// ... (incref/decref for fd)
	d += (time.Second - time.Nanosecond)
	secs := int(d.Seconds())

	err := os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs))
	if err != nil {
		return err
	}
	return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs))
}

このファイルは、LinuxなどのPOSIXシステム向けです。

  • syscall.TCP_KEEPINTVL: Keep-Aliveプローブ間の間隔を設定します。
  • syscall.TCP_KEEPIDLE: 接続がアイドル状態になってから最初のKeep-Aliveプローブを送信するまでの時間を設定します。
  • この実装では、SetKeepAlivePeriodに渡された単一の期間dを、TCP_KEEPINTVLTCP_KEEPIDLEの両方に設定しています。これは、コミットメッセージにある「Sets both the duration from the last data packet to the first keep alive packet and the duration between keep alive packets to be the passed duration.」という説明と一致します。

src/pkg/net/tcpsockopt_windows.go

// ...
func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
	if err := fd.incref(false); err != nil {
		return err
	}
	defer fd.decref()

	// We can't actually set this per connection.  Act as a noop rather than an error.
	return nil
}

Windowsでは、TCP Keep-Aliveの期間を個々のソケットに対して設定する直接的なAPIが提供されていないため、この関数は単にnil(エラーなし)を返します。これは、Goがプラットフォームの制約を認識し、可能な限り一貫したAPIを提供しようとするアプローチを示しています。アプリケーション開発者は、SetKeepAlivePeriodを呼び出してもWindows上では効果がないことを認識しておく必要があります。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特にsrc/pkg/netディレクトリ)
  • TCP Keep-Aliveに関する一般的なネットワークプロトコルの知識
  • 各OSのsetsockoptに関するドキュメント (例: Linux man pages, Apple Developer Documentation)
  • Go言語のコードレビューシステム (Gerrit) の変更セット: https://golang.org/cl/11130044