fix: allow retrying failed connection pool stream closures

AD
2d ago
+162-14

Summary

Fixes #1328.

PoolByteStream previously marked itself as closed before closing the underlying stream. If that operation raised an exception, subsequent close attempts were skipped, leaving the request registered in the connection pool.

This PR fixes that behaviour for both synchronous and asynchronous streams by resetting _closed when the underlying close fails, allowing a later attempt to retry.

Pool cleanup still occurs only after the underlying stream closes successfully.

Testing:

  • Added regression tests for sync and async streams.
  • Covered repeated failures, retries, BaseException and idempotent cleanup.
  • All 2,125 tests passed across Python 3.11 to 3.15.
  • Achieved 100% test coverage.
  • Ruff, MyPy, build and consistency checks passed.

[Successful CI run](https://github.com/AdityaSreevatsaK/httpx2/actions/runs/43093935454)

Checklist

  • I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've reviewed the documentation requirements. No documentation changes are needed because this is an internal bug fix with no public API changes.