@@ -13,6 +13,7 @@ import (
13
13
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
14
14
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
15
15
16
+ memcache "github.com/patrickmn/go-cache"
16
17
"go.uber.org/ratelimit"
17
18
"golang.org/x/sync/singleflight"
18
19
)
@@ -122,6 +123,10 @@ var registryLock sync.RWMutex
122
123
// credentialGroup ensures only one credential refresh happens per registry
123
124
var credentialGroup singleflight.Group
124
125
126
+ // Transport cache to avoid creating new transports for each request
127
+ // Using go-cache with 30 minute expiration and 10 minute cleanup interval
128
+ var transportCache = memcache .New (30 * time .Minute , 10 * time .Minute )
129
+
125
130
func AddRegistryEndpointFromConfig (epc RegistryConfiguration ) error {
126
131
ep := NewRegistryEndpoint (epc .Prefix , epc .Name , epc .ApiURL , epc .Credentials , epc .DefaultNS , epc .Insecure , TagListSortFromString (epc .TagSortMode ), epc .Limit , epc .CredsExpire )
127
132
return AddRegistryEndpoint (ep )
@@ -308,16 +313,76 @@ func (ep *RegistryEndpoint) DeepCopy() *RegistryEndpoint {
308
313
return newEp
309
314
}
310
315
316
+ // ClearTransportCache clears the transport cache
317
+ // This is useful when registry configuration changes
318
+ func ClearTransportCache () {
319
+ transportCache .Flush ()
320
+ }
321
+
311
322
// GetTransport returns a transport object for this endpoint
323
+ // Implements connection pooling and reuse to avoid creating new transports for each request
312
324
func (ep * RegistryEndpoint ) GetTransport () * http.Transport {
325
+ // Check if we have a cached transport for this registry
326
+ if cachedTransport , found := transportCache .Get (ep .RegistryAPI ); found {
327
+ transport := cachedTransport .(* http.Transport )
328
+ log .Debugf ("Transport cache HIT for %s: %p" , ep .RegistryAPI , transport )
329
+
330
+ // Validate that the transport is still usable
331
+ if isTransportValid (transport ) {
332
+ return transport
333
+ }
334
+
335
+ // Transport is stale, remove it from cache
336
+ log .Debugf ("Transport for %s is stale, removing from cache" , ep .RegistryAPI )
337
+ transportCache .Delete (ep .RegistryAPI )
338
+ }
339
+
340
+ log .Debugf ("Transport cache MISS for %s" , ep .RegistryAPI )
341
+
342
+ // Create a new transport with optimized connection pool settings
313
343
tlsC := & tls.Config {}
314
344
if ep .Insecure {
315
345
tlsC .InsecureSkipVerify = true
316
346
}
317
- return & http.Transport {
318
- Proxy : http .ProxyFromEnvironment ,
319
- TLSClientConfig : tlsC ,
347
+
348
+ // Create transport with aggressive timeout and connection management
349
+ transport := & http.Transport {
350
+ Proxy : http .ProxyFromEnvironment ,
351
+ TLSClientConfig : tlsC ,
352
+ MaxIdleConns : 20 , // Reduced global max idle connections
353
+ MaxIdleConnsPerHost : 5 , // Reduced per-host connections
354
+ IdleConnTimeout : 90 * time .Second , // Reduced idle timeout
355
+ TLSHandshakeTimeout : 10 * time .Second , // Reduced TLS timeout
356
+ ExpectContinueTimeout : 1 * time .Second , // Expect-Continue timeout
357
+ DisableKeepAlives : false , // Enable HTTP Keep-Alive
358
+ ForceAttemptHTTP2 : true , // Enable HTTP/2 if available
359
+ // Critical timeout settings to prevent hanging connections
360
+ ResponseHeaderTimeout : 10 * time .Second , // Response header timeout
361
+ MaxConnsPerHost : 10 , // Limit total connections per host
320
362
}
363
+
364
+ // Cache the transport for reuse with default expiration (30 minutes)
365
+ transportCache .Set (ep .RegistryAPI , transport , memcache .DefaultExpiration )
366
+ log .Debugf ("Cached NEW transport for %s: %p" , ep .RegistryAPI , transport )
367
+
368
+ return transport
369
+ }
370
+
371
+ // isTransportValid checks if a cached transport is still valid and usable
372
+ func isTransportValid (transport * http.Transport ) bool {
373
+ // Basic validation - check if transport is not nil and has valid configuration
374
+ if transport == nil {
375
+ return false
376
+ }
377
+
378
+ // Check if the transport's connection settings are reasonable
379
+ // This is a simple validation, more sophisticated checks could be added
380
+ if transport .MaxIdleConns < 0 || transport .MaxIdleConnsPerHost < 0 {
381
+ return false
382
+ }
383
+
384
+ // Transport appears to be valid
385
+ return true
321
386
}
322
387
323
388
// init initializes the registry configuration
0 commit comments