Skip to content

feat(swift): add Connection pool and configurable ETag mismatch policy#13

Merged
nledez merged 2 commits into
cgwire:mainfrom
nledez:feat/swift-connection-pool
May 21, 2026
Merged

feat(swift): add Connection pool and configurable ETag mismatch policy#13
nledez merged 2 commits into
cgwire:mainfrom
nledez:feat/swift-connection-pool

Conversation

@nledez
Copy link
Copy Markdown

@nledez nledez commented May 20, 2026

  • Problem

    SwiftBackend shares a single swiftclient.Connection across all calls. Under gevent or threaded workers, multiple greenlets/threads write to the same HTTP socket concurrently
    and corrupt the auth-token state, which causes:

    • Sporadic 400s and ConnectionReset errors
    • Silent content corruption (object written ≠ object read)
    • No write-side integrity guarantee — Swift's returned ETag is never verified against the local MD5
    • list_files() caps at 10 000 objects (no full_listing)
    • read_chunks() never releases its connection → slot leak
    • Tests inherit an app fixture without app_context nor request_context → RuntimeError: Working outside of application context on recent Flask
    • Crypto tests leave encrypted.bin / decrypted.txt in cwd on every run
  • Solution

    Per-process Connection pool + ETag verification with configurable policy + test fixture cleanup.

    Swift backend

    • Per-process Connection pool (gevent.queue.Queue with stdlib queue.Queue fallback). Configurable pool_size (default 20), pool_timeout raising PoolExhaustedError (default
      30s).
    • write() pre-computes content_length + ETag (MD5) and verifies the ETag returned by Swift.
    • New etag_mismatch_policy setting:
      • "log" (default, legacy behavior) — log error, keep the object
      • "raise" — log + raise ClientException, keep the object
      • "raise_and_delete" — log + delete + raise ClientException
    • read_chunks() returns the connection to the pool when the generator is exhausted or close() is called (via _PoolReleasingStream).
    • list_files() passes full_listing=True.
    • timeout (60s) and retries (5) passed to every Connection.
    • os_options merged on top of tenant_name / region_name (Keystone v3).

    Tests

    • app fixture pushes test_request_context() + SERVER_NAME=localhost in TestConfig → url_for, current_app, request available out of the box.
    • tests/test_crypto.py uses tmp_path instead of writing to cwd.
    • New tests/test_swift_pool.py (skipped without gevent): concurrent writes, concurrent reads, pool_size enforcement, ETag mismatch with raise_and_delete policy.
    • gevent>=24.0.0 added to requirements/test.pip.

    Compatibility

    • No runtime breaking change: default etag_mismatch_policy="log" = legacy behavior.
    • All new settings optional with sane defaults.

    Tests

    • pytest: 212 passed (before fix: 57 failed / 155 passed).
    • Pool tests require a local Swift on 127.0.0.1:8085 (e.g. docker run -p 8085:8080 fnndsc/docker-swift-onlyone).

@nledez nledez force-pushed the feat/swift-connection-pool branch from ee50e8e to ad7f646 Compare May 21, 2026 08:13
nledez added 2 commits May 21, 2026 10:21
  - Per-process Connection pool (gevent Queue with stdlib fallback)
  so concurrent greenlets/threads no longer share a single
  swiftclient.Connection. Fixes sporadic 400s, ConnectionReset
  and content corruption under gevent workers.
  - write() pre-computes content_length and ETag and verifies
  the ETag returned by Swift. New etag_mismatch_policy config
  (log default / raise / raise_and_delete) controls
  behavior on mismatch — legacy log-only kept as default.
  - read_chunks() releases the borrowed Connection back to the
  pool when the generator is exhausted or close() is called.
  - list_files() uses full_listing=True to enumerate containers
  with more than 10 000 objects.
  - New optional settings: pool_size (20), pool_timeout (30),
  timeout (60), retries (5), os_options (merged over
  tenant_name / region_name for Keystone v3).
  - Tests: app fixture now pushes test_request_context and sets
  SERVER_NAME so url_for / current_app / request work
  out of the box; crypto tests use tmp_path instead of leaking
  encrypted.bin / decrypted.txt in cwd; new
  tests/test_swift_pool.py covers concurrent writes, pool sizing
  and ETag mismatch handling.
  - Adds gevent>=24.0.0 to test requirements.
@nledez nledez force-pushed the feat/swift-connection-pool branch from ad7f646 to fde93e3 Compare May 21, 2026 08:25
@nledez nledez merged commit 40b33f0 into cgwire:main May 21, 2026
7 checks passed
@nledez nledez deleted the feat/swift-connection-pool branch May 21, 2026 08:32
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.

1 participant