-
Notifications
You must be signed in to change notification settings - Fork 0
State Lifecycle and Limits
Process-local TTL state is live while
age < ttland expires whenage >= ttl.
This is the common boundary for the deduplication, multipart assembly, and secure-ingress state covered by Campaign B. The Behavioural Contract remains normative for exact observable edge cases; this page explains how the owners fit together in the running Python service.
| State | Authoritative owner | Identity | Clock | TTL | Capacity | Duplicate refresh | Removal reasons | Durable |
|---|---|---|---|---|---|---|---|---|
| Deduplication entries | One process-local Deduplicator, owned by PythonDataPlaneProcessor
|
(scope, logical key); the scope is global or a target_id
|
Local monotonic clock | 30 seconds by default | Optional max_entries; current service wiring uses None
|
No | Expiry, capacity eviction, reset | No |
| Multipart assembly groups | One process-local AIVDMAssembler, owned by PythonDataPlaneProcessor
|
(source_identity, sequential_id, channel, declared_total); the processor passes frame.assembler_key as source_identity
|
Local monotonic clock | 1.0 second by default | Optional max_pending_groups; current service wiring uses None
|
Exact duplicate: no; unique progress: yes | Expiry, conflict, completion, capacity eviction, reset | No |
| Multipart fragment declarations |
AIVDMAssembler admission policy |
Declared total on the incoming sentence | None before admission | Not applicable | Optional max_fragments_per_group; current service wiring uses None
|
Not applicable | Rejected as LIMIT_EXCEEDED before group state is touched |
No |
Multipart TAG s, c, and g context |
PythonDataPlaneProcessor |
Exact assembler AssemblyKey
|
No independent clock | Follows the owning assembler generation | No independent capacity; current assembler group capacity is unbounded | Metadata on an exact duplicate may refine context without refreshing assembly TTL | Assembler-reported conflict, expiry, or capacity removal; completion; reset keys if reset is invoked | No |
| Handshake replay records | Module-wide SecureState
|
Station ID, transmitted timestamp, and signature; peer address is excluded | Local monotonic clock | 60 seconds | 100,000 records | No | Expiry, capacity eviction | No |
| Secure sessions | Module-wide SecureState
|
Exact peer socket address | Local monotonic clock | 300 seconds since last valid activity | 100,000 sessions | Invalid or replayed traffic: no; valid keepalive, ping, or NMEA activity: yes | Expiry, least-recently-active capacity eviction, same-address replacement | No |
| Data nonces | Per-session set owned through SecureState
|
Exact 12-byte nonce within one session | Local monotonic clock | 300 seconds | 100,000 per session | No | Expiry, capacity eviction, owning-session discard | No |
| Routing snapshot and generation | RoutingState |
Current immutable RoutingTable or None, plus an integer generation |
No clock | None | One current snapshot | Not applicable | Whole-snapshot replacement or disable | No |
max_fragments_per_group, max_pending_groups, and deduplication
max_entries are Python constructor capabilities. They are not AISMixer YAML
settings. The current service's PythonDataPlaneProcessor constructs its
assembler and deduplicator with their None capacity defaults, so those
retained-state dimensions are not capacity-bounded in normal runtime wiring.
Secure-state capacities are hard production defaults, but the secure state is
still process-local and non-durable.
Each state domain has one owner:
- The long-lived
PythonDataPlaneProcessorowns its process-localDeduplicator,AIVDMAssembler, multipart TAG contexts, and processing configuration. - Within that processor,
Deduplicatoris authoritative for retained logical-message observations. - Within that processor,
AIVDMAssembleris authoritative for fragment groups and their unique-progress time. -
PythonDataPlaneProcessorseparately owns multipart output metadata associated with those groups. -
SecureStateowns replay records, sessions, per-session nonces, and their statistics. -
RoutingStateowns the active routing snapshot and generation number.
Auxiliary queues and ordering indexes support expiry or deterministic eviction;
they do not define a second state model. In particular, the assembler does not
own output TAG s, c, or g. It reports discarded AssemblyKey values, and
PythonDataPlaneProcessor consumes those keys to keep its metadata context
aligned.
Campaign C did not add a second assembler lifecycle. Production
ParsedSentence values enter through feed_parsed_outcome(), while the legacy
string feed_outcome() API remains valid; both converge on the same state
transitions. The Python assembler still materializes and stores sentence
strings.
Deduplication and multipart assembly use injected local monotonic clocks, with
time.monotonic as the default. Their runtime instances use those defaults.
Wall-clock adjustments therefore do not extend or revive these TTL entries.
UDPSEC separates two clock domains:
- Wall time is used for the externally meaningful handshake freshness check, pong timestamps, and diagnostics. The handshake window accepts a difference of exactly 30 seconds and rejects a larger difference.
- Monotonic time owns replay TTL, session creation and last-seen time, nonce TTL, and local capacity ordering.
Ingress network policy is applied before secure-state clock observation or mutation. Every allowed UDPSEC packet uses one monotonic observation for its local lifecycle decisions.
Routing snapshots and processor-owned TAG contexts have no independent clock. TAG context follows assembler lifecycle notifications.
Rejecting or observing an already-live identity does not normally make it live for longer:
- a deduplication hit retains its original insertion time;
- an exact multipart fragment duplicate does not refresh group progress time;
- a repeated handshake replay key does not refresh its expiry;
- a repeated data nonce does not refresh its expiry.
The intended refresh cases are different:
- a new unique multipart ordinal updates the group progress time;
- a valid matching UDPSEC keepalive, or a fully validated ping or NMEA packet, updates session last-seen time and least-recently-active order.
An exact multipart duplicate may still carry a lower valid TAG c, a new TAG
g observation, or eligible TAG s metadata. That metadata can refine
processor-owned context without refreshing the assembler lifetime.
When bounded state needs capacity, expired state is removed before a live victim is selected:
- deduplication evicts the oldest currently live insertion;
- multipart assembly evicts the least-recently-progressed group, using
AssemblyKeyas a deterministic timestamp tie-break; - handshake replay and data-nonce sets evict their oldest live record;
- secure sessions evict the least-recently-active live session.
These policies apply only when the corresponding capacity is enabled. A live duplicate is rejected without causing a capacity eviction.
Cleanup is operation-driven, not a guarantee of immediate physical removal at
the instant wall time passes. The logical expiry boundary is still exact: an
entry at age == ttl is not live.
Lifecycle accounting keeps removal reasons distinct:
- deduplication distinguishes expiry, capacity eviction, and reset;
- assembly distinguishes expiry, conflict, completion, capacity eviction, and reset;
- session state distinguishes expiry, capacity eviction, and replacement;
- nonce state distinguishes its own expiry or capacity eviction from discard with the owning session.
Processor-owned TAG context has no separate statistics. It is removed because
its assembler generation was discarded, or consumed when that generation
completes. PythonDataPlaneProcessor clears every assembler-reported discarded
key before it observes metadata on the current arrival, preventing stale
context from being attached to a fresh generation.
Deduplicator.stats(), AIVDMAssembler.stats(), and SecureState.stats()
return frozen point-in-time snapshots.
The shared guarantees are:
- reading statistics does not observe a clock;
- reading statistics performs no cleanup;
- cumulative counters remain cumulative across ordinary removals;
- current and peak sizes are reported where applicable;
- an earlier snapshot does not change when live state changes later.
Assembler reset additionally counts the number of groups it discarded. Deduplicator reset clears retained keys and increments the reset-call counter while preserving its other cumulative counters and peak. These statistics are reference and regression-test surfaces; AISMixer does not currently expose them as a complete metrics endpoint.
RoutingState starts at generation 0, whether its initial table is enabled or
None. Reading a snapshot or status does not change that generation.
Every successful replace or disable operation installs a whole new
immutable snapshot and increments the generation by exactly one. This includes
replacing with equivalent content or disabling an already-disabled table.
Callers may supply expected_generation; a stale value rejects the operation
without changing the current snapshot.
After direct frames and valid legacy IngressEvent values cross the
compatibility adapter, the processor stage captures one routing snapshot for
each accepted IngressFrame and supplies the corresponding immutable
ProcessingSnapshot to the processor. PythonDataPlaneProcessor matches
frame.source_id once only when that table is active, and every accepted
sentence from the frame uses the same result. An update during that frame
affects the next accepted frame, not work already using the captured table.
Older immutable snapshots remain safe for existing readers, but RoutingState
owns only the current snapshot and does not maintain durable history.
Routing state has no TTL, capacity eviction, or lifecycle-statistics object. Runtime changes are neither written back to static configuration nor shared with another process.
All state in the table is in memory:
- one long-lived
PythonDataPlaneProcessorowns its processing state for the lifetime of the current runtime process; that state is not shared with another process; - service restart loses deduplication history, pending assemblies, multipart metadata, secure replay/session/nonce state, and runtime routing changes;
- independent processes do not share state;
- no multiprocessing coordinator currently distributes routing generations or processing state;
- runtime routing replacement does not rewrite
config.yaml; - secure cleanup is driven by allowed traffic rather than a background cleanup timer.
This model makes current Python behavior deterministic and testable. It does not provide persistence, restoration, or distributed coordination.
See also Deduplication, Multipart NMEA Assembly, TAG Handling, UDPSEC Security and State, and Runtime Routing Control.
- Quick Start
- Installation and Operations
- OpenWrt Deployment
- Configuration and Examples
- Inputs and Outputs
- Architecture Overview
- Data Representation, Processor, and Runtime Boundary
- Processing and Routing Model
- State, Lifecycle, and Limits
- Multipart NMEA Assembly
- TAG Handling
- Deduplication
- Routing Engine
- Behavioural Contract
- Native-Ready Reference Foundation