Skip to content

Commit 719009e

Browse files
rainsunchengfang
authored andcommitted
Reuse transport of registry-scanner (argoproj-labs#1215)
Signed-off-by: rainsun <[email protected]> (cherry picked from commit 8c9172e) Signed-off-by: Cheng Fang <[email protected]> # Conflicts: # registry-scanner/pkg/registry/endpoints.go
1 parent ac5b3a0 commit 719009e

File tree

3 files changed

+128
-5
lines changed

3 files changed

+128
-5
lines changed

registry-scanner/pkg/registry/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ func clearRegistries() {
3636
registryLock.Lock()
3737
registries = make(map[string]*RegistryEndpoint)
3838
registryLock.Unlock()
39+
40+
// Also clear transport cache when registries are cleared
41+
// This ensures that when registry configuration changes, we use fresh transports
42+
ClearTransportCache()
3943
}
4044

4145
// LoadRegistryConfiguration loads a YAML-formatted registry configuration from

registry-scanner/pkg/registry/endpoints.go

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
1515
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
1616

17+
memcache "github.com/patrickmn/go-cache"
1718
"go.uber.org/ratelimit"
1819
"golang.org/x/sync/singleflight"
1920
)
@@ -123,6 +124,10 @@ var registryLock sync.RWMutex
123124
// credentialGroup ensures only one credential refresh happens per registry
124125
var credentialGroup singleflight.Group
125126

