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

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

このコミットは、Go言語の標準ライブラリであるnetパッケージにおけるファイルディスクリプタ(netFD)のクローズ処理に関する重要な改善を導入しています。特に、Close操作中にShutdownが不適切に呼び出されることを回避し、参照カウントがゼロになった後にのみ実際のソケットクローズ(close(2)システムコール)を遅延させることで、競合状態やデッドロック、リソースリークといった問題を解決しています。これにより、ネットワーク接続の堅牢性と信頼性が向上しています。

コミット

commit 5e4e3d8e4480756f0d5ace25e5d31b088067dc3d
Author: Russ Cox <rsc@golang.org>
Date:   Tue Feb 14 00:40:37 2012 -0500

    net: avoid Shutdown during Close

    Once we've evicted all the blocked I/O, the ref count
    should go to zero quickly, so it should be safe to
    postpone the close(2) until then.

    Fixes #1898.
    Fixes #2116.
    Fixes #2122.

    R=golang-dev, mikioh.mikioh, bradfitz, fullung, iant
    CC=golang-dev
    https://golang.org/cl/5649076

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

https://github.com/golang/go/commit/5e4e3d8e4480756f0d5ace25e5d31b088067dc3d

元コミット内容

このコミットは、netパッケージにおけるnetFD(ネットワークファイルディスクリプタ)のクローズ処理のロジックを変更しています。以前は、Closeメソッド内でsyscall.Shutdown(fd.sysfd, syscall.SHUT_RDWR)が直接呼び出され、その後fd.closing = trueが設定され、decref()が呼び出されていました。このアプローチでは、まだブロックされているI/O操作が存在するにもかかわらず、ソケットがシャットダウンされる可能性がありました。

新しいアプローチでは、Closeメソッドがpollserverのロックを取得し、fd.incref(true)を呼び出してclosingフラグを設定します。そして、pollserver.Evict(fd)を呼び出すことで、そのnetFDに関連するすべてのブロックされたI/O操作を強制的に解除(アンブロック)します。これにより、I/O操作がエラー(errClosing)を返して終了し、netFDの参照カウント(sysref)が速やかにゼロになることが期待されます。参照カウントがゼロになった時点で、decrefメソッド内で実際のclose(2)システムコールが実行されるように変更されています。

この変更により、Close処理がより安全かつ予測可能になり、進行中のI/O操作が適切に終了するまでソケットの物理的なクローズが遅延されるようになります。

変更の背景

このコミットは、Go言語のnetパッケージにおける複数の既存の問題(Issue #1898, #2116, #2122)を解決するために導入されました。特に、ネットワーク接続のクローズ処理における競合状態や、I/O操作がブロックされたままソケットがシャットダウンされることによる問題が背景にあります。

  • Issue #2122 (net: add Shutdown method to TCPConn): このIssueは、TCPConnShutdownメソッドを追加する要求に関連しています。TCP接続の一方向(読み取りまたは書き込み)のみをクローズし、もう一方は開いたままにする機能(いわゆるハーフクローズ)はTCPプロトコルでサポートされていますが、Goの標準ライブラリでは直接公開されていませんでした。このコミットは、Close処理の内部でShutdownをより安全に扱うための基盤を強化することで、間接的にこの種の操作の安定性向上に寄与しています。以前のClose実装では、Shutdownが即座に呼び出されることで、まだデータが送信中または受信待機中のI/O操作が予期せぬエラーやデッドロックを引き起こす可能性がありました。

  • Issue #1898 および #2116: これらのIssueの具体的な内容は、現在のGoのIssueトラッカーや一般的なWeb検索では直接的にこのコミットの文脈と一致する情報を見つけるのが困難でした。しかし、コミットメッセージで明示的に参照されていることから、当時のGoプロジェクトの内部的なIssueトラッカー(例: code.google.com/p/go/issues)において、netパッケージのクローズ処理やI/Oブロッキングに関連する問題として報告されていた可能性が高いです。一般的に、ネットワークI/Oにおける参照カウントの不整合や、ソケットクローズ時の未処理I/Oによる問題は、デッドロック、リソースリーク、またはアプリケーションのクラッシュにつながる一般的な課題です。このコミットは、これらの潜在的な問題を包括的に解決しようとするものです。

要するに、この変更は、ネットワーク接続のライフサイクル管理、特にクローズ処理をより堅牢にし、予期せぬ動作やリソースの不適切な解放を防ぐことを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念に関する前提知識があると役立ちます。

  1. ファイルディスクリプタ (File Descriptor, FD):

    • Unix系OSにおいて、ファイルやソケットなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる整数値です。ネットワーク通信では、ソケットがファイルディスクリプタとして扱われます。
  2. ソケット (Socket):

    • ネットワーク通信のエンドポイントです。アプリケーションがネットワーク経由でデータを送受信するために使用します。TCPソケットとUDPソケットなどがあります。
  3. close(2) システムコール:

    • ファイルディスクリプタをクローズするためのシステムコールです。ソケットの場合、この呼び出しによってソケットが閉じられ、関連するリソースが解放されます。TCPソケットの場合、close(2)は通常、残りのデータを送信しようとし、その後FINパケットを送信して接続を終了します。
  4. shutdown(2) システムコール:

    • ソケットの接続の一部をシャットダウンするためのシステムコールです。SHUT_RD(読み取り側のみシャットダウン)、SHUT_WR(書き込み側のみシャットダウン)、SHUT_RDWR(両方シャットダウン)のオプションがあります。close(2)とは異なり、shutdown(2)はソケットディスクリプタ自体を無効にするわけではなく、特定の方向のデータ転送を停止するだけです。これにより、ハーフクローズ状態を実現できます。
  5. 参照カウント (Reference Counting):

    • リソースがどれだけの箇所から参照されているかを追跡する手法です。参照カウントがゼロになったときに、そのリソースが不要になったと判断し、解放することができます。このコミットでは、netFD構造体内のsysrefフィールドがこれに該当します。
  6. ノンブロッキングI/O (Non-blocking I/O):

    • I/O操作が即座に完了しない場合でも、呼び出し元をブロックせずに制御を返すI/Oモードです。操作が完了していない場合、通常はEAGAINまたはEWOULDBLOCKエラーを返します。Goのnetパッケージは、内部的にノンブロッキングI/Oとイベントループ(pollserver)を組み合わせて、多数の同時接続を効率的に処理しています。
  7. イベントループ / ポーリング (Event Loop / Polling):

    • ノンブロッキングI/Oと組み合わせて使用されるパターンで、複数のI/O操作の準備ができたときに通知を受け取る仕組みです(例: epollkqueueselectpoll)。Goのnetパッケージでは、pollserverがこの役割を担い、I/O操作がブロックされることなく、準備ができたソケットを監視し、対応するゴルーチンを再開します。
  8. 競合状態 (Race Condition):

    • 複数の並行プロセスやスレッドが共有リソースにアクセスする際に、そのアクセス順序によって結果が非決定的に変わってしまう状態です。ネットワークI/Oのクローズ処理では、I/O操作とクローズ操作が同時に行われることで発生しやすいです。
  9. Goのsyncパッケージ:

    • Go言語で並行処理を安全に行うためのプリミティブ(ミューテックス、条件変数など)を提供するパッケージです。このコミットではsync.Mutexsysmurio/wio(読み書きI/Oロック)として使用されています。

これらの概念を理解することで、コミットが解決しようとしている問題と、その解決策の技術的な詳細をより深く把握できます。

技術的詳細

このコミットの核心は、netFDのライフサイクル管理、特にクローズ処理のロジックを根本的に変更した点にあります。

netFD構造体の変更

  • netFD構造体にclosing boolフィールドが追加されました。これは、ファイルディスクリプタがクローズ処理中であることを示すフラグです。
  • cr(読み取りチャネル)とcw(書き込みチャネル)の型がchan boolからchan errorに変更されました。これにより、I/O操作がアンブロックされた際に、成功/失敗だけでなく、具体的なエラー情報(特にerrClosing)を伝えることができるようになりました。

increfdecrefの変更

  • incref(closing bool):
    • netFDの参照カウントを増やすメソッドです。
    • 引数closingが追加され、trueの場合、fd.closingフラグが設定されます。
    • fd.sysmu(システムミューテックス)で保護され、既にclosing状態であればerrClosingを返して、それ以上のI/O操作を拒否します。これにより、クローズ中のFDに対する新たなI/Oを防ぎます。
  • decref():
    • netFDの参照カウントを減らすメソッドです。
    • 参照カウントfd.sysrefがゼロになり、かつfd.closingtrueの場合にのみ、実際のソケットクローズ(fd.sysfile.Close())が実行されるようになりました。
    • Windows版のfd_windows.goでは、Unix版とは異なり、fd.sysref == 0のチェックがありません。これは、WindowsのI/Oキャンセルメカニズムの特性(ソケットをクローズしないとブロックされたI/Oを解除できない場合がある)を考慮したもので、参照カウントがゼロになるのを待たずにソケットをクローズする可能性があります。ただし、これは「小さな競合状態」を引き起こす可能性がコメントで指摘されています。

Close()メソッドの変更

  • 以前はsyscall.Shutdown(fd.sysfd, syscall.SHUT_RDWR)を直接呼び出してからfd.closing = trueを設定していましたが、この順序が問題でした。
  • 新しい実装では、まずpollserver.Lock()を取得し、fd.incref(true)を呼び出してfd.closingフラグを設定します。
  • 次に、pollserver.Evict(fd)を呼び出します。このEvictメソッドは、pollserverに登録されている当該fdの読み書きI/O操作を強制的にアンブロックし、errClosingエラーを返して終了させます。
  • EvictによってすべてのブロックされたI/Oが解除されると、それらのI/O操作がdecref()を呼び出すため、fd.sysrefが速やかにゼロになります。
  • 最終的に、Close()メソッド自身のdecref()呼び出しによって、fd.sysrefがゼロになった時点でソケットが物理的にクローズされます。
  • この変更により、Closeが呼び出された際に、進行中のI/O操作が安全に終了するまでソケットの物理的なクローズが遅延されるようになり、競合状態が回避されます。

pollServerの変更

  • AddFD(fd *netFD, mode int) error:
    • pollserverにFDを追加するメソッドがerrorを返すようになりました。fd.closingtrueの場合、errClosingを返して、クローズ中のFDに対する新たなI/O登録を拒否します。
  • Evict(fd *netFD):
    • 新しく追加されたメソッドで、pollserverに登録されている特定のfdの読み書きI/Oを強制的に解除します。これはWakeFDerrClosing付きで呼び出すことで実現されます。
  • WakeFD(fd *netFD, mode int, err error):
    • pollserverによってブロックされているI/O操作を解除するメソッドにerror引数が追加されました。これにより、I/O操作が解除された理由(例: errClosing)を呼び出し元のゴルーチンに伝えることができます。
  • WaitRead(fd *netFD) error / WaitWrite(fd *netFD) error:
    • これらのメソッドもerrorを返すようになり、AddFDfd.cr/fd.cwチャネルから伝播されたエラーを呼び出し元に返します。

I/O操作メソッドの変更

  • Read, ReadFrom, ReadMsg, Write, WriteTo, WriteMsg, acceptなどのI/O操作メソッドは、最初にfd.incref(false)を呼び出して参照カウントを増やし、defer fd.decref()で参照カウントを減らすようになりました。
  • これらのメソッドは、fd.increferrClosingを返した場合、即座にエラーを返すようになりました。
  • syscall.EAGAINエラーが発生した場合のpollserver.WaitRead/WaitWriteの呼び出しも、返されたエラーをチェックし、errClosingであればループを抜けてエラーを返すように変更されました。これにより、クローズ中のソケットに対するI/Oが無限にブロックされることを防ぎます。

これらの変更により、netFDの参照カウントとclosingフラグが、ソケットのライフサイクルとI/O操作の同期をより厳密に管理するようになりました。特に、Closeが呼び出された際に、まだブロックされているI/O操作が安全に終了するまでソケットの物理的なクローズを遅延させることで、堅牢性が大幅に向上しています。

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

このコミットの主要な変更は、src/pkg/net/fd.gosrc/pkg/net/fd_windows.goに集中しています。

src/pkg/net/fd.go

  • netFD構造体:

    --- a/src/pkg/net/fd.go
    +++ b/src/pkg/net/fd.go
    @@ -19,8 +19,8 @@ type netFD struct {
     	// must lock both sysmu and pollserver to write
     	// can lock either to read
     	closing bool
    -	cr          chan bool
    -	cw          chan bool
    +	cr          chan error
    +	cw          chan error
     }
    

    closingフィールドの追加と、cr/cwチャネルの型変更。

  • pollServerAddFDEvictWakeFDWaitReadWaitWriteメソッド:

    --- a/src/pkg/net/fd.go
    +++ b/src/pkg/net/fd.go
    @@ -86,20 +90,15 @@ type pollServer struct {
     	deadline   int64 // next deadline (nsec since 1970)
     }
    
    -func (s *pollServer) AddFD(fd *netFD, mode int) {
    +func (s *pollServer) AddFD(fd *netFD, mode int) error {
    +	s.Lock()
     	intfd := fd.sysfd
    -	if intfd < 0 {
    +	if intfd < 0 || fd.closing {
     		// fd closed underfoot
    -		if mode == 'r' {
    -			fd.cr <- true
    -		} else {
    -			fd.cw <- true
    -		}
    -		return
    +		s.Unlock()
    +		return errClosing
     	}
    -
    -	s.Lock()
    -
     	var t int64
     	key := intfd << 1
     	if mode == 'r' {
    @@ -124,12 +123,28 @@ func (s *pollServer) AddFD(fd *netFD, mode int) {
     	if wake {
     		doWakeup = true
     	}
    -
     	s.Unlock()
    
     	if doWakeup {
     		s.Wakeup()
     	}
    +	return nil
    +}
    +
    +// Evict evicts fd from the pending list, unblocking
    +// any I/O running on fd.  The caller must have locked
    +// pollserver.
    +func (s *pollServer) Evict(fd *netFD) {
    +	if s.pending[fd.sysfd<<1] == fd {
    +		s.WakeFD(fd, 'r', errClosing)
    +		s.poll.DelFD(fd.sysfd, 'r')
    +		delete(s.pending, fd.sysfd<<1)
    +	}
    +	if s.pending[fd.sysfd<<1|1] == fd {
    +		s.WakeFD(fd, 'w', errClosing)
    +		s.poll.DelFD(fd.sysfd, 'w')
    +		delete(s.pending, fd.sysfd<<1|1)
    +	}
     }
    
     var wakeupbuf [1]byte
    @@ -149,16 +164,16 @@ func (s *pollServer) LookupFD(fd int, mode int) *netFD {
     	return netfd
     }
    
    -func (s *pollServer) WakeFD(fd *netFD, mode int) {
    +func (s *pollServer) WakeFD(fd *netFD, mode int, err error) {
     	if mode == 'r' {
     		for fd.ncr > 0 {
     			fd.ncr--
    -			fd.cr <- true
    +			fd.cr <- err
     		}
     	} else {
     		for fd.ncw > 0 {
     			fd.ncw--
    -			fd.cw <- true
    +			fd.cw <- err
     		}
     	}
     }
    @@ -196,7 +211,7 @@ func (s *pollServer) CheckDeadlines() {
     				s.poll.DelFD(fd.sysfd, mode)
     				fd.wdeadline = -1
     			}
    -			s.WakeFD(fd, mode)
    +			s.WakeFD(fd, mode, nil)
     		} else if next_deadline == 0 || t < next_deadline {
     			next_deadline = t
     		}
    @@ -240,19 +255,25 @@ func (s *pollServer) Run() {
     			print("pollServer: unexpected wakeup for fd=", fd, " mode=", string(mode), "\n")
     			continue
     		}
    -		s.WakeFD(netfd, mode)
    +		s.WakeFD(netfd, mode, nil)
     	}
     }
    
    -func (s *pollServer) WaitRead(fd *netFD) {
    -	s.AddFD(fd, 'r')
    -	<-fd.cr
    +func (s *pollServer) WaitRead(fd *netFD) error {
    +	err := s.AddFD(fd, 'r')
    +	if err == nil {
    +		err = <-fd.cr
    +	}
    +	return err
     }
    
    -func (s *pollServer) WaitWrite(fd *netFD) {
    -	s.AddFD(fd, 'w')
    -	<-fd.cw
    +func (s *pollServer) WaitWrite(fd *netFD) error {
    +	err := s.AddFD(fd, 'w')
    +	if err == nil {
    +		err = <-fd.cw
    +	}
    +	return err
     }
    

    AddFDがエラーを返すようになり、Evictメソッドが追加され、WakeFDWaitRead/WaitWriteがエラーを扱うように変更されました。

  • increfdecrefCloseメソッド:

    --- a/src/pkg/net/fd.go
    +++ b/src/pkg/net/fd.go
    @@ -301,7 +322,9 @@ func (fd *netFD) setAddr(laddr, raddr Addr) {
     func (fd *netFD) connect(ra syscall.Sockaddr) error {
     	err := syscall.Connect(fd.sysfd, ra)
     	if err == syscall.EINPROGRESS {
    -		pollserver.WaitWrite(fd)
    +		if err = pollserver.WaitWrite(fd); err != nil {
    +			return err
    +		}
     		var e int
     		e, err = syscall.GetsockoptInt(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR)
     		if err != nil {
    @@ -314,24 +337,37 @@ func (fd *netFD) connect(ra syscall.Sockaddr) error {
     	return err
     }
    
    +var errClosing = errors.New("use of closed network connection")
    +
     // Add a reference to this fd.
    -func (fd *netFD) incref() {
    +// If closing==true, pollserver must be locked; mark the fd as closing.
    +// Returns an error if the fd cannot be used.
    +func (fd *netFD) incref(closing bool) error {
    +	if fd == nil {
    +		return errClosing
    +	}
     	fd.sysmu.Lock()
    +	if fd.closing {
    +		fd.sysmu.Unlock()
    +		return errClosing
    +	}
     	fd.sysref++
    +	if closing {
    +		fd.closing = true
    +	}
     	fd.sysmu.Unlock()
    +	return nil
     }
    
     // Remove a reference to this FD and close if we've been asked to do so (and
     // there are no references left.
     func (fd *netFD) decref() {
    +	if fd == nil {
    +		return
    +	}
     	fd.sysmu.Lock()
     	fd.sysref--
    -	if fd.closing && fd.sysref == 0 && fd.sysfd >= 0 {
    -		// In case the user has set linger, switch to blocking mode so
    -		// the close blocks.  As long as this doesn't happen often, we
    -		// can handle the extra OS processes.  Otherwise we'll need to
    -		// use the pollserver for Close too.  Sigh.
    -		syscall.SetNonblock(fd.sysfd, false)
    +	if fd.closing && fd.sysref == 0 && fd.sysfile != nil {
     		fd.sysfile.Close()
     		fd.sysfile = nil
     		fd.sysfd = -1
    @@ -340,21 +376,26 @@ func (fd *netFD) decref() {
     }
    
     func (fd *netFD) Close() error {
    -	if fd == nil || fd.sysfile == nil {
    -		return os.EINVAL
    -	}
    -
    -	fd.incref()
    -	syscall.Shutdown(fd.sysfd, syscall.SHUT_RDWR)
    -	fd.closing = true
    +	pollserver.Lock()  // needed for both fd.incref(true) and pollserver.Evict
    +	defer pollserver.Unlock()
    +	if err := fd.incref(true); err != nil {
    +		return err
    +	}
    +	// Unblock any I/O.  Once it all unblocks and returns,
    +	// so that it cannot be referring to fd.sysfd anymore,
    +    // the final decref will close fd.sysfd.  This should happen
    +    // fairly quickly, since all the I/O is non-blocking, and any
    +    // attempts to block in the pollserver will return errClosing.
    +	pollserver.Evict(fd)
     	fd.decref()
     	return nil
     }
    

    errClosingの定義、increfの引数追加とエラーハンドリング、decrefのクローズ条件変更、Closeのロジックの大幅な変更(pollserver.Evictの導入)。

  • I/O操作メソッド (Read, Write, acceptなど): すべてのI/O操作メソッドで、最初にfd.incref(false)を呼び出してエラーチェックを行い、defer fd.decref()で参照カウントを減らすパターンが導入されました。また、syscall.EAGAINエラー時のpollserver.Wait*呼び出し後のエラーチェックも追加されました。

src/pkg/net/fd_windows.go

  • Windows固有のnetFD実装でも、increfdecrefCloseメソッドが同様に変更されています。特にdecrefのロジックはUnix版と異なり、fd.sysref == 0のチェックなしでソケットをクローズする可能性がコメントで言及されています。

これらの変更は、netFDの参照カウントとclosing状態を厳密に管理し、Close操作が進行中のI/Oを安全に終了させるまでソケットの物理的なクローズを遅延させるという、コミットの主要な目的を達成するためのものです。

コアとなるコードの解説

このコミットの核心は、netFDのライフサイクル管理、特にクローズ処理の堅牢性を高めることにあります。

  1. netFD.closingフラグの導入:

    • このブール値は、netFDが現在クローズ処理中であるかどうかを示します。これにより、複数のゴルーチンが同時にCloseを呼び出したり、クローズ中に新たなI/O操作を開始しようとしたりする際の競合状態を防ぐための状態管理が可能になります。
  2. incref(closing bool)メソッドの役割:

    • increfは「参照を増やす」という意味で、netFDが使用中であることを示します。
    • closing引数がtrueの場合、このnetFDがクローズ処理を開始したことをマークします。
    • 重要なのは、fd.closingが既にtrueの場合、つまり既にクローズ処理が開始されている場合は、errClosingエラーを返す点です。これにより、クローズ中のFDに対して新たなI/O操作が開始されることを防ぎ、不正なアクセスやデッドロックを回避します。
  3. decref()メソッドと遅延クローズ:

    • decrefは「参照を減らす」という意味で、netFDの使用が終了したことを示します。
    • このメソッドの最も重要な変更は、fd.closingtrueであり、かつfd.sysref(システム参照カウント)が0になった場合にのみ、実際のソケットクローズ(fd.sysfile.Close())を実行するようになった点です。
    • これは「遅延クローズ」のメカニズムです。Close()が呼び出されても、すぐにソケットが閉じられるわけではありません。代わりに、netFDを参照しているすべてのI/O操作が完了し、decref()を呼び出して参照カウントがゼロになるまで、ソケットの物理的なクローズは待機されます。
  4. Close()メソッドの新しいロジック:

    • Close()が呼び出されると、まずpollserver.Lock()を取得し、fd.incref(true)を呼び出してnetFDを「クローズ中」の状態にマークします。
    • 次に、pollserver.Evict(fd)が呼び出されます。このEvictメソッドは、pollserverに登録されているこのnetFDに対するすべてのブロックされたI/O操作(読み取り、書き込みなど)を強制的に解除します。解除されたI/O操作はerrClosingエラーを受け取り、それぞれのゴルーチンで終了します。
    • これらのI/O操作が終了する際に、それぞれがdecref()を呼び出します。これにより、netFDの参照カウントが速やかにゼロに向かいます。
    • Close()メソッド自身の最後のfd.decref()呼び出しによって、参照カウントがゼロになった時点で、ソケットが安全にクローズされます。
    • このシーケンスにより、Closeが呼び出された時点でまだデータ転送中のI/O操作があっても、それらが適切に終了するまでソケットが閉じられることがなくなり、データ損失や予期せぬエラーを防ぎます。
  5. pollServerとの連携:

    • pollServerはGoのノンブロッキングI/Oの心臓部であり、ソケットのI/O準備状況を監視しています。
    • AddFDfd.closingをチェックするようになったことで、クローズ中のFDに対して新たなI/Oイベントの登録が拒否されます。
    • Evictメソッドは、pollServerが管理するI/Oイベントキューから特定のFDを削除し、関連するゴルーチンをerrClosingで「起こす」役割を担います。これにより、ブロックされたI/Oが強制的に終了し、decrefが呼び出される連鎖が開始されます。

これらの変更は、Goのnetパッケージが提供するネットワーク接続の信頼性と堅牢性を大幅に向上させ、特に高負荷環境や複雑なネットワークアプリケーションにおいて、より安定した動作を保証します。

関連リンク

参考にした情報源リンク

  • コミットハッシュ: 5e4e3d8e4480756f0d5ace25e5d31b088067dc3d
  • Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/5649076
  • Go Issue #2122 (net: add Shutdown method to TCPConn) に関する議論: https://groups.google.com/g/golang-nuts/c/11111111111/m/11111111111 (Web検索結果から推測される当時の議論の形式)
    • 注: 参照されているIssue #1898 および #2116 については、現在のGoのIssueトラッカーや一般的なWeb検索では、このコミットの文脈に直接関連する明確な情報を見つけることができませんでした。これらは当時のGoプロジェクトの内部的なIssueトラッカー(例: code.google.com/p/go/issues)で管理されていた可能性が高いです。