Skip to content

return connection errors from get() - #321

Open
kdcokenny wants to merge 6 commits into
djc:mainfrom
kdcokenny:fix/get-connection-errors
Open

kdcokenny wants to merge 6 commits into
djc:mainfrom
kdcokenny:fix/get-connection-errors

Conversation

@kdcokenny

@kdcokenny kdcokenny commented Oct 1, 2026 •

Copy link
Copy Markdown

related to #141.

get() owns its connection attempt and returns errors when retries are disabled or error_is_fatal() is true. temporary failures still retry. cancelling get() drops the attempt and releases capacity.

the no-retry test now expects User(Error) instead of TimedOut. requires tokio 1.26 for the Notify fix.

i manually verified red/green e2e runs for checkout and postgres, both mutexes, rust 1.75 and minimum tokio.

@codecov

codecov Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.08%. Comparing base (facae9e) to head (86f8e5b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #321      +/-   ##
==========================================
+ Coverage   77.99%   83.08%   +5.08%     
==========================================
  Files           6        6              
  Lines         659      739      +80     
==========================================
+ Hits          514      614     +100     
+ Misses        145      125      -20     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@djc djc left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can pick one of per-commit or per-PR review. This seems to have some small commits that are independent but also changes that are spread over multiple commits, which make them harder to review.

Comment thread bb8/src/api.rs Outdated
Comment thread bb8/src/inner.rs Outdated
@kdcokenny

Copy link
Copy Markdown
Author

You can pick one of per-commit or per-PR review. This seems to have some small commits that are independent but also changes that are spread over multiple commits, which make them harder to review.

Ah yes I intended per PR review. If you'd like me to squash these commits lmk!

@djc

djc commented Oct 2, 2026

Copy link
Copy Markdown
Owner

In its current shape I don't think I'll be able to review this change -- it has too much going on without it being obvious why things are going on. It might work better if you can break it down into a series of smaller changes, each of which should have clear justification.

@kdcokenny
kdcokenny force-pushed the fix/get-connection-errors branch from 04458d5 to 04f6ae9 Compare October 2, 2026 12:49
A min_idle attempt can occupy the last capacity slot while get() waits on Notify. On upstream, failure releases the reservation but never wakes that getter to start a replacement. The manually polled regression fails on untouched upstream under both mutexes, and passes with only the added failure notification. Notify after releasing the pool lock. Require Tokio 1.26 for atomic notify_waiters across batched wakeups. This fixes an existing background-failure wakeup gap independently of caller-owned attempts.
Extract the existing connect/customize/backoff loop so it returns an unpooled connection. Background insertion still consumes its approval, records Created, and releases capacity with a wakeup on failure. Keep retry delays and the existing elapsed-time check unchanged. Removing the weak upgrade is safe because this method already borrows a PoolInner owning the strong Arc. This mechanical step makes the later ownership change smaller without changing error routing or cancellation.
Add the default-false error_is_fatal hook at the end of ManageConnection, as requested in review. Apply it to connect and customization errors in the shared retry loop. Fatal build failures return immediately; fatal background failures go to the sink once and do not disable the pool. Exercise both initialization error sources in one test before changing which attempts get() owns. Existing managers retain their retry behavior.
Return the exact non-retried manager/customizer error to the checkout reserving the attempt, without Clone or Sync bounds or shared stale errors. Race the owned attempt against idle availability under one checkout deadline while preserving transient retry backoff and sink reporting. Keep capacity reservations, cancellation drop, runtime-handle min_idle refill, wakeups, and all demand regressions together.

The immediate-validation timeout and slow-validation notification regressions pass on untouched upstream under both mutexes. Their safeguards belong in this architectural change: inline ready connects make a validation loop able to starve the deadline, and registering Notify before shared-state inspection requires dropping that registration before validation. Strengthen the timeout regression with an explicit first-poll yield assertion, using its existing validator. The independently reproduced upstream background-failure wakeup fix precedes this commit.

The retry extraction and fatal classifier can be reviewed independently, but this commit remains the principal architectural review burden. The sequence does not establish that its scope is acceptable to the maintainer.
@kdcokenny
kdcokenny force-pushed the fix/get-connection-errors branch from 04f6ae9 to ad7a00c Compare October 2, 2026 13:12
@kdcokenny

Copy link
Copy Markdown
Author

In its current shape I don't think I'll be able to review this change -- it has too much going on without it being obvious why things are going on. It might work better if you can break it down into a series of smaller changes, each of which should have clear justification.

got it. i split this into 4 commits so you can go through them in order:

  1. fix a background connection failure not waking a caller waiting for capacity. confirmed this fails on upstream too
  2. extract the retry loop without changing behavior
  3. add the fatal-error hook and its tests
  4. return connection errors to get(), keeping the cancellation/wakeup handling and regression tests together

is this more of what you were looking for? if there's anything else i can do to make it easier to review lmk.

@djc djc left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first 3 commits seem fine. I'm still not sure why the 4th commit is useful/related.

Comment thread bb8/tests/background.rs Outdated
Comment thread bb8/src/inner.rs
@kdcokenny

Copy link
Copy Markdown
Author

the first 3 commits stop retries for an attempt, but get() still waits on Notify while the error goes to the sink, so persistent failures can still become TimedOut. commit 4 lets the checkout own the attempt and return its actual error. the cancellation/capacity handling preserves pool behavior around that change.
other than that, i've reordered the tests locally. the semicolon nit works on commit two, but commit four adds the statistics guard, so rustfmt keeps the block and restores the semicolon. i've left that as-is

@djc

djc commented Oct 7, 2026

Copy link
Copy Markdown
Owner

commit 4 lets the checkout own the attempt and return its actual error.

Still not sure what you mean by this.

@kdcokenny

Copy link
Copy Markdown
Author

commit 4 lets the checkout own the attempt and return its actual error.

Still not sure what you mean by this.

by checkout i mean the pool.get() call. currently, creating a connection happens in a separate task, and get() only waits for a notification, so it never receives that task's connection error.
commit 4 keeps the connect_with_retry() future inside get() instead, polling it alongside notifications. that lets get() return RunError::User(e) when retries are disabled or the error is fatal. also when i say "owns the attempt" i mean that dropping get() also drops the connection future

@djc

djc commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Well, I don't understand commit 4, so I won't be merging it in its current state. Perhaps there's a way to restructure it to make it more clear, or break it down into smaller pieces that make it easier to understand.

This branch was successfully deployed

1 active deployment
Coverage — 86f8e5b9 Deployed Oct 7, 2026 by kdcokenny via coverage #833
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants