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

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

このコミットは、Go言語の標準ライブラリであるbufioiohttpパッケージに対して、パフォーマンス改善、APIの堅牢化、新機能の追加、およびコードの再編成を行うものです。特に、bufioパッケージにおける大規模なコピーの回避、NewBufReadおよびNewBufWrite関数のエラー返却の廃止、BufReadWrite型の導入、ioパッケージへのCloseインターフェースの追加、そしてhttpパッケージにおけるConn.Hijack機能の実装が主要な変更点です。

コミット

commit 9aa28f9231bf8239027d8d2eda230f090eddaf83
Author: Russ Cox <rsc@golang.org>
Date:   Tue Feb 3 14:16:22 2009 -0800

    bufio:
            * avoid large copies
            * NewBufRead, NewBufWrite never fail
            * add BufReadWrite
    
    io:
            * add io.Close
    
    http, google/net/rpc:
            * add, use http.Conn.Hijack
    
    R=r
    DELTA=416  (202 added, 123 deleted, 91 changed)
    OCL=24153
    CL=24238

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

https://github.com/golang/go/commit/9aa28f9231bf8239027d8d2eda230f090eddaf83

元コミット内容

このコミットは、Go言語の初期段階(2009年2月)に行われたもので、Russ Cox氏によって提出されました。主な内容は以下の通りです。

  • bufioパッケージの改善:
    • バッファリングされたI/O操作において、大規模なデータコピーを避けるための最適化。
    • NewBufReadおよびNewBufWrite関数がエラーを返さなくなり、常に有効な*BufReadまたは*BufWriteインスタンスを返すように変更(内部でパニックする可能性はあるが、APIとしてはエラーを返さない)。
    • 読み書き両方をサポートするBufReadWrite型を新しく追加。
  • ioパッケージの改善:
    • リソースを閉じるための共通インターフェースio.Closeを追加。
  • httpおよびgoogle/net/rpcパッケージの改善:
    • http.ConnHijackメソッドを追加し、HTTPハンドラが基盤となるTCP接続を直接制御できるようにする。
    • このHijack機能の利用。

変更の背景

このコミットが行われた2009年は、Go言語がまだ公開されて間もない、活発な開発初期段階でした。この時期のコミットは、言語の基本的な設計思想や標準ライブラリのAPIが固まっていく過程を反映しています。

  1. パフォーマンスの最適化: bufioパッケージにおける大規模なコピーの回避は、I/O性能の向上を目的としています。特にネットワークI/OやファイルI/Oにおいて、不要なデータコピーは大きなオーバーヘッドとなるため、効率的なバッファリング戦略が求められました。
  2. APIの使いやすさと堅牢性: NewBufReadNewBufWriteがエラーを返さなくなったのは、これらの関数が通常は失敗しない(例えば、無効なバッファサイズが指定された場合など、プログラミングエラーに起因するケースを除けば)という設計判断に基づいています。これにより、呼び出し側でのエラーハンドリングが簡素化され、よりクリーンなコードが書けるようになります。Go言語の初期には、エラーハンドリングのパターンが模索されており、このような変更はその一環です。
  3. 柔軟なネットワークプロトコル対応: http.Conn.Hijackの導入は、HTTPプロトコルだけでなく、その基盤となるTCP接続を直接操作したいというニーズに応えるものです。これは、WebSocketのようなHTTPのアップグレードメカニズムを利用するプロトコルや、カスタムのTCPベースのプロトコルをHTTPサーバー上で動かす場合に不可欠な機能となります。これにより、Goのnet/httpパッケージがより汎用的なネットワークサーバーフレームワークとしての役割も果たせるようになりました。
  4. インターフェースによる抽象化: io.Closeインターフェースの追加は、Go言語の設計哲学である「インターフェースによる抽象化」を推進するものです。これにより、様々な種類のリソース(ファイル、ネットワーク接続など)が共通のClose()メソッドを持つことで、統一的なリソース管理とクリーンアップが可能になります。

前提知識の解説

Go言語の初期のエラーハンドリング (os.Errorpanic)

このコミットが書かれた2009年当時、Go言語のエラーハンドリングは現在とは異なり、os.Error型が使われていました。現在のGoでは、組み込みのerrorインターフェースが標準となっています。また、NewBufReadNewBufWriteが「never fail」とされているにも関わらず、内部でpanicを使用している点も当時のGoのエラーハンドリングの考え方を反映しています。これは、通常発生しないと想定される「プログラミングエラー」や「回復不能なエラー」に対してはpanicを使用し、それ以外の「予期されるエラー」(例えば、ファイルの終端やネットワークエラーなど)に対しては戻り値としてエラーを返すという区別があったためです。

