Skip to content

ToRESTConfig stacks retryingRoundTripper on every call #1484

Description

@SebTardif

Bug description

MemoryRESTClientGetter.ToRESTConfig() calls c.cfg.Wrap() unconditionally on every invocation. Because rest.Config.Wrap() is additive and the config is shared across the getter's lifetime, each call stacks another retryingRoundTripper layer around the transport.

The retrying round-tripper was added in #1084 to fix temporary etcdserver: leader changed errors from kube-apiserver. However, the Wrap call was placed inside ToRESTConfig() without a guard, so it fires on every call rather than once.

Impact

  • Each reconciliation invokes ToRESTConfig() multiple times (via ToDiscoveryClient, ToRESTMapper, and direct calls).
  • Over time, the nesting depth grows linearly: after N calls, each HTTP request traverses N retry layers.
  • This causes memory growth from retained closure/wrapper chains and deepening call stacks.

Steps to reproduce

  1. Create a MemoryRESTClientGetter with a *rest.Config.
  2. Call ToRESTConfig() 10 times.
  3. Inspect cfg.WrapTransport(nil) and walk the retryingRoundTripper chain.
  4. Observe 10 nested layers instead of 1.

Expected behavior

The transport should be wrapped exactly once regardless of how many times ToRESTConfig() is called.

Proposed fix

Guard the c.cfg.Wrap() call with a sync.Once field on the struct, consistent with the existing lazy-init patterns already used in the struct (sync.Mutex for discoveryClient, restMapper, clientCfg).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions