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

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

コミット

commit f72c828c677f1cc47f62bc6f39096e9806b45c1d
Author: Dave Cheney <dave@cheney.net>
Date:   Fri Apr 27 22:17:08 2012 +1000

    net: consolidate common socket functions
    
    In resolving 3507, the fix had to be applied individually to
    the four *Conn types, tcp, udp, rawip and unix, due to the
    duplicate code in each Conn type.
    
    This CL consolidates the common net.Conn methods that all four
    *Conn types implement into a base conn type.
    
    Pros:
    * The fix for 3507 would have only needed to be applied to one
    method. Further improvements, such as possibly removing the
    c.fd != nil check in c.ok(), would benefit from this CL.
    * Nearly 300 lines removed from the net package.
    * The public interface and documentation are not changed.
    * I think this is an excellent example of the power of embedding.
    
    Cons:
    * The net package is already distributed over many files, this
    CL adds another place to look.
    * The fix for 3507 was a total of 16 lines changed, this follow
    up CL could be considered to be an overreaction as new Conn types
    are unlikely to be added in the near future.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6098047

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

https://github.com/golang/go/commit/f72c828c677f1cc47f62bc6f39096e9806b45c1d

元コミット内容

このコミットは、Go言語の標準ライブラリnetパッケージにおけるソケット関連の共通関数を統合することを目的としています。具体的には、tcp, udp, rawip, unixの4種類の*Conn型に散らばっていた重複コードを、connという基底型に集約しています。

この変更の主な動機は、以前のバグ修正(Issue 3507)において、各*Conn型に個別に修正を適用する必要があったという経験に基づいています。共通の基底型を導入することで、将来的な同様の修正や機能追加がより容易になり、コードの重複を削減し、保守性を向上させることが期待されます。

変更の背景

このコミットの背景には、Go言語のnetパッケージにおけるコードの重複と、それに伴う保守性の課題がありました。特に、Issue 3507の修正作業において、net.Connインターフェースを実装する*Conn型(TCPConn, UDPConn, IPConn, UnixConn)それぞれに、同じようなソケット操作に関するメソッド(Read, Write, Close, LocalAddr, RemoteAddr, SetDeadlineなど)が個別に実装されていることが問題となりました。

この重複は、バグ修正や機能追加の際に、すべての関連する型に対して同じ変更を適用する必要があることを意味し、コードの変更漏れや一貫性の欠如のリスクを高めていました。コミットメッセージでは、Issue 3507の修正が各*Conn型に個別に適用されたことに言及しており、この経験が共通の基底型を導入する動機となったことが示唆されています。

このコミットは、Go言語の設計思想である「シンプルさ」と「効率性」に沿って、コードベースの健全性を高めることを目指しています。Goの組み込み(embedding)機能を利用することで、公開インターフェースを変更することなく、内部実装の重複を解消し、よりクリーンで保守しやすいコード構造を実現しています。

前提知識の解説

Go言語のnetパッケージ

Go言語のnetパッケージは、ネットワークI/Oのプリミティブを提供します。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うためのインターフェースと実装が含まれています。

  • net.Connインターフェース: ネットワーク接続の一般的なインターフェースを定義しています。Read, Write, Close, LocalAddr, RemoteAddr, SetDeadlineなどのメソッドが含まれます。
  • netFD: ネットワークファイルディスクリプタ(File Descriptor)を抽象化した内部構造体です。実際のソケット操作はnetFDを通じて行われます。

Go言語の組み込み(Embedding)

Go言語の組み込みは、他の言語における継承に似た機能ですが、よりシンプルで柔軟なメカニズムです。構造体の中に別の構造体やインターフェースを匿名フィールドとして含めることで、その匿名フィールドのメソッドを外側の構造体が直接呼び出せるようになります。これにより、コードの再利用性を高め、型階層を構築することができます。

例えば、type Outer struct { Inner } のように定義すると、Outer型のインスタンスはInner型のメソッドを直接呼び出すことができます。このとき、InnerOuterのフィールドとして存在しますが、フィールド名なしでアクセスできるため、あたかもOuter自身のメソッドであるかのように振る舞います。

Issue 3507

Go言語のIssue 3507は、netパッケージにおけるソケットのデッドライン設定に関するバグでした。具体的には、SetReadDeadlineSetWriteDeadlineが正しく機能しない、または競合状態を引き起こす可能性がありました。このバグの修正は、net.Connインターフェースを実装する各*Conn型(TCPConn, UDPConn, IPConn, UnixConn)に個別に適用する必要がありました。これは、各型がソケット操作のロジックを独自に持っていたためです。