バッファリングI/Oの基本

bufioパッケージは、I/O操作の効率を向上させるためにバッファリングを提供します。

  • BufRead: 読み込み操作をバッファリングします。これにより、少量のデータを頻繁に読み込む際に、実際のシステムコール(ディスクI/OやネットワークI/O)の回数を減らし、性能を向上させます。
  • BufWrite: 書き込み操作をバッファリングします。同様に、少量のデータを頻繁に書き込む際に、システムコールの回数を減らします。バッファが満杯になったり、明示的にFlushが呼ばれたりすると、バッファの内容が基盤となるライターに書き込まれます。

HTTPプロトコルと接続のライフサイクル

HTTP/1.1では、一つのTCP接続上で複数のリクエスト/レスポンスをやり取りする「持続的接続(Persistent Connection)」が一般的です。通常のHTTPサーバーは、リクエストを読み込み、ハンドラで処理し、レスポンスを書き込んだ後、次のリクエストを同じ接続で待機します。

HTTPコネクションハイジャック (Connection Hijacking)

HTTPコネクションハイジャックとは、HTTPサーバーが通常行うリクエスト/レスポンスの処理フローから逸脱し、基盤となるTCP接続の制御をアプリケーションハンドラに完全に委譲するメカニズムです。これにより、ハンドラはHTTPプロトコルに縛られず、そのTCP接続上で任意のプロトコル(例: WebSocket、SPDY、カスタムプロトコル)を実装できるようになります。これは、HTTPのアップグレードメカニズム(Upgradeヘッダ)と組み合わせて使用されることが多いです。

技術的詳細

bufioパッケージの変更

  • 大規模コピーの回避:
    • BufRead.Read(p []byte)メソッドにおいて、読み込み先のスライスpのサイズが内部バッファb.bufのサイズ以上であり、かつ内部バッファが空の場合、b.rd.Read(p)を直接呼び出すように変更されました。これにより、データを内部バッファに読み込んでからpにコピーするという二重のコピーが回避され、効率が向上します。
    • 同様に、BufWrite.Write(p []byte)メソッドにおいて、書き込み元のスライスpのサイズが内部バッファb.bufのサイズ以上であり、かつ内部バッファが空の場合、b.wr.Write(p)を直接呼び出すように変更されました。これにより、pから内部バッファへのコピー、そして内部バッファから基盤となるライターへの書き込みという二重のコピーが回避されます。
  • NewBufRead, NewBufWriteのエラー返却廃止:
    • 以前はNewBufRead(rd io.Read) (b *BufRead, err *os.Error)のようにエラーを返していましたが、NewBufRead(rd io.Read) *BufReadのようにエラーを返さないシグネチャに変更されました。内部的にはNewBufReadSizeを呼び出し、そこでエラーが発生した場合はpanicを発生させるようになりました。これは、defaultBufSizeが常に有効なサイズであるため、通常はエラーが発生しないという前提に基づいています。NewBufWriteも同様の変更が加えられました。
  • BufReadWriteの追加:
    • type BufReadWrite struct { *BufRead; *BufWrite; }という新しい構造体が導入されました。これは、BufReadBufWriteを埋め込むことで、両方の機能を持つ単一の型を提供します。これにより、HTTPコネクションのように読み書き両方のバッファリングが必要な場合に、コードが簡潔になります。NewBufReadWrite(r *BufRead, w *BufWrite) *BufReadWriteというコンストラクタも提供されます。
  • UnreadByteの改善:
    • BufRead.UnreadByte()メソッドにlastbyteフィールドを利用したロジックが追加されました。これにより、バッファが空であっても、直前に読み込んだ1バイトを「アンリード」できるようになり、より柔軟なパーシングが可能になります。

ioパッケージの変更

  • io.Closeインターフェースの追加:
    • type Close interface { Close() *os.Error; }というシンプルなインターフェースが追加されました。これは、リソースを閉じるための共通の契約を提供し、様々なI/Oタイプ(ファイル、ネットワーク接続など)に対して統一的なClose()操作を可能にします。

