fix(pipe): prevent error swallowing and goroutine leak in Pipe/Transport

Pipe: remove context.WithCancel that caused premature ctx.Done()
before the second pipeHalf could return its error. Replace with a
completed counter that drains both errors even on cancellation.

Transport: increase error channel buffer from 1 to 2 to prevent
goroutine leak when both CopyBuffer goroutines complete.
This commit is contained in:
ginuerzh
2026-05-21 10:28:55 +08:00
parent 07ca57055a
commit d189dc7967
4 changed files with 185 additions and 19 deletions
+14 -17
View File
@@ -15,34 +15,32 @@ const (
readTimeout = 30 * time.Second
)
// Pipe 在两个连接之间建立双向数据通道
func Pipe(ctx context.Context, rw1, rw2 io.ReadWriteCloser) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
errCh := make(chan error, 2)
// 启动两个方向的传输
go func() {
errCh <- pipeHalf(ctx, rw1, rw2)
}()
go func() { errCh <- pipeHalf(ctx, rw1, rw2) }()
go func() { errCh <- pipeHalf(ctx, rw2, rw1) }()
go func() {
errCh <- pipeHalf(ctx, rw2, rw1)
}()
// 等待第一个错误或完成
var firstErr error
for i := 0; i < 2; i++ {
completed := 0
for completed < 2 {
select {
case err := <-errCh:
completed++
if firstErr == nil && err != nil {
firstErr = err
cancel() // 一个方向出错,取消另一个
}
case <-ctx.Done():
// 超时或主动取消
forceClose(rw1, rw2)
for completed < 2 {
if err := <-errCh; firstErr == nil && err != nil {
firstErr = err
}
completed++
}
if firstErr != nil {
return firstErr
}
return ctx.Err()
}
}
@@ -60,7 +58,6 @@ func pipeHalf(ctx context.Context, src, dst io.ReadWriteCloser) error {
buf := bufpool.Get(bufferSize / 2)
defer bufpool.Put(buf)
// 创建带超时的读取器
reader := &readDeadliner{
Reader: src,
+1 -1
View File
@@ -11,7 +11,7 @@ const (
)
func Transport(rw1, rw2 io.ReadWriter) error {
errc := make(chan error, 1)
errc := make(chan error, 2)
go func() {
errc <- CopyBuffer(rw1, rw2, bufferSize)
}()