技術的詳細

このコミットの技術的な核心は、Go言語の組み込み(embedding)を活用して、netパッケージ内の重複するソケット操作ロジックを抽象化し、共通化することにあります。

  1. conn基底構造体の導入: src/pkg/net/net_posix.goという新しいファイルが作成され、connという新しい構造体が定義されました。このconn構造体は、*netFDフィールドを持ち、net.Connインターフェースが要求するRead, Write, Close, LocalAddr, RemoteAddr, SetDeadline, SetReadDeadline, SetWriteDeadline, SetReadBuffer, SetWriteBuffer, Fileといった共通のメソッドを実装しています。

    type conn struct {
        fd *netFD
    }
    
    func (c *conn) ok() bool { return c != nil && c.fd != nil }
    
    // Implementation of the Conn interface - see Conn for documentation.
    // ... (Read, Write, Close, LocalAddr, RemoteAddr, SetDeadline, etc. methods)
    

    これらのメソッドは、内部的にc.fdnetFD)の対応するメソッドを呼び出すことで、実際のソケット操作を行います。

  2. 既存の*Conn型へのconnの組み込み: 既存のIPConn, TCPConn, UDPConn, UnixConnの各構造体から、重複していたnet.Connインターフェースのメソッド実装が削除されました。代わりに、これらの構造体はconn型を匿名フィールドとして組み込むようになりました。

    // Before:
    // type IPConn struct {
    //     fd *netFD
    // }
    // func (c *IPConn) Read(...) ...
    // func (c *IPConn) Write(...) ...
    // ...
    
    // After:
    type IPConn struct {
        conn // conn型を組み込む
    }
    // IPConnはconnのメソッドを直接利用できるようになる
    

    これにより、IPConnなどの型は、conn型が実装するRead, Writeなどのメソッドを、あたかも自身のメソッドであるかのように利用できるようになります。例えば、ipConnInstance.Read(b)と呼び出すと、内部的には組み込まれたconnフィールドのReadメソッドが呼び出されます。

  3. コンストラクタの変更: 各*Conn型のコンストラクタ(例: newIPConn)も変更され、netFDを直接*Conn型のfdフィールドに割り当てるのではなく、conn構造体を介してnetFDを初期化するように修正されました。

    // Before:
    // func newIPConn(fd *netFD) *IPConn { return &IPConn{fd} }
    
    // After:
    func newIPConn(fd *netFD) *IPConn { return &IPConn{conn{fd}} }
    

この変更により、約300行のコードがnetパッケージ全体から削除され、コードベースが大幅に簡素化されました。また、net.Connインターフェースの公開APIは一切変更されていないため、既存のコードとの互換性は完全に維持されています。

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

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

  1. src/pkg/net/net_posix.go (新規作成):

    • connという新しい構造体が定義され、*netFDフィールドを持つ。
    • net.Connインターフェースの共通メソッド(Read, Write, Close, LocalAddr, RemoteAddr, SetDeadlineなど)がこのconn構造体に実装される。これらのメソッドは、内部的にc.fdの対応するメソッドを呼び出す。
  2. src/pkg/net/iprawsock_posix.go:

    • IPConn構造体から、Read, Write, Close, LocalAddr, RemoteAddr, SetDeadlineなどの重複するメソッド実装が削除される。
    • IPConn構造体の定義がfd *netFDからconnに変わる。
    • newIPConn関数が&IPConn{conn{fd}}を返すように変更される。
  3. src/pkg/net/tcpsock_posix.go:

    • TCPConn構造体から、Read, Write, Close, LocalAddr, RemoteAddr, SetDeadlineなどの重複するメソッド実装が削除される。
    • TCPConn構造体の定義がfd *netFDからconnに変わる。
    • newTCPConn関数が&TCPConn{conn{fd}}を返すように変更される。
  4. src/pkg/net/udpsock_posix.go:

    • UDPConn構造体から、Read, Write, Close, LocalAddr, RemoteAddr, SetDeadlineなどの重複するメソッド実装が削除される。
    • UDPConn構造体の定義がfd *netFDからconnに変わる。
    • newUDPConn関数が&UDPConn{conn{fd}}を返すように変更される。
  5. src/pkg/net/unixsock_posix.go:

    • UnixConn構造体から、Read, Write, Close, LocalAddr, RemoteAddr, SetDeadlineなどの重複するメソッド実装が削除される。
    • UnixConn構造体の定義がfd *netFDからconnに変わる。
    • newUnixConn関数が&UnixConn{conn{fd}}を返すように変更される。

これらの変更により、各*Conn型はconnを組み込むことで、共通のソケット操作メソッドを再利用し、コードの重複が解消されています。

コアとなるコードの解説

このコミットの核心は、Go言語の「組み込み(embedding)」機能を用いて、netパッケージ内のネットワーク接続型(IPConn, TCPConn, UDPConn, UnixConn)が共通して持つソケット操作のロジックをconnという単一の基底構造体に集約した点にあります。

src/pkg/net/net_posix.go での conn 構造体の定義

package net

import (
	"os"
	"syscall"
	"time"
)

type conn struct {
	fd *netFD
}

func (c *conn) ok() bool { return c != nil && c.fd != nil }

// Implementation of the Conn interface - see Conn for documentation.

// Read implements the Conn Read method.
func (c *conn) Read(b []byte) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	return c.fd.Read(b)
}

// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	return c.fd.Write(b)
}

// LocalAddr returns the local network address.
func (c *conn) LocalAddr() Addr {
	if !c.ok() {
		return nil
	}
	return c.fd.laddr
}

// RemoteAddr returns the remote network address, a *UDPAddr.
func (c *conn) RemoteAddr() Addr {
	if !c.ok() {
		return nil
	}
	return c.fd.raddr
}

// SetDeadline implements the Conn SetDeadline method.
func (c *conn) SetDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return setDeadline(c.fd, t)
}

// ... (他の共通メソッドも同様に実装)

// Close closes the connection.
func (c *conn) Close() error {
	if !c.ok() {
		return syscall.EINVAL
	}
	return c.fd.Close()
}

このconn構造体は、netFD(ネットワークファイルディスクリプタ)へのポインタを保持し、net.Connインターフェースが定義するほとんどのメソッドを実装しています。これらのメソッドは、実際のソケット操作をc.fdに委譲しています。c.ok()ヘルパー関数は、connfdが有効であるかを確認するためのものです。

*Conn 型での conn の組み込み

例えば、src/pkg/net/iprawsock_posix.goIPConn の変更を見てみましょう。

変更前:

type IPConn struct {
	fd *netFD
}

func newIPConn(fd *netFD) *IPConn { return &IPConn{fd} }

// Read implements the Conn Read method.
func (c *IPConn) Read(b []byte) (int, error) {
	n, _, err := c.ReadFrom(b)
	return n, err
}

// Write implements the Conn Write method.
func (c *IPConn) Write(b []byte) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	return c.fd.Write(b)
}

// ... (他の重複メソッド)

変更後:

type IPConn struct {
	conn // conn型を匿名フィールドとして組み込む
}

func newIPConn(fd *netFD) *IPConn { return &IPConn{conn{fd}} }

// IP-specific methods.
// ... (IPConn固有のメソッドのみが残る)

変更後、IPConn構造体はconn型を匿名フィールドとして持つようになりました。これにより、IPConnのインスタンスは、connが実装するRead, Write, Closeなどのメソッドを直接呼び出すことができます。例えば、myIPConn.Read(buffer)と書くと、Goコンパイラは自動的に組み込まれたconnフィールドのReadメソッドを解決します。

このパターンは、TCPConn, UDPConn, UnixConnにも同様に適用されています。結果として、各*Conn型は、それぞれのプロトコルに固有のメソッド(例: TCPConnSetNoDelayReadFrom)のみを保持し、共通のソケット操作ロジックはconn基底型に一元化されました。これにより、コードの重複が大幅に削減され、将来のメンテナンスや機能追加が容易になります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(netパッケージ): https://pkg.go.dev/net
  • Go言語の組み込み(Embedding)に関する解説記事(例: A Tour of Go - Embedded fieldsなど)
  • Go言語のnetパッケージのソースコード (GoのGitHubリポジトリ)
  • Go言語のIssueトラッカー (Issue 3507の具体的な内容を理解するため)
  • Dave Cheney氏のブログやGoに関する記事 (コミット作者の技術的背景を理解するため)
    • 特に、Goの設計原則やコードの簡潔性に関する議論は、このコミットの意図を深く理解する上で役立ちます。