Skip to content

cache: apply the configured back-off and Get timeout - #22408

Open
datrixlab wants to merge 1 commit into
etcd-io:mainfrom
datrixlab:fix/cache-retry-honours-config
Open

cache: apply the configured back-off and Get timeout#22408
datrixlab wants to merge 1 commit into
etcd-io:mainfrom
datrixlab:fix/cache-retry-honours-config

Conversation

@datrixlab

Copy link
Copy Markdown

Three Config fields in cache are documented and settable but never read.

InitialBackoff and MaxBackoff

getWatchLoop builds its own defaultConfig() rather than reading the Config
the cache was constructed with:

backoff := defaultConfig().InitialBackoff

So WithInitialBackoff has no effect at all — an unreachable endpoint is retried
every 50ms no matter what the caller asked for. The delay is also constant, which
leaves MaxBackoff with nothing to cap; its doc comment promises it bounds "the
exponential back-off between successive upstream watch retries", and there is no
exponential back-off and no reader of the field anywhere in the package.

GetTimeout

Also unread. The bootstrap Get runs on the cache's lifetime context, so an
endpoint that accepts the connection and then goes quiet parks it indefinitely:
the retry loop never gets another turn, the cache never becomes ready, and every
Get/Watch blocked on WaitReady stays blocked. This is the failure mode
GetTimeout exists to prevent.

Change

Read all three from c.cfg, double the delay after each failed attempt up to
MaxBackoff, and bound the bootstrap Get. The retry timer comes from the
injected Clock, the way the progress-notify timer already does, so the schedule
is testable without sleeping.

New now rejects values that would make the loop spin, alongside the existing
HistoryWindowSize and BTreeDegree checks.

The back-off is deliberately not reset after a watch that ran for a while.
That is a separate policy question from the one the Config already documents,
and I did not want to decide it inside a fix for the fields not being read.

Tests

Three tests in cache_test.go, all inside synctest.Test so they use virtual
time and run in 0.00s:

  • TestCacheRetriesUpstreamWatchWithConfiguredBackoff — a KV whose watch always
    fails; asserts the retry instants are the configured sequence and that it
    flattens at MaxBackoff rather than growing past it.
  • TestCacheBoundsBootstrapGetByGetTimeout — a KV whose Get blocks forever;
    asserts the cache still reaches a retry instead of parking on WaitReady.
  • TestNewCacheRejectsUnusableRetrySettings — table over the three rejected
    configurations.

Each was checked by reverting cache.go alone: the three new tests fail, the
existing ones pass both before and after.

getWatchLoop built its own defaultConfig() instead of reading the Config the
cache was constructed with, so WithInitialBackoff had no effect: an unreachable
endpoint was retried every 50ms whatever the caller asked for. The delay was
also constant, which left MaxBackoff -- documented as capping "the exponential
back-off between successive upstream watch retries" -- with nothing to cap and
no reader anywhere in the package.

GetTimeout had no reader either. The bootstrap Get ran on the cache's lifetime
context, so an endpoint that accepts a connection and then goes quiet parks it
forever: the retry loop never gets another turn and the cache never becomes
ready, which blocks every Get and Watch on WaitReady.

Take all three from c.cfg, double the delay after each failed attempt up to
MaxBackoff, and bound the bootstrap Get. The retry timer now comes from the
injected Clock, like the progress-notify timer already does, so the schedule is
testable without sleeping. Reject the values that would make the loop spin,
alongside the existing HistoryWindowSize and BTreeDegree checks.

The back-off is deliberately not reset after a watch that ran for a while;
that is a separate policy question from the one the Config already documents.

Signed-off-by: datrixlab <325650023+datrixlab@users.noreply.github.com>
@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: datrixlab
Once this PR has been reviewed and has the lgtm label, please assign ahrtr for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow

Copy link
Copy Markdown

Hi @datrixlab. Thanks for your PR.

I'm waiting for a etcd-io member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

1 participant