Skip to content

Extract a connection-local runtime and centralize lifecycle finalization #643

Description

@leynos

Summary

Extract the mutable state for one accepted connection into an explicit connection-local runtime, leaving PreparedApp immutable and shared.

This is the second application implementation slice for ADR 012 proposal #637. It follows #641 and can land before or after #642 depending on the most reviewable intermediate topology.

Problem

Connection handling currently lives as methods borrowing WireframeApp, while local variables and helper contexts separately carry:

  • the accepted and rewound stream;
  • framed codec state;
  • setup state C;
  • inbound frame pipeline;
  • deserialization failure count;
  • message assembly and fragmentation state;
  • timeout and memory-pressure bookkeeping;
  • protocol ConnectionContext and outbound actor state;
  • peer metadata and teardown decisions.

Because no type owns the full connection lifecycle, cleanup occurs as a clean-path tail call. #549 records the concrete bug: when stream processing returns an error after setup succeeds, teardown is skipped.

Proposed design

Introduce a crate-private ConnectionRuntime or a small family of focused connection-owned types. The exact generic parameter list should favour comprehensibility over mirroring every PreparedApp parameter.

The runtime should own or borrow with an unambiguous connection lifetime:

  • stream/framed codec state;
  • connection state C after setup succeeds;
  • peer and connection identifiers;
  • inbound pipeline, fragmentation, and message-assembly state;
  • per-connection protocol context and hook invocation state;
  • read timeout and effective budget state;
  • a finalization state that guarantees teardown at most once.

Immutable application configuration, route services, serializer/codec templates, application data, and callback definitions remain in PreparedApp.

Lifecycle contract

Once setup returns C, teardown must run exactly once for every terminal connection outcome where Rust unwinding permits cleanup:

  • clean EOF;
  • decode or protocol error;
  • transport read/write error;
  • timeout-driven termination;
  • shutdown/cancellation;
  • handler error;
  • panic caught at the connection-task supervision boundary, if the teardown future can still be invoked safely.

The implementation may use an explicit async finalization block, a state machine, or a guard plus awaited finalizer. Do not rely on Drop to execute async teardown.

This issue does not silently supersede #549. Either land #549 first and preserve its tests, or satisfy all of #549's acceptance criteria explicitly and close it from the implementing PR.

Ownership requirements

  • The runtime is not shared across connection tasks.
  • Mutable pipeline and connection state do not move into Arc<Mutex<_>>.
  • A connection task owns one runtime value and drives it to completion.
  • Values that must escape independently, such as PushHandle, remain cloneable handles.
  • Connection-local Fragmenter ownership from Remove unambiguous local shared-ownership taxes from runtime hot paths #640 remains direct.

Acceptance criteria

  • A named connection-local runtime owns the mutable state for one connection.
  • PreparedApp contains no connection-specific mutable state.
  • Setup state C has one owner after creation.
  • Teardown runs exactly once after successful setup on clean termination.
  • Teardown runs exactly once after decode, protocol, transport, and handler-error termination.
  • Teardown is not invoked when setup never completed.
  • Teardown cannot run twice when an error is followed by outer task finalization.
  • Connection panic supervision remains isolated from the accept loop.
  • [security][medium] Connection teardown hook is skipped on stream-processing errors #549 is either completed by this work or remains explicitly sequenced with no duplicate untested path.
  • Existing fragmentation, assembly, memory-budget, timeout, and EOF behaviour remains unchanged.
  • No new shared mutable application state is introduced.

Tests

Add table-driven or fixture-based coverage for the lifecycle matrix:

Setup Terminal outcome Expected teardown count
fails or never completes any 0
succeeds clean EOF 1
succeeds decode error 1
succeeds protocol error 1
succeeds read error 1
succeeds write error 1
succeeds cancellation 1
succeeds caught connection panic, where safe 1 or an explicitly documented exception

Also assert that the exact C value produced by setup is moved into teardown and cannot be observed by two owners.

Non-goals

  • Changing public lifecycle callback signatures unless the extraction proves it necessary.
  • Consolidating protocol object ownership; tracked separately.
  • Performing user application I/O from a Drop implementation.
  • Moving connection state into the prepared application root.

Dependencies

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions