20 KiB
Deep Resources & Serialization — Extended Checklist
Context Gathering (REQUIRED)
Before applying the checklist, read the TARGET FILES (not just the diff) to understand:
- Serialization contract: Read both
serialize()anddeserialize()end-to-end. List every field. - Version gates: What protocol/format versions exist? Which fields are gated?
- Resource ownership: Who creates, who closes? Is ownership transferred?
- Error paths: Trace every exception exit. What resources are held at each exit?
- Lifecycle: When are resources acquired and released? What cleanup runs on shutdown?
Serialization Depth
Field count/order mismatch (weight: high)
- Compare
serialize(),deserialize(), andserializedSize()line by line. Do they write, read, and measure the SAME fields in the SAME order under the SAME conditional guards? - Is a field present in
serialize()but missing fromserializedSize()(or vice versa)? - Is a
sizeof()result computed but never accumulated (dead code)? - Is a length field missing from the symmetric read/write contract?
Wire format mismatch (weight: high)
- Does custom payload use
byte[]where protocol specifiesByteBuffer? - Are column names encoded as ASCII instead of UTF-8?
- Is Thrift wire format assumed when native protocol is in use (or vice versa)?
- Does nodetool use
String.getBytesinstead of the key validator?
Empty/null buffers (weight: high)
- Can an empty buffer reach a serializer validate method?
- Can a zero-length value enter code assuming non-empty content?
- Does the code handle UNSET sentinel values alongside null?
Wrong stream/variable (weight: high)
- After creating a wrapper stream (tee, counting, checked): does ALL subsequent I/O use the WRAPPER?
- Is the original field used instead of a locally transformed variable?
- Is
writableBytes()called wherereadableBytes()is needed?
Version compatibility (weight: high)
- Does the serializer restrict to current version only, breaking rolling upgrades?
- Has an enum ordinal been renumbered by removing middle constants?
- Do new schema columns break older peers?
- Is the digest computation version-aware?
Nullable dereferencing (weight: high)
- Can individual UDT fields be absent?
- Can frozen collection elements be null?
- Can bind values be null or UNSET?
- Is a method return value discarded where it should replace the parameter?
Checksum/digest range (weight: medium)
- Does the CRC cover ALL serialized fields (including newly added ones)?
- Is
ByteBuffer.duplicate()taken before write (not after)? - Is the digest computation version-aware across upgrade boundaries?
Collection serialization order (weight: medium)
- Are frozen maps serialized via
SortedMapwith correct type comparator? - Are JSON map keys wrapped in strings?
VInt signed/unsigned (weight: low)
- Is
computeVIntSizeused wherecomputeUnsignedVIntSizeis needed (or vice versa)?
Length prefix width (weight: low)
- Is a 4-byte length prefix written but 2-byte prefix expected on deserialization?
Enum deserialization (weight: low)
- Does enum-switched deserialization preserve a previously assigned field for a case where it should be absent?
Resource Leak Depth
Exception mid-sequence (weight: high)
- When multiple AutoCloseable resources are acquired in sequence: if the second allocation fails, is the first released?
- Is there a try/catch or try-with-resources chain guarding stepwise allocation?
Loop-built closeables (weight: medium)
- When Closeable resources are built in a loop: if an exception occurs partway, are already-created resources closed?
- Is each individual
release()in a cleanup loop independently try/caught?
Double-counting/release (weight: low)
- Is a resource counter incremented/decremented twice for the same event?
- Is a "free" operation using a pre-mutation delta?
Close side effects (weight: low)
- Does
close()trigger side-effecting callbacks without an idempotency guard? - Could close fire twice (once from try-with-resources, once from caller)?
Short-circuit cleanup (weight: low)
- Does a cleanup loop stop at the first exception instead of continuing to release all?
- Is each cleanup in the loop independently guarded?
Ref-counted asymmetry (weight: low)
- Is a reference count + lifecycle transaction released as a pair on error?
- Does one half leak on error paths?
View returned to pool (pattern)
- When a
ByteBufferis obtained vianioBuffer(),slice(),duplicate(): is the ORIGINAL returned to the pool? - Does the pool receive a view it can't recycle?
Filtered-out items (weight: low)
- When an iterator of closeable objects is filtered: are filtered-out items closed?
Metric registered without deregistration (weight: low)
- Does every
addMetrichave a matchingremoveMetricinclose()?
Lifecycle & Configuration Depth
Config not threaded (weight: high)
- Is a config knob parsed but never threaded to the builder?
- Is
nullpassed where config should flow? - Are streaming encryption settings dropped?
Throwable gotchas (weight: medium)
- Can
getMessage()return null (no-arg exception constructor)? - Can
getCause()be null (no chained cause)? - Can
getStackTrace()return zero elements (writableStackTrace=false)?
Success path missing cleanup (weight: medium)
- Does the success path miss cleanup that the error path has?
- Is session removal, counter decrement, or map eviction only on the error branch?
State persisted too early (weight: medium)
- Is persistent state written when an operation starts rather than when it commits?
- Could a crash leave stale state that prevents retry?
File I/O & Crash Safety Depth
File write modes (specific patterns)
WRITE | CREATEwithoutTRUNCATE_EXISTINGon pre-existing file: old content past new EOF remains.Files.copy()withoutREPLACE_EXISTINGin code that can run twice.- Jackson
writeValue(OutputStream)auto-closes: is write-to-temp, fsync, rename, fsync-parent used instead?
Multi-file atomicity (pattern)
- When multiple related files are persisted: are ALL flushed and synced before the directory sync?
- Is there a missing flush on any one file in the set?
Correlated state changes (pattern)
- Are multiple correlated state changes announced as separate operations instead of bundled atomically?
Metrics & Observability Depth
Metric timing (weight: high)
- Is a metric updated before the operation it measures?
- Is it after a different operation?
- Is it outside the guard that controls the measured operation?
Progress counter (weight: low)
- Does progress increment per inner-loop page instead of per outer logical unit?
- Is the count off by orders of magnitude for wide rows?
Platform & Encoding Depth
Locale/charset (weight: medium)
- Is
DecimalFormatused withoutLocale.ROOT? - Is
String.getBytes()called without explicit charset? - Does
Boolean.valueOf()need to beparseBoolean()?
I/O stream (specific patterns)
- Does
InputStream.read()return raw signed byte without& 0xFF? - Is
EINTRhandled in blocking system calls? - Is
MessageDigestshared across threads without synchronization?
Filesystem Path Depth
Path comparison (weight: medium)
- Is
String.startsWith()used for path containment instead ofPath.startsWith()? - Do symlinks break path comparisons?
Wrong directory level (weight: medium)
- Does
directory.getName()return the wrong component after hierarchy changes? - Is a directory level omitted in path construction?
Layout migration (weight: low)
- Does a directory layout change include migration of existing data from old location?
- Are files unreachable through the new path abstraction?
Missing from §18: Serialization — Specific Patterns
Transient or non-serializable field in Serializable class (weight: medium)
Transient field set only in constructor silently null after deserialization, or third-party library types fail remote deserialization.
Validation delegated to wrong serializer, wrong validator package, or bypassed entirely (weight: medium)
Nested tuple/UDT validation routed to BytesSerializer instead of type-aware serializer, collection validate() called from legacy CQL2 package.
Endianness or byte-order not accounted for in bit-shift arithmetic (weight: low)
ByteBuffer.getLong feeds into bit-shift arithmetic assuming big-endian on little-endian buffers, or unsafe put/get does not account for BIG_ENDIAN on s390x.
Protocol field order violation (encode vs decode sequence mismatch) (weight: low)
Encode writes flags before consistency level while decode reads them in opposite order, or paging state buffer not flipped before being read after write.
Deserialization-order sensitivity not enforced (drain instead of throw) (weight: low)
A streaming deserializer throws IllegalStateException when a caller short-circuits iteration, instead of draining unconsumed items.
ByteBuffer heap vs direct path divergence (weight: low)
VectorCodec deserialization using slice() and rewind() on heap buffers produces different behavior than on direct buffers.
Variadic or positional argument swap in serialization call (weight: low)
Two same-typed arguments swapped in format call, silently producing reversed serialized fields.
Negative-index sentinel encoding mishandled at position zero (weight: low)
Using (-1 - x) as a not-found sentinel conflates the not-found-at-zero case with valid positions.
Role or identity derived from heuristic instead of explicit field (weight: low)
Encoding role as a derived heuristic ("if X == 0, I must be the receiver") is fragile.
Multi-branch size calculation omits a component in one path (weight: low)
When serializedSize() has fast-path and slow-path branches, a component written in all paths may be counted in only one branch.
Missing from §20: Resource Leaks — Specific Patterns
close() / release called recursively or at wrong lifecycle point (weight: low)
A close() method that performs both bookkeeping and native-resource release is called mid-operation, causing double-free or use-after-free.
Fallback code path missing stateful transitions present in primary path (weight: low)
A fallback code path mirrors a primary path but omits seek, flush, or close calls.
abort() Not Overridden in Subclass That Adds Resources (weight: low)
A subclass adds new Closeable resources but inherits the parent's abort(), which does not know about the new resources.
Superclass Method Signature Change Silently Orphans Override (weight: low)
A superclass changes a method signature and the old override silently becomes unreachable dead code without @Override.
Iterator next() Result Not Closed on All Paths (weight: low)
An iterator yields AutoCloseable items but next() results are passed directly to a consumer without try-with-resources.
Half-open to closed range conversion missing +1 shift on upper bound (weight: low)
Wrapping a [a, b) distribution API for a [a, b] range without shifting the upper bound silently excludes the maximum value.
Resource ownership split between task body and lifecycle callback (weight: low)
A resource owner submitted to an executor has close() in task body try/finally, but if the task is cancelled, cleanup never runs.
Resource opened outside normal lifecycle tracking system (weight: low)
A ref-counted resource opened from a non-standard path (snapshot, offline tool) bypasses tracking and is never closed.
Double-close of shared AutoCloseable fires side-effects twice (weight: low)
When both try-with-resources and caller close, side-effects (metric increments, counter decrements) fire twice.
Missing from §12: Filesystem Paths — Specific Patterns
Missing validation for schema feature combination or filesystem constraint (weight: medium)
DDL does not check prerequisites: static columns require clustering columns, COMPACT STORAGE incompatible with collections, table name not validated against filesystem length.
Wrong path, directory, or base resolution (weight: medium)
SSTable written to wrong data directory (bare disk path, not keyspace/table), trigger directory resolved from JAR location.
Name stripping / directory parsing fails on unexpected suffix (weight: low)
Stripping a file extension does not remove additional well-known suffixes (-tmp, -old), or a path-construction step omits a directory level.
State Cleanup — Resources-Domain Patterns (§19)
Metric invisible to reporters, double-counted, stale at read time (weight: medium)
Metric implemented as bare interface invisible to monitoring exporters, chunk cache double-counted, disk-space metric computed at wrong time, or metrics library upgrade silently changes unit contract.
Hinting code path not audited for non-idempotent mutation types (weight: low)
A new mutation type (counter write) is added without auditing the hinting path, which attempts to hint a non-idempotent operation.
Early-Return Path Skips Cursor/Tracking State Update (weight: low)
An early-return path inside an accumulation loop skips the normal "advance the cursor / update tracking state" step.
Additional Patterns from Multi-Project Corpus
Shared ByteBuffer position side-effect on read (weight: high)
- Is a ByteBuffer obtained from a static singleton, cached entry, registry map, method parameter, or cross-thread queue
duplicate()d before ANY relativeget(),getInt,slice(),get(byte[]), comparator read, or checksum update? - Is a no-argument relative
get()used in a comparator that can be called repeatedly on the same shared key? - Does a deserialization helper return a reference to a shared singleton buffer whose position the caller will advance?
- Does a digest / array copy from a heap ByteBuffer use
array()+position()without ALSO addingarrayOffset()? Sliced heap buffers silently produce wrong digests.
Buffer flip vs rewind / channel read completeness (weight: medium)
- After writing into a ByteBuffer, is
flip()called (notrewind())?rewind()leaves limit = capacity and exposes uninitialized bytes past the write point. - Is a channel
read()return value checked (>=0) beforeflip()+get()? Closed-channel reads return 0 and causeBufferUnderflowException. - Is a write buffer flipped before being returned from a
serialize(...)helper (otherwise position == end and no bytes are readable)?
serializedSize double-count / variant confusion (weight: medium)
- Does
serializedSize()delegate to the on-disk / format-specificserializedSizevariant, not the in-memory variant, when the output is on-disk or wire format? - For variable-length fields: does the size include BOTH the length prefix AND the encoded bytes?
- Is
serializedSize()updated after every format change, or does it still describe the prior format? - Is a field's byte count added unconditionally AND re-added inside a conditional branch?
- Is the same
size()result reused for both the total-record size and an embedded sub-record length prefix when the two require different values?
Reference count not acquired before access (weight: medium)
- Is a ref-counted resource's counter incremented BEFORE it is read (not after the null check, not after
containsKey, not afterget)? - Does a CAS-deserialize path acquire the ref while holding the map slot, not after the atomic read?
- Does a read-only scan (cleanup scan, cardinality estimate, diagnostic dump) acquire a ref before traversing backing files?
- Is an "observe-then-acquire" retry loop aware that the resource can be freed between the two steps?
Close-on-shared-dependency / composite close (weight: medium)
- Does
close()release EVERY Closeable field it holds, not just the primary? Audit for omitted delegates when fields were added. - If
close()shuts down a shared static executor / connection pool / registry, is this instance the owner? Non-owning close corrupts sibling instances. - Does a composite appender / container define its own
stop/closethat orders children explicitly rather than relying on child-cascade shutdown?
Cancellation / early-return / partial-construction resource loss (weight: medium)
- If the constructor fails mid-sequence, does
close()tolerate null fields (no NPE during best-effort cleanup)? - Is a field opened in the constructor assigned to the owner field BEFORE any throw / early return?
- Does the submit-after-shutdown-check race release resources held by the partially-constructed task when submit throws
RejectedExecutionException? - Are resources held in a cancellation / exception-handler callback released even when the task body's try / finally never runs?
- Is an iterator returned to a caller still owning its Closeables, or was it wrapped in try-with-resources whose
closefires before the caller reads?
File writer durability / partial-file-as-complete (weight: low)
- Does a
BufferedWriteror serializing writer callflush()+fsync()beforeclose()— not justclose()? - Is an error path guaranteed to call
abort()on the writer so a partial file is distinguishable from a complete one on restart?
Background task in constructor / static-init / short-lived entrypoint (weight: medium)
- Does a constructor start a background thread that might observe partially-constructed state (non-final fields read on the thread before init completes)?
- Does a CLI entry point or short-lived tool call a process-exit helper to reap non-daemon threads before returning?
- Is a periodic task submitted from a
staticinitializer that may fire before its target executor is started? - Is there a strong reference cycle (long-lived owner ↔ scheduled task / leak-detector callback) that prevents garbage collection?
Re-arming / reschedule condition wrong (weight: medium)
- Is the re-arm decision driven by "did work occur" rather than by a transient collection being non-empty or by a side-effecting method's return value?
- For a one-shot scheduled refresh: does it reschedule itself after each successful execution?
Config resolved at construction / cached with no fallback (weight: medium)
- Is a config value resolved lazily per target (supplier / factory), not frozen at construction from a global context where per-target overrides were not yet applied?
- When the underlying config source (file, secret manager) is temporarily unavailable, does the resolver fall back to the last-known-good cached value rather than throw?
- Is a
static finalcaptured at class-load time from a subsystem that the caller must configure first?
Close semantics: null means both closed and not-yet-initialized (weight: low)
- Does a lazy wrapper that clears its delegate to null on close also cause post-close calls to silently re-initialize rather than fail?
- Distinguish "closed" from "not yet initialized" via an explicit state flag.
Management endpoint rename without alias (weight: low)
- Does a rename of a registered metric or management endpoint also register the prior name as a deprecated alias?
- Existing monitoring clients otherwise silently break.
Transient or non-serializable field in Serializable class (weight: low)
- Does a field declared
transientor backed by a non-serializable library type (ByteBuffer, Guava collection, native handle) survive Java serialization, or is it silently null on the far side? - Are dependencies captured in a holder class dropped during serialization, deferring the failure to first use post-deserialization?
Metric present on one branch, missing on sibling (weight: low)
- When a metric is recorded on the success (or timeout) path, is an equivalent recording also present on the paired failure (or success) path?
- Is a metric recorded at the moment the measured condition first holds, rather than inside a timeout callback that fires later?