@@ -121,6 +121,10 @@ var registryLock sync.RWMutex
121
121
// credentialGroup ensures only one credential refresh happens per registry
122
122
var credentialGroup singleflight.Group
123
123
124
+ // Transport cache to avoid creating new transports for each request
125
+ var transportCache = make (map [string ]* http.Transport )
126
+ var transportCacheLock sync.RWMutex
127
+
124
128
func AddRegistryEndpointFromConfig (epc RegistryConfiguration ) error {
125
129
ep := NewRegistryEndpoint (epc .Prefix , epc .Name , epc .ApiURL , epc .Credentials , epc .DefaultNS , epc .Insecure , TagListSortFromString (epc .TagSortMode ), epc .Limit , epc .CredsExpire )
126
130
return AddRegistryEndpoint (ep )
@@ -282,16 +286,65 @@ func (ep *RegistryEndpoint) DeepCopy() *RegistryEndpoint {
282
286
return newEp
283
287
}
284
288
289
+ // ClearTransportCache clears the transport cache
290
+ // This is useful when registry configuration changes
291
+ func ClearTransportCache () {
292
+ transportCacheLock .Lock ()
293
+ transportCache = make (map [string ]* http.Transport )
294
+ transportCacheLock .Unlock ()
295
+ }
296
+
285
297
// GetTransport returns a transport object for this endpoint
286
- func (ep * RegistryEndpoint ) GetTransport () * http.Transport {
298
+ // Implements connection pooling and reuse to avoid creating new transports for each request
299
+ func (ep * RegistryEndpoint ) GetTransport () * http.Transport {
300
+ // Check if we have a cached transport for this registry
301
+ transportCacheLock .RLock ()
302
+ if transport , exists := transportCache [ep .RegistryAPI ]; exists {
303
+ log .Debugf ("Transport cache HIT for %s: %p" , ep .RegistryAPI , transport )
304
+ transportCacheLock .RUnlock ()
305
+ return transport
306
+ }
307
+ log .Debugf ("Transport cache MISS for %s" , ep .RegistryAPI )
308
+ transportCacheLock .RUnlock ()
309
+
310
+ // Create a new transport with optimized connection pool settings
311
+ transportCacheLock .Lock ()
312
+ defer transportCacheLock .Unlock ()
313
+
314
+ // Double-check after acquiring write lock
315
+ if transport , exists := transportCache [ep .RegistryAPI ]; exists {
316
+ log .Debugf ("Transport cache double-check HIT for %s: %p" , ep .RegistryAPI , transport )
317
+ return transport
318
+ }
319
+
320
+ log .Debugf ("Creating NEW transport for %s" , ep .RegistryAPI )
321
+
287
322
tlsC := & tls.Config {}
288
323
if ep .Insecure {
289
324
tlsC .InsecureSkipVerify = true
290
325
}
291
- return & http.Transport {
292
- Proxy : http .ProxyFromEnvironment ,
293
- TLSClientConfig : tlsC ,
326
+
327
+ // Create transport with aggressive timeout and connection management
328
+ transport := & http.Transport {
329
+ Proxy : http .ProxyFromEnvironment ,
330
+ TLSClientConfig : tlsC ,
331
+ MaxIdleConns : 20 , // Reduced global max idle connections
332
+ MaxIdleConnsPerHost : 5 , // Reduced per-host connections
333
+ IdleConnTimeout : 30 * time .Second , // Reduced idle timeout
334
+ TLSHandshakeTimeout : 5 * time .Second , // Reduced TLS timeout
335
+ ExpectContinueTimeout : 1 * time .Second , // Expect-Continue timeout
336
+ DisableKeepAlives : false , // Enable HTTP Keep-Alive
337
+ ForceAttemptHTTP2 : true , // Enable HTTP/2 if available
338
+ // Critical timeout settings to prevent hanging connections
339
+ ResponseHeaderTimeout : 10 * time .Second , // Response header timeout
340
+ MaxConnsPerHost : 10 , // Limit total connections per host
294
341
}
342
+
343
+ // Cache the transport for reuse
344
+ transportCache [ep .RegistryAPI ] = transport
345
+ log .Debugf ("Cached NEW transport for %s: %p" , ep .RegistryAPI , transport )
346
+
347
+ return transport
295
348
}
296
349
297
350
// init initializes the registry configuration
0 commit comments