Fix silent data-corruption and lock contention in Codec#102
Conversation
Closes #83: Codec.decode silently returned raw bytes when the msgpack/bs4 optional dependency was absent because dispatch was gated on `umsgpack and ...` / `bs4 and ...`. Now the content type alone selects the branch and a DecodeError is raised when the required library is missing, matching the existing Avro pattern. The unreachable bs4 guard in _load_bs4 is left in place. Closes #96: _compress silently no-op'd for unrecognized content_encoding, so encode() emitted an uncompressed body whose AMQP property still claimed a compression scheme. It now raises EncodeError for anything other than gzip/bzip2. The decode-side skip is intentionally left as pass-through: a non-compression content_encoding (e.g. utf-8) is valid on decode and must not raise, and a genuinely corrupt body surfaces downstream as a DecodeError already. Closes #97: Avro HTTP schema loading serialized every first load behind one global lock and retried deterministic 4xx responses with backoff sleeps. Now a per-message-type asyncio.Lock lets different types load concurrently while still deduplicating concurrent loads of the same type, and 4xx responses fail fast without retry. 5xx/network errors still retry. Negative caching was left out to keep the change minimal. To keep decode() under the mccabe complexity limit after adding the missing-dependency guards, the content-type dispatch was extracted into a stateless _decode_body helper (no behavior change). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughCodec decode and encode behavior now raises errors for missing optional decoders and unsupported compression, while Avro schema loading uses per-message-type locks and fails fast on HTTP 4xx responses. New tests cover these decode, encode, and schema-loading paths. ChangesCodec Decode/Encode Fixes
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Consumer
participant Codec
participant PerTypeLock
participant SchemaRegistry
Consumer->>Codec: decode(message_type)
Codec->>PerTypeLock: acquire lock for message_type
alt schema cached
PerTypeLock-->>Codec: return cached schema
else not cached
Codec->>SchemaRegistry: GET schema
alt 4xx response
SchemaRegistry-->>Codec: 4xx status
Codec-->>Consumer: raise DecodeError
else 5xx response
SchemaRegistry-->>Codec: 5xx status
Codec->>SchemaRegistry: retry GET
SchemaRegistry-->>Codec: 200 schema
Codec->>PerTypeLock: cache schema
else 200 response
SchemaRegistry-->>Codec: 200 schema
Codec->>PerTypeLock: cache schema
end
end
Codec-->>Consumer: return decoded message
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@rejected/codecs.py`:
- Line 78: Clean up stale per-message-type schema locks in the codec loader,
because failed schema loads leave entries behind in _schema_locks without ever
populating _avro_schemas. Update the load path around the schema fetch/cache
logic in the codec class (the code that uses _schema_locks and _avro_schemas) so
that when a load fails for invalid, missing, or 4xx schema names, the
corresponding lock entry is removed after the attempt completes. Make sure the
cleanup happens for the failing message_type even when the exception is raised,
while preserving the lock for successful loads.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6eefa5b9-df6f-4207-bf40-9ea3d0fc8b60
📒 Files selected for processing (2)
rejected/codecs.pytests/test_codecs.py
The per-message-type lock dict added in #97 gained an entry for every message_type but only removed it implicitly via successful caching in _avro_schemas. Failed loads (invalid names, missing files, 4xx) never populate _avro_schemas, so their lock entries accumulated indefinitely. Since message_type comes from message properties, this was an unbounded growth vector. Pop the lock when the load raises, guarded by an identity check so a concurrent retry that has already re-created the lock is left intact. Successful loads keep their lock as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Fixes 3 verified bugs in
codecs.pythat caused silent data corruption and lock contention.Problem / Solution
Codec.decodesilently returned raw bytes when the optionalumsgpack/bs4dependency was missing → now raisesDecodeErrorwhen the content type matches but the library is absent, matching the Avro pattern._compresssilently no-op'd for unrecognizedcontent_encoding, emitting an uncompressed body whose AMQP property claimed a compression scheme → now raisesEncodeError. (Decode-side pass-through is intentionally retained: a non-compressioncontent_encodinglikeutf-8is legitimate on decode.)asyncio.Lock, fail-fast on 4xx (5xx/network errors still retry).A stateless
_decode_bodyhelper was extracted to keepdecodeunder the mccabe limit after adding the missing-dependency guards (no behavior change).Testing
Full suite green (218 tests) + ruff clean. New
tests/test_codecs.py(10 tests).Closes #83
Closes #96
Closes #97
Summary by CodeRabbit
Bug Fixes
Tests