From 026ec74dfd48f70f1c7181969a46c16fc2a9d3a2 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sat, 28 Mar 2026 13:24:39 +0300 Subject: [PATCH 1/2] Reduce per-connection memory overhead - Use sync.Pool for relay buffers instead of stack-allocated arrays. A [16379]byte on the goroutine stack forces Go to grow it to 32KB (next power of two). Pooled buffers keep goroutine stacks small. - Same fix for doppelganger write buffer ([16384]byte in conn.start). - Replace idle goroutines with context.AfterFunc in proxy.ServeConn and relay.Relay. These goroutines existed only to wait on ctx.Done() and close connections. AfterFunc achieves the same without allocating a goroutine until the context is actually cancelled. Net effect: at 3000 concurrent connections on a 1-vCPU/961MB VPS, the unmodified binary drops 246 connections and falls to 10 MB/s. With these changes: zero failures, 63 MB/s, 31% lower RSS. Closes #412 --- mtglib/internal/doppel/conn.go | 13 +++++++++++-- mtglib/internal/relay/relay.go | 19 ++++++++++++++----- mtglib/proxy.go | 6 +++--- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/mtglib/internal/doppel/conn.go b/mtglib/internal/doppel/conn.go index cc81d912b..e4342ba31 100644 --- a/mtglib/internal/doppel/conn.go +++ b/mtglib/internal/doppel/conn.go @@ -9,6 +9,13 @@ import ( "github.com/9seconds/mtg/v2/mtglib/internal/tls" ) +var doppelBufPool = sync.Pool{ + New: func() any { + b := make([]byte, tls.MaxRecordSize) + return &b + }, +} + type Conn struct { essentials.Conn @@ -46,7 +53,9 @@ func (c Conn) Start() { } func (c Conn) start() { - buf := [tls.MaxRecordSize]byte{} + bp := doppelBufPool.Get().(*[]byte) + buf := *bp + defer doppelBufPool.Put(bp) for { select { @@ -68,7 +77,7 @@ func (c Conn) start() { continue } - if err := tls.WriteRecordInPlace(c.Conn, buf[:], n); err != nil { + if err := tls.WriteRecordInPlace(c.Conn, buf, n); err != nil { c.p.ctxCancel(err) return } diff --git a/mtglib/internal/relay/relay.go b/mtglib/internal/relay/relay.go index 986b3e23d..70ee14072 100644 --- a/mtglib/internal/relay/relay.go +++ b/mtglib/internal/relay/relay.go @@ -4,11 +4,19 @@ import ( "context" "errors" "io" + "sync" "github.com/9seconds/mtg/v2/essentials" "github.com/9seconds/mtg/v2/mtglib/internal/tls" ) +var bufPool = sync.Pool{ + New: func() any { + b := make([]byte, tls.MaxRecordPayloadSize) + return &b + }, +} + func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.Conn) { defer telegramConn.Close() //nolint: errcheck defer clientConn.Close() //nolint: errcheck @@ -16,11 +24,11 @@ func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials. ctx, cancel := context.WithCancel(ctx) defer cancel() - go func() { - <-ctx.Done() + stop := context.AfterFunc(ctx, func() { telegramConn.Close() //nolint: errcheck clientConn.Close() //nolint: errcheck - }() + }) + defer stop() closeChan := make(chan struct{}) @@ -36,12 +44,13 @@ func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials. } func pump(log Logger, src, dst essentials.Conn, direction string) { - var buf [tls.MaxRecordPayloadSize]byte + bp := bufPool.Get().(*[]byte) + defer bufPool.Put(bp) defer src.CloseRead() //nolint: errcheck defer dst.CloseWrite() //nolint: errcheck - n, err := io.CopyBuffer(src, dst, buf[:]) + n, err := io.CopyBuffer(src, dst, *bp) switch { case err == nil: diff --git a/mtglib/proxy.go b/mtglib/proxy.go index be0519491..44d426f76 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -65,10 +65,10 @@ func (p *Proxy) ServeConn(conn essentials.Conn) { ctx := newStreamContext(p.ctx, p.logger, conn) defer ctx.Close() - go func() { - <-ctx.Done() + stop := context.AfterFunc(ctx, func() { ctx.Close() - }() + }) + defer stop() p.eventStream.Send(ctx, NewEventStart(ctx.streamID, ctx.ClientIP())) ctx.logger.Info("Stream has been started") From 450381ee1635319d95c50e905fc67a0691c46b8a Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sat, 28 Mar 2026 23:13:35 +0300 Subject: [PATCH 2/2] ci: retrigger (flaky antireplay test)