127+
// Transport cache to avoid creating new transports for each request
128+
// Using go-cache with 30 minute expiration and 10 minute cleanup interval
129+
var transportCache = memcache.New(30*time.Minute, 10*time.Minute)
130+
126131
func AddRegistryEndpointFromConfig(ctx context.Context, epc RegistryConfiguration) error {
127132
ep := NewRegistryEndpoint(epc.Prefix, epc.Name, epc.ApiURL, epc.Credentials, epc.DefaultNS, epc.Insecure, TagListSortFromString(epc.TagSortMode), epc.Limit, epc.CredsExpire)
128133
return AddRegistryEndpoint(ctx, ep)
@@ -311,16 +316,76 @@ func (ep *RegistryEndpoint) DeepCopy() *RegistryEndpoint {
311316
return newEp
312317
}
313318

319+
// ClearTransportCache clears the transport cache
320+
// This is useful when registry configuration changes
321+
func ClearTransportCache() {
322+
transportCache.Flush()
323+
}
324+
314325
// GetTransport returns a transport object for this endpoint
326+
// Implements connection pooling and reuse to avoid creating new transports for each request
315327
func (ep *RegistryEndpoint) GetTransport() *http.Transport {
328+
// Check if we have a cached transport for this registry
329+
if cachedTransport, found := transportCache.Get(ep.RegistryAPI); found {
330+
transport := cachedTransport.(*http.Transport)
331+
log.Debugf("Transport cache HIT for %s: %p", ep.RegistryAPI, transport)
332+
333+
// Validate that the transport is still usable
334+
if isTransportValid(transport) {
335+
return transport
336+
}
337+
338+
// Transport is stale, remove it from cache
339+
log.Debugf("Transport for %s is stale, removing from cache", ep.RegistryAPI)
340+
transportCache.Delete(ep.RegistryAPI)
341+
}
342+
343+
log.Debugf("Transport cache MISS for %s", ep.RegistryAPI)
344+
345+
// Create a new transport with optimized connection pool settings
316346
tlsC := &tls.Config{}
317347
if ep.Insecure {
318348
tlsC.InsecureSkipVerify = true
319349
}
320-
return &http.Transport{
321-
Proxy: http.ProxyFromEnvironment,
322-
TLSClientConfig: tlsC,
350+
351+
// Create transport with aggressive timeout and connection management
352+
transport := &http.Transport{
353+
Proxy: http.ProxyFromEnvironment,
354+
TLSClientConfig: tlsC,
355+
MaxIdleConns: 20, // Reduced global max idle connections
356+
MaxIdleConnsPerHost: 5, // Reduced per-host connections
357+
IdleConnTimeout: 90 * time.Second, // Reduced idle timeout
358+
TLSHandshakeTimeout: 10 * time.Second, // Reduced TLS timeout
359+
ExpectContinueTimeout: 1 * time.Second, // Expect-Continue timeout
360+
DisableKeepAlives: false, // Enable HTTP Keep-Alive
361+
ForceAttemptHTTP2: true, // Enable HTTP/2 if available
362+
// Critical timeout settings to prevent hanging connections
363+
ResponseHeaderTimeout: 10 * time.Second, // Response header timeout
364+
MaxConnsPerHost: 10, // Limit total connections per host
323365
}
366+
367+
// Cache the transport for reuse with default expiration (30 minutes)
368+
transportCache.Set(ep.RegistryAPI, transport, memcache.DefaultExpiration)
369+
log.Debugf("Cached NEW transport for %s: %p", ep.RegistryAPI, transport)
370+
371+
return transport
372+
}
373+
374+
// isTransportValid checks if a cached transport is still valid and usable
375+
func isTransportValid(transport *http.Transport) bool {
376+
// Basic validation - check if transport is not nil and has valid configuration
377+
if transport == nil {
378+
return false
379+
}
380+
381+
// Check if the transport's connection settings are reasonable
382+
// This is a simple validation, more sophisticated checks could be added
383+
if transport.MaxIdleConns < 0 || transport.MaxIdleConnsPerHost < 0 {
384+
return false
385+
}
386+
387+
// Transport appears to be valid
388+
return true
324389
}
325390

326391
// init initializes the registry configuration

registry-scanner/pkg/registry/endpoints_test.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package registry
33
import (
44
"context"
55
"fmt"
6+
"net/http"
67
"strings"
78
"sync"
89
"testing"
@@ -309,9 +310,12 @@ func Test_GetTagListSortFromString(t *testing.T) {
309310
}
310311

311312
func TestGetTransport(t *testing.T) {
313+
ClearTransportCache()
314+
defer ClearTransportCache()
312315
t.Run("returns transport with default TLS config when Insecure is false", func(t *testing.T) {
313316
endpoint := &RegistryEndpoint{
314-
Insecure: false,
317+
RegistryAPI: "secure-registry",
318+
Insecure: false,
315319
}
316320
transport := endpoint.GetTransport()
317321

@@ -322,7 +326,8 @@ func TestGetTransport(t *testing.T) {
322326

323327
t.Run("returns transport with insecure TLS config when Insecure is true", func(t *testing.T) {
324328
endpoint := &RegistryEndpoint{
325-
Insecure: true,
329+
RegistryAPI: "insecure-registry",
330+
Insecure: true,
326331
}
327332
transport := endpoint.GetTransport()
328333

@@ -374,3 +379,52 @@ func TestAddRegistryEndpointFromConfig(t *testing.T) {
374379
require.NoError(t, err)
375380
})
376381
}
382+
383+
// Test for transport caching and retrieval
384+
func TestTransportCache(t *testing.T) {
385+
// Clean up cache before and after test
386+
ClearTransportCache()
387+
defer ClearTransportCache()
388+
389+
endpoint := &RegistryEndpoint{
390+
RegistryAPI: "https://example.com",
391+
Insecure: false,
392+
}
393+
394+
// 1. Test cache MISS and creation of a new transport
395+
transport1 := endpoint.GetTransport()
396+
assert.NotNil(t, transport1, "Transport should not be nil on cache miss")
397+
398+
// 2. Test cache HIT
399+
transport2 := endpoint.GetTransport()
400+
assert.NotNil(t, transport2, "Transport should not be nil on cache hit")
401+
assert.Same(t, transport1, transport2, "Should retrieve the same transport instance from cache")
402+
403+
// 3. Test cache clearing
404+
ClearTransportCache()
405+
transport3 := endpoint.GetTransport()
406+
assert.NotSame(t, transport1, transport3, "Should create a new transport after cache is cleared")
407+
}
408+
409+
// Test for transport validation logic
410+
func TestIsTransportValid(t *testing.T) {
411+
t.Run("valid transport", func(t *testing.T) {
412+
transport := &http.Transport{
413+
MaxIdleConns: 10,
414+
MaxIdleConnsPerHost: 5,
415+
}
416+
assert.True(t, isTransportValid(transport), "Should be a valid transport")
417+
})
418+
419+
t.Run("nil transport", func(t *testing.T) {
420+
assert.False(t, isTransportValid(nil), "Nil transport should be invalid")
421+
})
422+
423+
t.Run("invalid connection settings", func(t *testing.T) {
424+
transport := &http.Transport{
425+
MaxIdleConns: -1,
426+
}
427+
assert.False(t, isTransportValid(transport), "Transport with invalid settings should be invalid")
428+
})
429+
}
430+

0 commit comments

Comments
 (0)