Skip to content

Stale composite type cache after ALTER TYPE on a recycled pooled connection #171

Open

Description

@aesslinger

Summary

tokio-postgres's InnerClient.cached_typeinfo.types (a HashMap<Oid, Type>) is warmed by prepare and never invalidated. RecyclingMethod::Fast (the plugin's pool recycle method, src/client.rs) does no cleanup query. So if a composite type is ALTERed (same OID, new field layout) between two queries on the same recycled connection, the second query reads a stale cached Type and CompositeValue::from_sql produces wrong JSON (old field names for new fields, or misaligned field-to-value decoding).

Root cause

  • prepare calls get_type which resolves a composite's Kind::Composite(fields) and caches the Type keyed by OID in InnerClient.cached_typeinfo.types (tokio-postgres prepare.rs:97, client.rs:138).
  • RecyclingMethod::Fast only checks Client::is_closed() on recycle — no DEALLOCATE ALL, no cache reset.
  • ALTER TYPE my_composite ADD ATTRIBUTE new_field text keeps the same OID but changes the field layout. The next prepare's get_type finds the OID in client.type_(oid) (cache hit) and returns the stale Type with the old field list.
  • CompositeValue::from_sql (src/extract.rs) reads fields from the stale cached Type and iterates them against the new wire data → wrong JSON.
  • Enum types are less affected: EnumLabel::from_sql reads raw bytes as UTF-8 and ignores the cached variant list. Composite types are directly affected because their decoder consumes the cached field list as schema.

Widened by PR #168

Pre-#166, the cache was only warmed on the empty-rows fallback path (rare). PR #168 warms it on every direct result-set query via the pre-resolve prepare, making the stale-read window far wider. The underlying issue (no cache invalidation + RecyclingMethod::Fast) is a tokio-postgres/deadpool limitation the plugin inherited, not introduced by #168.

Suggested fix direction

  • Call client.clear_type_cache() on connection recycle (if tokio-postgres 0.7 exposes it publicly — needs verification).
  • Or use a RecyclingMethod::Verified variant that resets session state (at a round-trip cost).
  • Or track the type-cache version and invalidate on ALTER TYPE — likely impractical.
  • The cleanest long-term fix is upstream (tokio-postgres invalidating the cache when the server reports a type change), but that's a tokio-postgres issue.

Scope

Pre-existing tokio-postgres limitation. Widened by #168's per-query cache warming. Filed as a follow-up so the direct-connection hang fix can land without expanding scope into connection-recycle / cache-invalidation.

Activity

  1. self-assigned this
    on Oct 23, 2026
  2. added a commit that references this issue on Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions