Skip to content

v0.32.0 — msgpack state codec

Choose a tag to compare

@rustyconover rustyconover released this 03 Aug 21:28
· 226 commits to main since this release

msgpack replaces the positional state codec

0.31.0 encoded flat stream states with a hand-rolled positional binary layout. That layout took each field's type from its annotation and then trusted it — but the values come from _to_row_dict(), which applies conversions the annotation doesn't describe. An Enum member is emitted as its .name whatever its mixin type, so a field annotated bytes holding a class Ns(bytes, Enum) member reached the encoder as a str and went straight into b"".join: a TypeError, and for kinds whose writer didn't type-check, a silently wrong value.

msgpack encodes what the value actually is, which removes the failure mode rather than patching around it — and takes the field-layout fingerprint with it.

Performance

Measured on the hot-path state, against the codec it replaces:

0.31.0 0.32.0
serialize 1.67 µs 1.18 µs
deserialize 1.45 µs
size 26 B 38 B

Serialize got faster: the C dict packer beats a Python loop over fields. Fields go out as a msgpack map, not an array — an array matches the old 26 B but is positional again, so it would need the fingerprint back to stop a class that gains or loses a field from misparsing older payloads. Names cost a few bytes on a blob that is then zstd-compressed and AEAD-sealed, against states measured at 11–12 KB.

Codec interchangeability

Two guards keep the compact and Arrow paths producing the same object:

  • serialize defers to Arrow when a value's type diverges from its annotation, so a mis-annotated state can't come back different depending on which codec carried it.
  • deserialize skips _convert_value_for_deserialization only on an exact type match. That conversion is Arrow parity, but it measured 1.21 µs per field against 0.14 µs for the whole unpack, and the serialize-side guard makes it provably identity on the common path.

Also in this release

  • fix(http) — retry a request lost to a stale keep-alive connection. The retry loop caught ConnectError/TimeoutException only; a server or load balancer reaping an idle persistent connection surfaces as RemoteProtocolError and fell through as a hard error, where RFC 9110 §6.3 expects recovery. Retry is gated on the disconnect happening before any response byte arrived, which proves the request went unanswered and so cannot be double-applied by a replay.
  • Two test-harness fixes exposed by running the suite under xdist.

Upgrading

  • New dependency: msgpack>=1.0 joins the http extra — the HTTP state token is its only caller. Core stays importable without it; the codec reports "not applicable" and everything falls back to Arrow IPC.
  • Rolling deploys: 0.31.0 and 0.32.0 both use marker byte 0x01 with different payloads, so a 0.32.0 worker rejects a 0.31.0-minted token rather than misreading it. Token TTL makes that self-heal.