httpパッケージの変更

  • Conn構造体の再設計:
    • http.Conn構造体から、個別のbr*bufio.BufRead)とbw*bufio.BufWrite)フィールドが削除され、代わりにbuf *bufio.BufReadWriteが導入されました。これにより、バッファリングされたI/Oの管理がBufReadWriteに集約され、Connのコードが簡潔になりました。
    • hijackedというブール型フィールドが追加され、コネクションがハイジャックされた状態かどうかを追跡します。
    • HTTPステータスコードの定数とstatusTextマップがsrc/lib/http/server.goからsrc/lib/http/status.goという新しいファイルに分離されました。これはコードのモジュール化と整理を目的としています。
  • Conn.Hijack()メソッドの追加:
    • func (c *Conn) Hijack() (fd io.ReadWriteClose, buf *bufio.BufReadWrite, err *os.Error)というメソッドがhttp.Connに追加されました。
    • このメソッドが呼び出されると、c.hijackedフラグがtrueに設定され、HTTPサーバーはそれ以上このコネクションを処理しなくなります。
    • 基盤となるio.ReadWriteCloseインターフェース(通常はTCP接続)と、バッファリングされたI/Oオブジェクト*bufio.BufReadWriteが呼び出し元に返されます。
    • これにより、ハンドラは返されたfdbufを使って、HTTPプロトコルに縛られずに直接ネットワーク通信を行うことができます。
    • Hijackが呼び出された後、Conn.WriteConn.WriteHeaderのような通常のHTTPレスポンス書き込みメソッドはエラーを返すか、ログに警告を出力するようになります。

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

src/lib/bufio.go

// Readメソッド内の大規模コピー回避ロジック
if len(p) >= len(b.buf) {
    // Large read, empty buffer.
    // Read directly into p to avoid copy.
    n, b.err = b.rd.Read(p);
    // ...
}

// NewBufReadのエラー返却廃止
func NewBufRead(rd io.Read) *BufRead {
    b, err := NewBufReadSize(rd, defaultBufSize);
    if err != nil {
        // cannot happen - defaultBufSize is a valid size
        panic("bufio: NewBufRead: ", err.String());
    }
    return b;
}

// UnreadByteのlastbyte利用
if b.r == b.w && b.lastbyte >= 0 {
    b.w = 1;
    b.r = 0;
    b.buf[0] = byte(b.lastbyte);
    b.lastbyte = -1;
    return nil;
}

// BufReadWrite型の追加
type BufReadWrite struct {
    *BufRead;
    *BufWrite;
}

func NewBufReadWrite(r *BufRead, w *BufWrite) *BufReadWrite {
    return &BufReadWrite{r, w}
}

src/lib/http/server.go

// Conn構造体の変更
type Conn struct {
    RemoteAddr string;    // network address of remote side
    Req *Request;    // current HTTP request

    fd io.ReadWriteClose;    // i/o connection
    buf *bufio.BufReadWrite;    // buffered fd
    handler Handler;    // request handler
    hijacked bool;    // connection has been hijacked by handler

    // state for the current reply
    closeAfterReply bool;    // close connection after this reply
    chunking bool;    // using chunked transfer encoding for reply body
    wroteHeader bool;    // reply header has been written
    header map[string] string;    // reply header parameters
}

// newConnでのBufReadWriteの利用
func newConn(rwc io.ReadWriteClose, raddr string, handler Handler) (c *Conn, err *os.Error) {
    // ...
    br := bufio.NewBufRead(rwc);
    bw := bufio.NewBufWrite(rwc);
    c.buf = bufio.NewBufReadWrite(br, bw);
    return c, nil
}

// Hijackメソッドの追加
func (c *Conn) Hijack() (fd io.ReadWriteClose, buf *bufio.BufReadWrite, err *os.Error) {
    if c.hijacked {
        return nil, nil, ErrHijacked;
    }
    c.hijacked = true;
    fd = c.fd;
    buf = c.buf;
    c.fd = nil;
    c.buf = nil;
    return;
}

src/lib/http/status.go (新規ファイル)

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// HTTP status codes.  See RFC 2616.

package http

const (
    StatusContinue = 100;
    // ... 多数のHTTPステータスコード定数
)

var statusText = map[int]string {
    StatusContinue:            "Continue",
    // ... ステータスコードとテキストのマッピング
}

src/lib/io/io.go

// Closeインターフェースの追加
type Close interface {
    Close() *os.Error;
}

コアとなるコードの解説

bufioパッケージの最適化とAPI変更

bufioパッケージの変更は、Go言語のI/O処理の効率と使いやすさを向上させることを目的としています。

  • 大規模コピーの回避: ReadおよびWriteメソッドにおける変更は、バッファリングI/Oの一般的な最適化パターンです。読み書きするデータ量が内部バッファサイズよりも大きい場合、中間バッファを介さずに直接基盤となるI/Oソース/シンクとやり取りすることで、メモリコピーの回数を減らし、CPUサイクルとメモリ帯域幅を節約します。これは特に、大きなファイルを読み書きする場合や、高スループットが求められるネットワークアプリケーションで顕著な性能向上をもたらします。
  • NewBufRead, NewBufWriteのエラー返却廃止: これはAPIの簡素化と、Go言語におけるエラーハンドリングの進化を示唆しています。defaultBufSizeが常に有効な値であるため、これらの関数が失敗する唯一のケースは、プログラミング上の誤り(例えば、NewBufReadSizeに無効なサイズを渡すなど)であると判断されました。このような「発生してはならない」エラーに対しては、エラーを返すのではなくpanicを発生させることで、開発者に即座に問題があることを知らせる設計が採用されました。これにより、呼び出し側はこれらの関数の戻り値を常に有効なものとして扱うことができ、if err != nilのチェックを省略できます。
  • BufReadWriteの導入: BufReadWriteは、BufReadBufWriteを匿名フィールドとして埋め込むことで、Goのコンポジション(合成)の原則を効果的に利用しています。これにより、BufReadWrite型のインスタンスは、BufReadBufWriteの両方のメソッド(例: Read, Write, Flushなど)を直接呼び出すことができます。これは、HTTPコネクションのように、同じ基盤となるI/Oストリームに対して読み書き両方のバッファリングが必要なシナリオで非常に便利です。コードの重複を避け、より凝集度の高い設計を可能にします。
  • UnreadByteの改善: lastbyteフィールドの追加とそれを利用したUnreadByteのロジックは、より柔軟なパーシングを可能にします。例えば、特定のデリミタを読み込んだ後、そのデリミタを「アンリード」して、別のパーサーに処理を委ねるような場合に役立ちます。

ioパッケージのCloseインターフェース

io.Closeインターフェースの追加は、Go言語のインターフェース設計の典型的な例です。Close()メソッドを持つ任意の型がこのインターフェースを満たすことになり、これにより、ファイル、ネットワーク接続、データベース接続など、様々なリソースに対して統一的なクリーンアップ処理を記述できるようになります。これは、リソース管理のコードを簡素化し、汎用的な関数やライブラリを構築する上で非常に重要です。

httpパッケージのConn.Hijack()

http.Conn.Hijack()メソッドは、Goのnet/httpパッケージが単なるHTTPサーバーにとどまらず、より低レベルのネットワークプロトコルを扱うための強力なメカニズムを提供します。

  • 動作原理: Hijack()が呼び出されると、http.Connは自身の内部状態(バッファリングされたI/Oオブジェクトや基盤となるネットワーク接続)を呼び出し元に渡し、それ以降のHTTPプロトコル処理から手を引きます。これにより、ハンドラは返されたfd(ファイルディスクリプタ、通常はTCPソケット)とbuf(バッファリングされたリーダー/ライター)を直接操作し、HTTP以外のプロトコル(例: WebSocketのハンドシェイク後のフレーム処理)を実装できます。
  • ユースケース: 最も一般的なユースケースはWebSocketサーバーの実装です。HTTPのUpgradeヘッダを介してWebSocketプロトコルへの切り替えが要求された場合、サーバーはHijack()を呼び出してTCP接続を奪い、その上でWebSocketフレームの送受信を開始します。
  • 注意点: Hijack()を呼び出したハンドラは、返されたfdbufのライフサイクル管理(読み書き、エラーハンドリング、そして最終的なClose())の全責任を負います。サーバーはこれらのリソースを自動的にクリーンアップしなくなるため、リソースリークを防ぐために適切な処理が必要です。

関連リンク

参考にした情報源リンク

  • Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語のエラーハンドリングに関する議論(初期の設計思想): Go言語の初期の設計ドキュメントやメーリングリストのアーカイブ(例: golang-nuts)には、エラーハンドリングに関する多くの議論が含まれています。
  • Go言語のnet/httpパッケージの歴史と設計に関する記事やトーク。
  • Go言語のbufioパッケージの内部実装に関する解説記事。
  • RFC 2616 (HTTP/1.1): https://datatracker.ietf.org/doc/html/rfc2616 (HTTPステータスコードの定義など)