add SetReadDeadline in Pipe, to prevent memory not auto release in mws/mtcp
This commit is contained in:
@@ -130,7 +130,7 @@ func (d *mwsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dia
|
|||||||
if host == "" {
|
if host == "" {
|
||||||
host = opts.Addr
|
host = opts.Addr
|
||||||
}
|
}
|
||||||
s, err := d.initSession(ctx, host, conn, log)
|
s, err := d.initSession(ctx, host, conn, log, opts.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@@ -151,7 +151,7 @@ func (d *mwsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dia
|
|||||||
return cc, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *mwsDialer) initSession(ctx context.Context, host string, conn net.Conn, log logger.Logger) (*muxSession, error) {
|
func (d *mwsDialer) initSession(ctx context.Context, host string, conn net.Conn, log logger.Logger, optAddr string) (*muxSession, error) {
|
||||||
dialer := websocket.Dialer{
|
dialer := websocket.Dialer{
|
||||||
HandshakeTimeout: d.md.handshakeTimeout,
|
HandshakeTimeout: d.md.handshakeTimeout,
|
||||||
ReadBufferSize: d.md.readBufferSize,
|
ReadBufferSize: d.md.readBufferSize,
|
||||||
@@ -191,7 +191,7 @@ func (d *mwsDialer) initSession(ctx context.Context, host string, conn net.Conn,
|
|||||||
c.SetReadDeadline(time.Now().Add(d.md.keepaliveInterval * 2))
|
c.SetReadDeadline(time.Now().Add(d.md.keepaliveInterval * 2))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
go d.keepAlive(cc)
|
go d.keepAlive(cc, optAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stream multiplex
|
// stream multiplex
|
||||||
@@ -203,7 +203,7 @@ func (d *mwsDialer) initSession(ctx context.Context, host string, conn net.Conn,
|
|||||||
return &muxSession{conn: cc, session: session}, nil
|
return &muxSession{conn: cc, session: session}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *mwsDialer) keepAlive(conn ws_util.WebsocketConn) {
|
func (d *mwsDialer) keepAlive(conn ws_util.WebsocketConn, optAddr string) {
|
||||||
ticker := time.NewTicker(d.md.keepaliveInterval)
|
ticker := time.NewTicker(d.md.keepaliveInterval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -211,6 +211,11 @@ func (d *mwsDialer) keepAlive(conn ws_util.WebsocketConn) {
|
|||||||
d.options.Logger.Debug("send ping")
|
d.options.Logger.Debug("send ping")
|
||||||
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
|
||||||
|
d.sessionMutex.Lock()
|
||||||
|
delete(d.sessions, optAddr)
|
||||||
|
d.sessionMutex.Unlock()
|
||||||
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.SetWriteDeadline(time.Time{})
|
conn.SetWriteDeadline(time.Time{})
|
||||||
|
|||||||
+100
-37
@@ -3,7 +3,6 @@ package net
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-gost/core/common/bufpool"
|
"github.com/go-gost/core/common/bufpool"
|
||||||
@@ -13,69 +12,133 @@ import (
|
|||||||
const (
|
const (
|
||||||
// tcpWaitTimeout implements a TCP half-close timeout.
|
// tcpWaitTimeout implements a TCP half-close timeout.
|
||||||
tcpWaitTimeout = 10 * time.Second
|
tcpWaitTimeout = 10 * time.Second
|
||||||
|
readTimeout = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Pipe 在两个连接之间建立双向数据通道
|
||||||
func Pipe(ctx context.Context, rw1, rw2 io.ReadWriteCloser) error {
|
func Pipe(ctx context.Context, rw1, rw2 io.ReadWriteCloser) error {
|
||||||
wg := sync.WaitGroup{}
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
wg.Add(2)
|
defer cancel()
|
||||||
|
|
||||||
ch := make(chan error, 2)
|
errCh := make(chan error, 2)
|
||||||
|
|
||||||
|
// 启动两个方向的传输
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
errCh <- pipeHalf(ctx, rw1, rw2)
|
||||||
if err := pipeBuffer(rw1, rw2, bufferSize/2); err != nil {
|
|
||||||
ch <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := pipeBuffer(rw2, rw1, bufferSize/2); err != nil {
|
|
||||||
ch <- err
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
errCh <- pipeHalf(ctx, rw2, rw1)
|
||||||
close(done)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// 等待第一个错误或完成
|
||||||
|
var firstErr error
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case err := <-errCh:
|
||||||
|
if firstErr == nil && err != nil {
|
||||||
|
firstErr = err
|
||||||
|
cancel() // 一个方向出错,取消另一个
|
||||||
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil
|
// 超时或主动取消
|
||||||
|
forceClose(rw1, rw2)
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
return firstErr
|
||||||
case err := <-ch:
|
|
||||||
return err
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipeBuffer(dst io.ReadWriteCloser, src io.ReadWriteCloser, bufferSize int) error {
|
// pipeHalf 单向管道传输
|
||||||
buf := bufpool.Get(bufferSize)
|
func pipeHalf(ctx context.Context, src, dst io.ReadWriteCloser) error {
|
||||||
|
defer func() {
|
||||||
|
// 传输完成后执行TCP半关闭
|
||||||
|
halfClose(src, dst)
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf := bufpool.Get(bufferSize / 2)
|
||||||
defer bufpool.Put(buf)
|
defer bufpool.Put(buf)
|
||||||
|
|
||||||
_, err := io.CopyBuffer(dst, src, buf)
|
|
||||||
|
|
||||||
// Do the upload/download side TCP half-close.
|
// 创建带超时的读取器
|
||||||
|
reader := &readDeadliner{
|
||||||
|
Reader: src,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环读取并写入,每次读取都有超时
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
// 设置读取超时
|
||||||
|
if rd, ok := src.(interface{ SetReadDeadline(time.Time) error }); ok {
|
||||||
|
rd.SetReadDeadline(time.Now().Add(readTimeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取数据
|
||||||
|
nr, err := reader.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil // 正常结束
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入数据
|
||||||
|
_, err = dst.Write(buf[:nr])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDeadliner 包装读取器,支持上下文取消
|
||||||
|
type readDeadliner struct {
|
||||||
|
io.Reader
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *readDeadliner) Read(p []byte) (int, error) {
|
||||||
|
// 检查上下文是否已取消
|
||||||
|
select {
|
||||||
|
case <-r.ctx.Done():
|
||||||
|
return 0, r.ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return r.Reader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// halfClose 执行TCP半关闭
|
||||||
|
func halfClose(src, dst io.ReadWriteCloser) {
|
||||||
|
// 关闭读取端
|
||||||
if cr, ok := src.(xio.CloseRead); ok {
|
if cr, ok := src.(xio.CloseRead); ok {
|
||||||
cr.CloseRead()
|
cr.CloseRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭写入端,尝试半关闭
|
||||||
if cw, ok := dst.(xio.CloseWrite); ok {
|
if cw, ok := dst.(xio.CloseWrite); ok {
|
||||||
if e := cw.CloseWrite(); e == xio.ErrUnsupported {
|
if err := cw.CloseWrite(); err == xio.ErrUnsupported {
|
||||||
dst.Close()
|
dst.Close() // 不支持半关闭,完全关闭
|
||||||
} else {
|
} else {
|
||||||
// Set TCP half-close timeout.
|
// 设置半关闭超时
|
||||||
xio.SetReadDeadline(dst, time.Now().Add(tcpWaitTimeout))
|
if rd, ok := dst.(interface{ SetReadDeadline(time.Time) error }); ok {
|
||||||
|
rd.SetReadDeadline(time.Now().Add(tcpWaitTimeout))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dst.Close()
|
dst.Close() // 不支持CloseWrite,完全关闭
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// forceClose 强制关闭两个连接
|
||||||
|
func forceClose(conns ...io.ReadWriteCloser) {
|
||||||
|
for _, conn := range conns {
|
||||||
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user