fix(client): reopen a closed client on use instead of raising ClientClosed - #106
Merged
Merged
Conversation
…losed
0.7.0 made close() release the connection pool and turned use after close
into ClientClosed. Every released pyvespa consumes Vespa.visit() and
streaming-query generators after the owning `with VespaSync` block has
closed the client, which worked for a year because close() was a no-op,
so 0.7.0 and 0.7.1 broke them all (pyvespa PR #1347 fixes the pattern
going forward, but published releases allow any httpr >= 0.4.0).
A request on a closed client now rebuilds the reqwest client from the
constructor settings, installs it with a fresh cancellation token (the
old one was cancelled by close()) and emits ClientReopenedWarning, a
ResourceWarning subclass: silent for end users, visible in test runs,
and `warnings.simplefilter("error", httpr.ClientReopenedWarning)`
restores httpx's strict behaviour. close() still releases the pool
immediately. The rebuilt client starts with an empty cookie store;
headers, params, auth, proxy and timeout live on the RClient and carry
over. Assigning client.proxy on a closed client records the proxy for
the rebuild instead of raising.
AsyncClient creates its thread pool on demand and drops it on close, so
a reopened client gets a new pool too. An open stream keeps reading
after aclose() since it holds its own handle to the pool.
ClientClosed stays exported (still a RuntimeError) but is only raised
when a close() races the reopen itself.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Owner
Author
|
Downstream verification against unmodified pyvespa
Each Docker test emitted |
…find the vespa CLI 48 tests in pyvespa's tests/unit/test_deployment.py generate a throwaway Vespa Cloud cert/key pair with the `vespa` CLI and fail with "Certificate and key not found" when the binary is not on PATH. Locally the CLI is installed globally, on the runner it only exists in the venv. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restores compatibility with every released pyvespa, which 0.7.0 and 0.7.1 broke (see pyvespa#1347). Intended for 0.7.2, after which 0.7.0 and 0.7.1 should be yanked on PyPI.
Problem
0.7.0 made
close()release the connection pool and turned use after close intoClientClosed.Vespa.visit()andVespa.query(streaming=True)return lazy generators from inside awith VespaSync(...)block, so every released pyvespa consumes them after the client is closed. That worked for a year becauseclose()was a no-op. Published pyvespa releases allow anyhttpr>=0.4.0, so only httpr can fix the field.Change
A request on a closed client now reopens it (
requests.Sessionsemantics):RClient::begin_requestrebuilds thereqwest::Clientfrom the storedClientConfigwith a freshCancellationToken(the old one was cancelled byclose()), installs it via the newClientState::reopen(), and emitshttpr.ClientReopenedWarning, aResourceWarningsubclass: silent for end users, shown in test runs, andwarnings.simplefilter("error", httpr.ClientReopenedWarning)restores httpx's strict behaviour.close()still releases the pool immediately; the lifecycle fix from close() / aclose() / __exit__ are no-ops — connections never released #88 is intact.is_closedis true fromclose()until the next request.RClientand carry over; the rebuilt client starts with an empty cookie store.client.proxy = ...on a closed client records the proxy for the rebuild instead of raising.AsyncClientcreates its thread pool on demand and drops it on close, so a reopened client gets a new one. An open stream keeps reading afteraclose()since it holds its own pool handle.ClientClosedstays exported (still aRuntimeError) but is only raised if aclose()races the reopen itself.Downstream CI job
New
downstream-pyvespajob inCI.yml: checks out pyvespamaster, installs it with[unittest], swaps in the wheel built from the PR with--no-deps, and runs pyvespa's unit suite. Runs on PRs, main and tags.Verification
origin/masterunit suite against this wheel, with-W error::httpr.ClientReopenedWarning: 693 passed, 140 subtests.origin/masterDocker integration tests that usevisit()(the ones that fail on 0.7.1): running, results in a comment below.🤖 Generated with Claude Code