Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

perf: Upstream transports keep only 2 idle connections per host

クローズ 初心者向け
#1,546 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
80/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
go

調査の方向性

issueで指定されているトランスポート設定箇所で、core/tlsbridge/upstream.go と core/listener/forwardproxy/server.go を読みます。両方の箇所に、含まれている接続プール設定を追加し、その後 core/tlsbridge/ から提供された TestUpstreamPoolReuse の実験を繰り返し実行します。worker あたり約1つの upstream 接続が開かれ、その数倍にはならないことを完了の条件とします。

索引モデルが issue の本文から書いたものです。

説明

bug

What happens

The proxy keeps only 2 idle upstream connections per host. When more than two requests to one host overlap, each extra request opens a new TCP+TLS connection rather than reusing one, and that connection is closed afterwards instead of being kept for the next request.

This hurts most on a gateway that speaks only HTTP/1.1, where each connection carries one request at a time. LiteLLM is one: it offers no h2 in ALPN. Each extra handshake costs about 40-90 ms on the laptop's VPN-reached gateways.

Reproduction

The bridge's own upstream client (tlsbridge.NewUpstreamClient), callers running at the same time, 10 sequential requests each, against one h1-only TLS origin. We counted the TCP connections the origin accepted, over three runs:

Concurrent callers Ideal Today With MaxIdleConnsPerHost: 100
4 4 5 / 16 / 19 5 / 5 / 5
10 10 36 / 39 / 45 18 / 19 / 23
20 20 66 / 76 / 79 46 / 49 / 49

Through the forward proxy end to end (10 bridged clients × 10 requests), the origin accepted 69 connections.

Test source (drop into core/tlsbridge/)
package tlsbridge

import (
	"crypto/tls"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"sync"
	"sync/atomic"
	"testing"
)

func TestUpstreamPoolReuse(t *testing.T) {
	var conns atomic.Int32
	origin := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { io.WriteString(w, "ok") }))
	origin.Config.ConnState = func(_ net.Conn, s http.ConnState) {
		if s == http.StateNew {
			conns.Add(1)
		}
	}
	origin.TLS = &tls.Config{NextProtos: []string{"http/1.1"}} // h1-only, like LiteLLM
	origin.StartTLS()
	defer origin.Close()
	const workers = 10
	c, _ := NewUpstreamClient(nil, true)
	var wg sync.WaitGroup
	for i := 0; i < workers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 10; j++ {
				if resp, err := c.Get(origin.URL); err == nil {
					io.Copy(io.Discard, resp.Body)
					resp.Body.Close()
				}
			}
		}()
	}
	wg.Wait()
	t.Logf("%d callers x 10 requests: %d upstream connections (ideal %d)", workers, conns.Load(), workers)
}

Why

Both transports set MaxIdleConns: 100 (the total across all hosts) but not MaxIdleConnsPerHost, so Go's DefaultMaxIdleConnsPerHost of 2 applies:

  • core/tlsbridge/upstream.go:31: every decrypted (bridged) request goes through this transport.
  • core/listener/forwardproxy/server.go:212: plain-HTTP forward-proxy requests.

Possible fix

Add MaxIdleConnsPerHost: 100 next to MaxIdleConns in both transports. Two lines.

Even with the fix, concurrent callers still open more connections than the ideal; Go dials a new connection whenever none is idle at that moment. A MaxConnsPerHost cap would bound that too, but it makes extra requests wait in a queue, so it is a separate decision.

Acceptance

The test above opens about workers connections, not several times that, across repeated runs.

Related, not verified

proxy.log on a laptop shows 65 fast EOF failures (128-221 ms) on LiteLLM POSTs. That is the usual sign of reusing a connection the server had just closed. Go retries such a request only when it can replay the body, and the forward proxy never sets r.GetBody after buffering the body (setBody, server.go:1306). We did not reproduce this, so it is noted here rather than filed.


Found by an experiment-driven audit of main at 43cf5539; every number above was re-measured on that commit.

Assisted-By: Claude (Anthropic AI) [email protected]

主要言語
Go
スター
21
フォーク
43
平均マージ
13時間 8分
マージ済み PR(30日)
267

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

rossoctl/cortex のほかの issue

rossoctl/cortex の issue をすべて見る

似ている issue

Go の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。