Skip to content

Consolidate protocol ownership and hook dispatch per connection #644

Description

@leynos

Summary

Represent one installed protocol as one strong owner per connection runtime, rather than cloning the same protocol Arc into six forwarding closures.

At the same time, give every packet-oriented outbound path one consistent hook-dispatch boundary, completing or explicitly coordinating #547 without reopening ADR 010.

Problem

ProtocolHooks::from_protocol currently creates separate boxed closures for:

  • on_connection_setup;
  • before_send;
  • on_command_end;
  • handle_error;
  • stream_end_frame;
  • on_eof.

Each closure captures its own clone of the same protocol Arc. The resulting ProtocolHooks value belongs to one connection actor, so these six strong references do not represent six independent lifetimes.

Separately, #547 records a correctness gap: ordinary WireframeApp responses bypass the configured packet-oriented before_send hook, while actor-driven push and streaming paths invoke it.

Design goals

  • One installed protocol definition may be shared by independent connections.
  • One connection should hold at most one strong protocol reference.
  • Per-connection mutable context remains in ConnectionContext, not inside a shared protocol lock.
  • Existing custom hook construction, if retained, must remain possible without forcing the protocol adapter through six Arcs.
  • Hook ordering and packet-versus-transport semantics remain consistent with ADR 010.

Proposed representation

Use an enum or equivalent explicit representation, for example:

enum ProtocolHooks<F, E> {
    None,
    Protocol(Arc<dyn WireframeProtocol<Frame = F, ProtocolError = E>>),
    Custom(CustomProtocolHooks<F, E>),
}

CustomProtocolHooks may retain boxed FnMut/FnOnce callbacks where callers genuinely install independent closures. The protocol-backed variant dispatches directly to one shared protocol object.

An alternative representation is acceptable if it proves:

  • one strong protocol owner per connection;
  • no six-closure forwarding layer for WireframeProtocol;
  • no loss of custom hook behaviour.

Hook dispatch boundary

Preserve ADR 010's decision:

  • hooks remain packet-oriented;
  • the codec driver owns transport-frame emission;
  • no second transport-frame hook API is introduced.

Ensure the installed protocol observes the intended events for all outbound packet paths:

  • ordinary route-handler responses;
  • push queue frames;
  • streamed responses;
  • multi-packet channels;
  • stream-end terminators.

before_send should execute exactly once for each emitted packet before serialization. Avoid applying it once in the app path and again in the actor path.

Context ownership

Each connection owns one ConnectionContext. Every protocol method for that connection receives the same context instance in the documented order.

Do not place ConnectionContext behind Arc<Mutex<_>>; the connection runtime serializes hook invocation.

Acceptance criteria

  • A protocol-backed connection runtime contains at most one strong reference to the installed protocol.
  • ProtocolHooks::from_protocol no longer creates six protocol-owning forwarding closures.
  • Custom independently registered callbacks remain supported or receive a documented migration path.
  • One ConnectionContext is reused for the complete connection lifetime.
  • before_send runs exactly once for ordinary route-handler responses.
  • before_send continues to run exactly once for push, stream, multi-packet, and terminator frames.
  • on_connection_setup, on_command_end, handle_error, stream_end_frame, and on_eof retain their current ordering and cardinality.
  • No transport-frame-level hook surface is added.
  • [security][medium] WireframeApp responses bypass configured protocol hooks #547 is completed by this work or remains explicitly sequenced with no duplicate dispatch path.
  • Mutation testing: untested public accessor survivors #598 is reviewed: protocol() is retained and tested only if downstream access still has a justified ownership contract.
  • Protocol object drop-count tests demonstrate one connection-held strong owner rather than six.

Tests

Create a recording protocol that captures ordered events and counts strong ownership/drop behaviour. Cover:

  • one ordinary response;
  • one high-priority push;
  • one low-priority push;
  • one streamed response;
  • one multi-packet response and terminator;
  • one protocol error;
  • clean and partial EOF;
  • connection setup and final command completion.

Assert exact event sequences and exactly-once invocation.

Non-goals

  • Changing ADR 010's packet/codec boundary.
  • Making protocol implementations mutable through &mut self; per-connection mutation belongs in ConnectionContext.
  • Removing Arc from the application-wide protocol root when independent connection tasks genuinely share it.
  • Redesigning the public protocol error type beyond what this representation requires.

Dependencies

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingconcurrencyConcurrency, parallelism, and synchronization work, including races and deadlocks.enhancementNew feature or requestmediumRoadmap items to schedule within the current quarter. Clear scope, normal review cycles.performancerefactorBehaviour-preserving restructuring that improves code health.testingTest coverage, test infrastructure, and verification tooling work.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions