[インデックス 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は、
TCPConn
にShutdown
メソッドを追加する要求に関連しています。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による問題は、デッドロック、リソースリーク、またはアプリケーションのクラッシュにつながる一般的な課題です。このコミットは、これらの潜在的な問題を包括的に解決しようとするものです。
要するに、この変更は、ネットワーク接続のライフサイクル管理、特にクローズ処理をより堅牢にし、予期せぬ動作やリソースの不適切な解放を防ぐことを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念に関する前提知識があると役立ちます。
-
ファイルディスクリプタ (File Descriptor, FD):
- Unix系OSにおいて、ファイルやソケットなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる整数値です。ネットワーク通信では、ソケットがファイルディスクリプタとして扱われます。
-
ソケット (Socket):
- ネットワーク通信のエンドポイントです。アプリケーションがネットワーク経由でデータを送受信するために使用します。TCPソケットとUDPソケットなどがあります。
-
close(2)
システムコール:- ファイルディスクリプタをクローズするためのシステムコールです。ソケットの場合、この呼び出しによってソケットが閉じられ、関連するリソースが解放されます。TCPソケットの場合、
close(2)
は通常、残りのデータを送信しようとし、その後FINパケットを送信して接続を終了します。
- ファイルディスクリプタをクローズするためのシステムコールです。ソケットの場合、この呼び出しによってソケットが閉じられ、関連するリソースが解放されます。TCPソケットの場合、
-
shutdown(2)
システムコール:- ソケットの接続の一部をシャットダウンするためのシステムコールです。
SHUT_RD
(読み取り側のみシャットダウン)、SHUT_WR
(書き込み側のみシャットダウン)、SHUT_RDWR
(両方シャットダウン)のオプションがあります。close(2)
とは異なり、shutdown(2)
はソケットディスクリプタ自体を無効にするわけではなく、特定の方向のデータ転送を停止するだけです。これにより、ハーフクローズ状態を実現できます。
- ソケットの接続の一部をシャットダウンするためのシステムコールです。
-
参照カウント (Reference Counting):
- リソースがどれだけの箇所から参照されているかを追跡する手法です。参照カウントがゼロになったときに、そのリソースが不要になったと判断し、解放することができます。このコミットでは、
netFD
構造体内のsysref
フィールドがこれに該当します。
- リソースがどれだけの箇所から参照されているかを追跡する手法です。参照カウントがゼロになったときに、そのリソースが不要になったと判断し、解放することができます。このコミットでは、
-
ノンブロッキングI/O (Non-blocking I/O):
- I/O操作が即座に完了しない場合でも、呼び出し元をブロックせずに制御を返すI/Oモードです。操作が完了していない場合、通常は
EAGAIN
またはEWOULDBLOCK
エラーを返します。Goのnet
パッケージは、内部的にノンブロッキングI/Oとイベントループ(pollserver
)を組み合わせて、多数の同時接続を効率的に処理しています。
- I/O操作が即座に完了しない場合でも、呼び出し元をブロックせずに制御を返すI/Oモードです。操作が完了していない場合、通常は
-
イベントループ / ポーリング (Event Loop / Polling):
- ノンブロッキングI/Oと組み合わせて使用されるパターンで、複数のI/O操作の準備ができたときに通知を受け取る仕組みです(例:
epoll
、kqueue
、select
、poll
)。Goのnet
パッケージでは、pollserver
がこの役割を担い、I/O操作がブロックされることなく、準備ができたソケットを監視し、対応するゴルーチンを再開します。
- ノンブロッキングI/Oと組み合わせて使用されるパターンで、複数のI/O操作の準備ができたときに通知を受け取る仕組みです(例:
-
競合状態 (Race Condition):
- 複数の並行プロセスやスレッドが共有リソースにアクセスする際に、そのアクセス順序によって結果が非決定的に変わってしまう状態です。ネットワークI/Oのクローズ処理では、I/O操作とクローズ操作が同時に行われることで発生しやすいです。
-
Goの
sync
パッケージ:- Go言語で並行処理を安全に行うためのプリミティブ(ミューテックス、条件変数など)を提供するパッケージです。このコミットでは
sync.Mutex
がsysmu
やrio
/wio
(読み書きI/Oロック)として使用されています。
- Go言語で並行処理を安全に行うためのプリミティブ(ミューテックス、条件変数など)を提供するパッケージです。このコミットでは
これらの概念を理解することで、コミットが解決しようとしている問題と、その解決策の技術的な詳細をより深く把握できます。
技術的詳細
このコミットの核心は、netFD
のライフサイクル管理、特にクローズ処理のロジックを根本的に変更した点にあります。
netFD
構造体の変更
netFD
構造体にclosing bool
フィールドが追加されました。これは、ファイルディスクリプタがクローズ処理中であることを示すフラグです。cr
(読み取りチャネル)とcw
(書き込みチャネル)の型がchan bool
からchan error
に変更されました。これにより、I/O操作がアンブロックされた際に、成功/失敗だけでなく、具体的なエラー情報(特にerrClosing
)を伝えることができるようになりました。
incref
とdecref
の変更
incref(closing bool)
:netFD
の参照カウントを増やすメソッドです。- 引数
closing
が追加され、true
の場合、fd.closing
フラグが設定されます。 fd.sysmu
(システムミューテックス)で保護され、既にclosing
状態であればerrClosing
を返して、それ以上のI/O操作を拒否します。これにより、クローズ中のFDに対する新たなI/Oを防ぎます。
decref()
:netFD
の参照カウントを減らすメソッドです。- 参照カウント
fd.sysref
がゼロになり、かつfd.closing
がtrue
の場合にのみ、実際のソケットクローズ(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.closing
がtrue
の場合、errClosing
を返して、クローズ中のFDに対する新たなI/O登録を拒否します。
Evict(fd *netFD)
:- 新しく追加されたメソッドで、
pollserver
に登録されている特定のfd
の読み書きI/Oを強制的に解除します。これはWakeFD
をerrClosing
付きで呼び出すことで実現されます。
- 新しく追加されたメソッドで、
WakeFD(fd *netFD, mode int, err error)
:pollserver
によってブロックされているI/O操作を解除するメソッドにerror
引数が追加されました。これにより、I/O操作が解除された理由(例:errClosing
)を呼び出し元のゴルーチンに伝えることができます。
WaitRead(fd *netFD) error
/WaitWrite(fd *netFD) error
:- これらのメソッドも
error
を返すようになり、AddFD
やfd.cr
/fd.cw
チャネルから伝播されたエラーを呼び出し元に返します。
- これらのメソッドも
I/O操作メソッドの変更
Read
,ReadFrom
,ReadMsg
,Write
,WriteTo
,WriteMsg
,accept
などのI/O操作メソッドは、最初にfd.incref(false)
を呼び出して参照カウントを増やし、defer fd.decref()
で参照カウントを減らすようになりました。- これらのメソッドは、
fd.incref
がerrClosing
を返した場合、即座にエラーを返すようになりました。 syscall.EAGAIN
エラーが発生した場合のpollserver.WaitRead
/WaitWrite
の呼び出しも、返されたエラーをチェックし、errClosing
であればループを抜けてエラーを返すように変更されました。これにより、クローズ中のソケットに対するI/Oが無限にブロックされることを防ぎます。
これらの変更により、netFD
の参照カウントとclosing
フラグが、ソケットのライフサイクルとI/O操作の同期をより厳密に管理するようになりました。特に、Close
が呼び出された際に、まだブロックされているI/O操作が安全に終了するまでソケットの物理的なクローズを遅延させることで、堅牢性が大幅に向上しています。
コアとなるコードの変更箇所
このコミットの主要な変更は、src/pkg/net/fd.go
とsrc/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
チャネルの型変更。 -
pollServer
のAddFD
、Evict
、WakeFD
、WaitRead
、WaitWrite
メソッド:--- 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
メソッドが追加され、WakeFD
とWaitRead
/WaitWrite
がエラーを扱うように変更されました。 -
incref
、decref
、Close
メソッド:--- 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
実装でも、incref
、decref
、Close
メソッドが同様に変更されています。特にdecref
のロジックはUnix版と異なり、fd.sysref == 0
のチェックなしでソケットをクローズする可能性がコメントで言及されています。
これらの変更は、netFD
の参照カウントとclosing
状態を厳密に管理し、Close
操作が進行中のI/Oを安全に終了させるまでソケットの物理的なクローズを遅延させるという、コミットの主要な目的を達成するためのものです。
コアとなるコードの解説
このコミットの核心は、netFD
のライフサイクル管理、特にクローズ処理の堅牢性を高めることにあります。
-
netFD.closing
フラグの導入:- このブール値は、
netFD
が現在クローズ処理中であるかどうかを示します。これにより、複数のゴルーチンが同時にClose
を呼び出したり、クローズ中に新たなI/O操作を開始しようとしたりする際の競合状態を防ぐための状態管理が可能になります。
- このブール値は、
-
incref(closing bool)
メソッドの役割:incref
は「参照を増やす」という意味で、netFD
が使用中であることを示します。closing
引数がtrue
の場合、このnetFD
がクローズ処理を開始したことをマークします。- 重要なのは、
fd.closing
が既にtrue
の場合、つまり既にクローズ処理が開始されている場合は、errClosing
エラーを返す点です。これにより、クローズ中のFDに対して新たなI/O操作が開始されることを防ぎ、不正なアクセスやデッドロックを回避します。
-
decref()
メソッドと遅延クローズ:decref
は「参照を減らす」という意味で、netFD
の使用が終了したことを示します。- このメソッドの最も重要な変更は、
fd.closing
がtrue
であり、かつfd.sysref
(システム参照カウント)が0
になった場合にのみ、実際のソケットクローズ(fd.sysfile.Close()
)を実行するようになった点です。 - これは「遅延クローズ」のメカニズムです。
Close()
が呼び出されても、すぐにソケットが閉じられるわけではありません。代わりに、netFD
を参照しているすべてのI/O操作が完了し、decref()
を呼び出して参照カウントがゼロになるまで、ソケットの物理的なクローズは待機されます。
-
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操作があっても、それらが適切に終了するまでソケットが閉じられることがなくなり、データ損失や予期せぬエラーを防ぎます。
-
pollServer
との連携:pollServer
はGoのノンブロッキングI/Oの心臓部であり、ソケットのI/O準備状況を監視しています。AddFD
がfd.closing
をチェックするようになったことで、クローズ中のFDに対して新たなI/Oイベントの登録が拒否されます。Evict
メソッドは、pollServer
が管理するI/Oイベントキューから特定のFDを削除し、関連するゴルーチンをerrClosing
で「起こす」役割を担います。これにより、ブロックされたI/Oが強制的に終了し、decref
が呼び出される連鎖が開始されます。
これらの変更は、Goのnet
パッケージが提供するネットワーク接続の信頼性と堅牢性を大幅に向上させ、特に高負荷環境や複雑なネットワークアプリケーションにおいて、より安定した動作を保証します。
関連リンク
- Go言語の
net
パッケージのドキュメント: https://pkg.go.dev/net - Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Go言語の
sync
パッケージのドキュメント: https://pkg.go.dev/sync
参考にした情報源リンク
- コミットハッシュ:
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
)で管理されていた可能性が高いです。
- 注: 参照されているIssue #1898 および #2116 については、現在のGoのIssueトラッカーや一般的なWeb検索では、このコミットの文脈に直接関連する明確な情報を見つけることができませんでした。これらは当時のGoプロジェクトの内部的なIssueトラッカー(例: