Skip to main content

dynamo_runtime/pipeline/network/tcp/
server.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use socket2::{Domain, SockAddr, SockRef, Socket, Type};
5use std::{
6    collections::{HashMap, HashSet},
7    net::{IpAddr, SocketAddr},
8    os::fd::{AsFd, FromRawFd},
9    sync::Arc,
10    time::Duration,
11};
12use tokio::io::{AsyncRead, AsyncWrite};
13use tokio::time::Instant;
14use tokio_rustls::TlsAcceptor;
15
16/// Tombstone lifetime. Bridges the `register()` → `associate_instance()`
17/// window (sub-millisecond in practice); 5s bounds the set by recent worker
18/// churn rather than process lifetime, since etcd lease IDs are unique per
19/// restart and never get cleared by an `Added` event for the same identity.
20const TOMBSTONE_TTL: Duration = Duration::from_secs(5);
21
22/// Bound best-effort worker notification after a response handshake is cancelled.
23const HANDSHAKE_CANCEL_CLEANUP_TIMEOUT: Duration = Duration::from_secs(1);
24
25use bytes::Bytes;
26use derive_builder::Builder;
27use futures::{SinkExt, StreamExt};
28use parking_lot::Mutex;
29
30use serde::{Deserialize, Serialize};
31use tokio::{
32    io::AsyncWriteExt,
33    sync::{mpsc, oneshot},
34    time,
35};
36use tokio_util::codec::{FramedRead, FramedWrite};
37
38use super::{
39    CallHomeHandshake, ControlMessage, PendingConnections, RegisteredStream, StreamOptions,
40    StreamReceiver, StreamSender, TcpStreamConnectionInfo, TwoPartCodec, bind_listener,
41};
42use crate::discovery::EndpointInstanceId;
43use crate::engine::AsyncEngineContext;
44use crate::error::{DynamoError, ErrorType};
45use crate::pipeline::{
46    PipelineError,
47    network::{
48        ResponseService, ResponseStreamPrologue, StreamPrologueError,
49        codec::{TwoPartMessage, TwoPartMessageType},
50        tcp::StreamType,
51    },
52};
53use crate::utils::ip_resolver::{resolve_host_or_interface, resolve_local_host};
54use anyhow::{Result, anyhow as error};
55
56pub use crate::utils::ip_resolver::{DefaultIpResolver, IpResolver};
57
58#[allow(dead_code)]
59type ResponseType = TwoPartMessage;
60
61#[derive(Debug, Serialize, Deserialize, Clone, Builder, Default)]
62pub struct ServerOptions {
63    #[builder(default = "0")]
64    pub port: u16,
65
66    /// IP literal, bracketed IPv6 literal, wildcard, or network interface name.
67    /// When unset, Dynamo selects a local address automatically.
68    /// The field name is retained for source compatibility.
69    #[builder(default)]
70    pub interface: Option<String>,
71}
72
73impl ServerOptions {
74    pub fn builder() -> ServerOptionsBuilder {
75        ServerOptionsBuilder::default()
76    }
77}
78
79/// A [`TcpStreamServer`] is a TCP service that listens on a port for incoming response connections.
80/// A Response connection is a connection that is established by a client with the intention of sending
81/// specific data back to the server.
82pub struct TcpStreamServer {
83    address: String,
84    state: Arc<Mutex<State>>,
85}
86
87// pub struct TcpStreamReceiver {
88//     address: TcpStreamConnectionInfo,
89//     state: Arc<Mutex<State>>,
90//     rx: mpsc::Receiver<ResponseType>,
91// }
92
93#[allow(dead_code)]
94struct RequestedSendConnection {
95    context: Arc<dyn AsyncEngineContext>,
96    connection: oneshot::Sender<Result<StreamSender, StreamPrologueError>>,
97    /// Capacity of the per-stream mpsc buffer between the socket task and the
98    /// engine producer; carried from the registration [`StreamOptions`].
99    send_buffer_count: usize,
100}
101
102struct RequestedRecvConnection {
103    context: Arc<dyn AsyncEngineContext>,
104    connection: oneshot::Sender<Result<StreamReceiver, StreamPrologueError>>,
105    /// Remains addressable after the worker's TCP call-home so discovery
106    /// removal can interrupt a stalled response prologue.
107    cancellation: tokio_util::sync::CancellationToken,
108    /// Capacity of the per-stream mpsc buffer between the socket task and the
109    /// engine consumer; carried from the registration [`StreamOptions`].
110    send_buffer_count: usize,
111    defer_cancellation_until_prologue: bool,
112}
113
114/// Build the per-stream data-plane mpsc channel that bridges the socket task
115/// and the engine producer/consumer. The capacity is driven by the
116/// registration options ([`StreamOptions::send_buffer_count`]) rather than a
117/// hard-coded constant; both `process_request_stream` and
118/// `process_response_stream` size their channel through this helper. See #10293.
119fn data_plane_channel<T>(send_buffer_count: usize) -> (mpsc::Sender<T>, mpsc::Receiver<T>) {
120    // `tokio::sync::mpsc::channel` panics on a capacity of 0. Now that the value
121    // is caller-configurable via `StreamOptions::send_buffer_count`, clamp to at
122    // least 1 so a misconfigured `0` degrades to a minimal buffer instead of
123    // panicking the connection handler task.
124    mpsc::channel(send_buffer_count.max(1))
125}
126
127// /// When registering a new TcpStream on the server, the registration method will return a [`Connections`] object.
128// /// This [`Connections`] object will have two [`oneshot::Receiver`] objects, one for the [`TcpStreamSender`] and one for the [`TcpStreamReceiver`].
129// /// The [`Connections`] object can be awaited to get the [`TcpStreamSender`] and [`TcpStreamReceiver`] objects; these objects will
130// /// be made available when the matching Client has connected to the server.
131// pub struct Connections {
132//     pub address: TcpStreamConnectionInfo,
133
134//     /// The [`oneshot::Receiver`] for the [`TcpStreamSender`]. Awaiting this object will return the [`TcpStreamSender`] object once
135//     /// the client has connected to the server.
136//     pub sender: Option<oneshot::Receiver<StreamSender>>,
137
138//     /// The [`oneshot::Receiver`] for the [`TcpStreamReceiver`]. Awaiting this object will return the [`TcpStreamReceiver`] object once
139//     /// the client has connected to the server.
140//     pub receiver: Option<oneshot::Receiver<StreamReceiver>>,
141// }
142
143#[derive(Default)]
144struct State {
145    tx_subjects: HashMap<String, RequestedSendConnection>,
146    rx_subjects: HashMap<String, RequestedRecvConnection>,
147    /// Response registrations remain cancellable after `rx_subjects` is
148    /// consumed by call-home and until the response prologue is accepted.
149    rx_cancellations: HashMap<String, tokio_util::sync::CancellationToken>,
150    /// subject UUID -> EndpointInstanceId. Full 4-field key isolates services
151    /// that share an endpoint name across namespaces/components.
152    subject_instance: HashMap<String, EndpointInstanceId>,
153    /// EndpointInstanceId -> tagged subject UUIDs, for batch cancellation on
154    /// removal. The `StreamType` tag tells `cancel_instance_streams` which
155    /// of `rx_subjects` / `tx_subjects` holds the registration so both halves
156    /// of a bidirectional session get dropped together.
157    instance_subjects: HashMap<EndpointInstanceId, HashSet<(StreamType, String)>>,
158    /// Tombstones (instance -> insertion time) close the
159    /// `cancel_instance_streams` vs `associate_instance` race; entries expire
160    /// after [`TOMBSTONE_TTL`].
161    removed_instances: HashMap<EndpointInstanceId, Instant>,
162    handle: Option<tokio::task::JoinHandle<Result<()>>>,
163}
164
165struct ResponseRegistrationGuard {
166    state: Arc<Mutex<State>>,
167    subject: String,
168    is_active: bool,
169}
170
171impl ResponseRegistrationGuard {
172    /// Serialize prologue acceptance with cancellation under the registration lock.
173    /// A removed cancellation entry means cancellation already won.
174    fn accept(mut self) -> bool {
175        self.is_active = false;
176        TcpStreamServer::finish_response_stream(&self.state, &self.subject)
177    }
178}
179
180impl Drop for ResponseRegistrationGuard {
181    fn drop(&mut self) {
182        if self.is_active {
183            TcpStreamServer::finish_response_stream(&self.state, &self.subject);
184        }
185    }
186}
187
188/// Drop tombstones older than [`TOMBSTONE_TTL`]. Called lazily on every
189/// `associate_instance` / `cancel_instance_streams` to bound the set size.
190fn prune_tombstones(tombstones: &mut HashMap<EndpointInstanceId, Instant>, now: Instant) {
191    tombstones.retain(|_, ts| now.saturating_duration_since(*ts) < TOMBSTONE_TTL);
192}
193
194impl TcpStreamServer {
195    pub fn local_address(&self) -> Result<SocketAddr> {
196        Ok(self.address.parse()?)
197    }
198
199    pub fn options_builder() -> ServerOptionsBuilder {
200        ServerOptionsBuilder::default()
201    }
202
203    pub async fn new(options: ServerOptions) -> Result<Arc<Self>, PipelineError> {
204        Self::new_with_resolver(options, DefaultIpResolver).await
205    }
206
207    pub async fn new_with_resolver<R: IpResolver>(
208        options: ServerOptions,
209        resolver: R,
210    ) -> Result<Arc<Self>, PipelineError> {
211        let resolved_host = if let Some(host_or_interface) = options.interface.as_deref() {
212            resolve_host_or_interface(host_or_interface, &resolver).map_err(|error| {
213                PipelineError::Generic(format!(
214                    "Failed to resolve configured TCP host '{host_or_interface}': {error}"
215                ))
216            })?
217        } else {
218            resolve_local_host(&resolver).map_err(|error| {
219                PipelineError::Generic(format!("Failed to resolve local IP address: {error}"))
220            })?
221        };
222
223        if resolved_host.used_loopback_fallback() {
224            tracing::warn!(
225                bind_ip = %resolved_host.bind_ip(),
226                advertise_ip = %resolved_host.advertise_ip(),
227                "No usable local IP address found; advertising loopback"
228            );
229        }
230
231        let state = Arc::new(Mutex::new(State::default()));
232
233        // Build TLS acceptor from environment if cert+key paths are configured.
234        let tls_acceptor = Self::build_tls_acceptor().map_err(|e| {
235            PipelineError::Generic(format!("Failed to build TCP TLS acceptor: {}", e))
236        })?;
237
238        let bind_address = SocketAddr::new(resolved_host.bind_ip(), options.port);
239        let local_address = Self::start(bind_address, state.clone(), tls_acceptor)
240            .await
241            .map_err(|error| {
242                PipelineError::Generic(format!("Failed to start TcpStreamServer: {error}"))
243            })?;
244        let advertised_address =
245            SocketAddr::new(resolved_host.advertise_ip(), local_address.port());
246        let address = advertised_address.to_string();
247
248        tracing::debug!(%local_address, %advertised_address, "tcp transport service started");
249
250        Ok(Arc::new(Self { address, state }))
251    }
252
253    /// Associate one or both halves of a registration with a backend instance.
254    ///
255    /// `recv_subject` is the response-stream subject (always present on TCP);
256    /// `send_subject` is the request-stream subject, set only for
257    /// bidirectional sessions. Tracking the send half here is what lets
258    /// [`Self::cancel_instance_streams`] drop the request-stream
259    /// `tx_subjects` oneshot directly when discovery removes the worker,
260    /// instead of relying on the cascade from the recv-side cancellation.
261    ///
262    /// Returns `false` if the instance is already tombstoned, in which case
263    /// both subjects are cancelled immediately and the caller should skip
264    /// `send_request` and fail with a migratable `Disconnected` error.
265    pub async fn associate_instance(
266        &self,
267        recv_subject: &str,
268        send_subject: Option<&str>,
269        id: &EndpointInstanceId,
270    ) -> bool {
271        let mut state = self.state.lock();
272        let now = Instant::now();
273        prune_tombstones(&mut state.removed_instances, now);
274        if state.removed_instances.contains_key(id) {
275            // Instance was already removed -- cancel immediately.
276            tracing::warn!(
277                recv_subject,
278                send_subject,
279                namespace = %id.namespace,
280                component = %id.component,
281                endpoint = %id.endpoint,
282                instance_id = id.instance_id,
283                "Cancelling subject immediately: instance already removed (tombstoned)"
284            );
285            state.rx_subjects.remove(recv_subject);
286            if let Some(token) = state.rx_cancellations.remove(recv_subject) {
287                token.cancel();
288            }
289            if let Some(s) = send_subject {
290                state.tx_subjects.remove(s);
291            }
292            return false;
293        }
294        state
295            .subject_instance
296            .insert(recv_subject.to_string(), id.clone());
297        if let Some(s) = send_subject {
298            state.subject_instance.insert(s.to_string(), id.clone());
299        }
300        let entry = state.instance_subjects.entry(id.clone()).or_default();
301        entry.insert((StreamType::Response, recv_subject.to_string()));
302        if let Some(s) = send_subject {
303            entry.insert((StreamType::Request, s.to_string()));
304        }
305        true
306    }
307
308    /// Associate a request-only callback registration with a backend instance.
309    /// QUIC response mode still uses TCP for bidirectional request callbacks.
310    pub async fn associate_request_instance(&self, subject: &str, id: &EndpointInstanceId) -> bool {
311        let mut state = self.state.lock();
312        let now = Instant::now();
313        prune_tombstones(&mut state.removed_instances, now);
314        if state.removed_instances.contains_key(id) {
315            state.tx_subjects.remove(subject);
316            return false;
317        }
318        state
319            .subject_instance
320            .insert(subject.to_string(), id.clone());
321        state
322            .instance_subjects
323            .entry(id.clone())
324            .or_default()
325            .insert((StreamType::Request, subject.to_string()));
326        true
327    }
328
329    /// Cancel one pending response-stream registration. Drops the
330    /// `oneshot::Sender` so the waiting receiver resolves with `RecvError`.
331    pub async fn cancel_recv_stream(&self, subject: &str) {
332        let mut state = self.state.lock();
333        state.rx_subjects.remove(subject);
334        if let Some(token) = state.rx_cancellations.remove(subject) {
335            token.cancel();
336        }
337        if let Some(key) = state.subject_instance.remove(subject)
338            && let Some(subjects) = state.instance_subjects.get_mut(&key)
339        {
340            subjects.remove(&(StreamType::Response, subject.to_string()));
341            if subjects.is_empty() {
342                state.instance_subjects.remove(&key);
343            }
344        }
345    }
346
347    /// Cancel one pending request-stream registration. Parallel to
348    /// [`Self::cancel_recv_stream`]: drops the `tx_subjects` entry and, if
349    /// the subject was associated with an instance, clears its
350    /// `(StreamType::Request, _)` tag from `instance_subjects` so the per-
351    /// instance bookkeeping stays consistent.
352    pub async fn cancel_send_stream(&self, subject: &str) {
353        let mut state = self.state.lock();
354        state.tx_subjects.remove(subject);
355        if let Some(key) = state.subject_instance.remove(subject)
356            && let Some(subjects) = state.instance_subjects.get_mut(&key)
357        {
358            subjects.remove(&(StreamType::Request, subject.to_string()));
359            if subjects.is_empty() {
360                state.instance_subjects.remove(&key);
361            }
362        }
363    }
364
365    /// Cancel all pending stream handshakes for an instance — both response-
366    /// side and request-side halves of any bidirectional sessions tracked by
367    /// `associate_instance` — and tombstone the id so any racing associate
368    /// for the same id cancels too. Returns the number of streams cancelled.
369    pub async fn cancel_instance_streams(&self, id: &EndpointInstanceId) -> usize {
370        let mut state = self.state.lock();
371        let now = Instant::now();
372        prune_tombstones(&mut state.removed_instances, now);
373        state.removed_instances.insert(id.clone(), now);
374        let subjects = match state.instance_subjects.remove(id) {
375            Some(subjects) => subjects,
376            None => return 0,
377        };
378        let count = subjects.len();
379        for (kind, subject) in &subjects {
380            match kind {
381                StreamType::Response => {
382                    state.rx_subjects.remove(subject);
383                    if let Some(token) = state.rx_cancellations.remove(subject) {
384                        token.cancel();
385                    }
386                }
387                StreamType::Request => {
388                    state.tx_subjects.remove(subject);
389                }
390            }
391            state.subject_instance.remove(subject);
392        }
393        count
394    }
395
396    /// Drop the tombstone for an instance that has reappeared in discovery,
397    /// so future subjects for that identity are tracked normally.
398    pub async fn clear_instance_tombstone(&self, id: &EndpointInstanceId) {
399        let mut state = self.state.lock();
400        state.removed_instances.remove(id);
401    }
402
403    /// Build a TLS acceptor from env vars if `DYN_TCP_TLS_CERT_PATH` and
404    /// `DYN_TCP_TLS_KEY_PATH` are set. Validation and diagnostics are shared with
405    /// the request-plane server via
406    /// [`crate::tls_utils::server_tls_acceptor_config`]; a client CA enables mTLS.
407    fn build_tls_acceptor() -> anyhow::Result<Option<TlsAcceptor>> {
408        use crate::config::environment_names::tcp_response_stream::tls as env;
409        let cert_path = std::env::var(env::DYN_TCP_TLS_CERT_PATH).ok();
410        let key_path = std::env::var(env::DYN_TCP_TLS_KEY_PATH).ok();
411        let client_ca = std::env::var(env::DYN_TCP_TLS_CLIENT_CA_CERT_PATH).ok();
412        Ok(crate::tls_utils::server_tls_acceptor_config(
413            "TCP server",
414            cert_path.as_deref().map(std::path::Path::new),
415            key_path.as_deref().map(std::path::Path::new),
416            client_ca.as_deref().map(std::path::Path::new),
417        )?
418        .map(|config| TlsAcceptor::from(Arc::new(config))))
419    }
420
421    async fn start(
422        bind_address: SocketAddr,
423        state: Arc<Mutex<State>>,
424        tls_acceptor: Option<TlsAcceptor>,
425    ) -> Result<SocketAddr> {
426        let state_clone = state.clone();
427        let (ready_tx, ready_rx) = tokio::sync::oneshot::channel::<Result<SocketAddr>>();
428        {
429            let mut guard = state.lock();
430            if guard.handle.is_some() {
431                panic!("TcpStreamServer already started");
432            }
433            guard.handle = Some(tokio::spawn(tcp_listener(
434                bind_address,
435                state_clone,
436                tls_acceptor,
437                ready_tx,
438            )));
439        }
440        ready_rx.await?
441    }
442
443    fn insert_request_stream(&self, subject: String, connection: RequestedSendConnection) {
444        self.state.lock().tx_subjects.insert(subject, connection);
445    }
446
447    fn insert_response_stream(&self, subject: String, connection: RequestedRecvConnection) {
448        let cancellation = connection.cancellation.clone();
449        let mut state = self.state.lock();
450        state.rx_subjects.insert(subject.clone(), connection);
451        state.rx_cancellations.insert(subject, cancellation);
452    }
453
454    fn take_request_stream(state: &Mutex<State>, subject: &str) -> Option<RequestedSendConnection> {
455        let mut state = state.lock();
456        let connection = state.tx_subjects.remove(subject);
457        if let Some(key) = state.subject_instance.remove(subject)
458            && let Some(subjects) = state.instance_subjects.get_mut(&key)
459        {
460            subjects.remove(&(StreamType::Request, subject.to_string()));
461            if subjects.is_empty() {
462                state.instance_subjects.remove(&key);
463            }
464        }
465        connection
466    }
467
468    fn take_response_stream(
469        state: &Mutex<State>,
470        subject: &str,
471    ) -> Option<RequestedRecvConnection> {
472        state.lock().rx_subjects.remove(subject)
473    }
474
475    /// Finish tracking a response registration after its prologue is accepted
476    /// or its connection handler exits. Returns whether cancellation had not
477    /// already removed the registration.
478    fn finish_response_stream(state: &Mutex<State>, subject: &str) -> bool {
479        let mut state = state.lock();
480        let is_pending = state.rx_cancellations.remove(subject).is_some();
481        if let Some(key) = state.subject_instance.remove(subject)
482            && let Some(subjects) = state.instance_subjects.get_mut(&key)
483        {
484            subjects.remove(&(StreamType::Response, subject.to_string()));
485            if subjects.is_empty() {
486                state.instance_subjects.remove(&key);
487            }
488        }
489        is_pending
490    }
491}
492
493// todo - possible rename ResponseService to ResponseServer
494#[async_trait::async_trait]
495impl ResponseService for TcpStreamServer {
496    /// Register a new subject and sender with the response subscriber
497    /// Produces an RAII object that will deregister the subject when dropped
498    ///
499    /// we need to register both data in and data out entries
500    /// there might be forward pipeline that want to consume the data out stream
501    /// and there might be a response stream that wants to consume the data in stream
502    /// on registration, we need to specific if we want data-in, data-out or both
503    /// this will map to the type of service that is runniing, i.e. Single or Many In //
504    /// Single or Many Out
505    ///
506    /// todo(ryan) - return a connection object that can be awaited. when successfully connected,
507    /// can ask for the sender and receiver
508    ///
509    /// OR
510    ///
511    /// we make it into register sender and register receiver, both would return a connection object
512    /// and when a connection is established, we'd get the respective sender or receiver
513    ///
514    /// the registration probably needs to be done in one-go, so we should use a builder object for
515    /// requesting a receiver and optional sender
516    async fn register(&self, options: StreamOptions) -> PendingConnections {
517        // oneshot channels to pass back the sender and receiver objects
518
519        let address = self.address.clone();
520        tracing::debug!("Registering new TcpStream on {address}");
521
522        let send_stream = if options.enable_request_stream {
523            let sender_subject = uuid::Uuid::new_v4().to_string();
524            let registry_subject = sender_subject.clone();
525
526            let (pending_sender_tx, pending_sender_rx) = oneshot::channel();
527
528            let connection_info = RequestedSendConnection {
529                context: options.context.clone(),
530                connection: pending_sender_tx,
531                send_buffer_count: options.send_buffer_count,
532            };
533
534            let cleanup_subject = sender_subject.clone();
535            let cleanup_state = self.state.clone();
536            let registered_stream = RegisteredStream::new(
537                TcpStreamConnectionInfo {
538                    address: address.clone(),
539                    subject: sender_subject,
540                    context: options.context.id().to_string(),
541                    stream_type: StreamType::Request,
542                }
543                .into(),
544                pending_sender_rx,
545            )
546            .with_cleanup(move || {
547                // Drop is sync; fire-and-forget the lock acquisition.
548                tokio::spawn(async move {
549                    let mut state = cleanup_state.lock();
550                    state.tx_subjects.remove(&cleanup_subject);
551                    if let Some(key) = state.subject_instance.remove(&cleanup_subject)
552                        && let Some(subjects) = state.instance_subjects.get_mut(&key)
553                    {
554                        subjects.remove(&(StreamType::Request, cleanup_subject.clone()));
555                        if subjects.is_empty() {
556                            state.instance_subjects.remove(&key);
557                        }
558                    }
559                });
560            });
561
562            self.insert_request_stream(registry_subject, connection_info);
563
564            Some(registered_stream)
565        } else {
566            None
567        };
568
569        let recv_stream = if options.enable_response_stream {
570            let (pending_recver_tx, pending_recver_rx) = oneshot::channel();
571            let receiver_subject = uuid::Uuid::new_v4().to_string();
572            let registry_subject = receiver_subject.clone();
573
574            let connection_info = RequestedRecvConnection {
575                context: options.context.clone(),
576                connection: pending_recver_tx,
577                cancellation: tokio_util::sync::CancellationToken::new(),
578                send_buffer_count: options.send_buffer_count,
579                defer_cancellation_until_prologue: options.defer_cancellation_until_prologue,
580            };
581
582            let cleanup_subject = receiver_subject.clone();
583            let cleanup_state = self.state.clone();
584            let registered_stream = RegisteredStream::new(
585                TcpStreamConnectionInfo {
586                    address: address.clone(),
587                    subject: receiver_subject,
588                    context: options.context.id().to_string(),
589                    stream_type: StreamType::Response,
590                }
591                .into(),
592                pending_recver_rx,
593            )
594            .with_cleanup(move || {
595                // Drop is sync; fire-and-forget the lock acquisition.
596                tokio::spawn(async move {
597                    let mut state = cleanup_state.lock();
598                    state.rx_subjects.remove(&cleanup_subject);
599                    if let Some(token) = state.rx_cancellations.remove(&cleanup_subject) {
600                        token.cancel();
601                    }
602                    if let Some(key) = state.subject_instance.remove(&cleanup_subject)
603                        && let Some(subjects) = state.instance_subjects.get_mut(&key)
604                    {
605                        subjects.remove(&(StreamType::Response, cleanup_subject.clone()));
606                        if subjects.is_empty() {
607                            state.instance_subjects.remove(&key);
608                        }
609                    }
610                });
611            });
612
613            self.insert_response_stream(registry_subject, connection_info);
614
615            Some(registered_stream)
616        } else {
617            None
618        };
619
620        PendingConnections {
621            send_stream,
622            recv_stream,
623        }
624    }
625}
626
627/// First retry delay applied after an `AcceptFailure::Exhaustion`.
628const ACCEPT_BACKOFF_INITIAL_DELAY: Duration = Duration::from_millis(5);
629/// Ceiling the retry delay saturates at, so the listener keeps polling often
630/// enough to notice recovery while no longer spinning.
631const ACCEPT_BACKOFF_MAX_DELAY: Duration = Duration::from_secs(1);
632/// Minimum wall-clock interval between two emitted exhaustion summaries.
633const ACCEPT_BACKOFF_LOG_INTERVAL: Duration = Duration::from_secs(5);
634
635/// How the listener loop must treat a failed `accept()`.
636#[derive(Debug, Clone, Copy, PartialEq, Eq)]
637enum AcceptFailure {
638    /// The process or the host is out of file descriptors or kernel memory
639    /// — see `AcceptBackoff::classify` for the exact errno set. Retrying
640    /// immediately cannot succeed, so the loop must back off.
641    Exhaustion,
642    /// Anything else — a per-connection error that the client is expected to
643    /// retry. Keeps the historical warn-and-retry-immediately behavior.
644    Ordinary,
645}
646
647/// What the caller should do about one exhaustion failure.
648#[derive(Debug, Clone, Copy, PartialEq, Eq)]
649struct AcceptBackoffAction {
650    /// How long to sleep before calling `accept()` again.
651    delay: Duration,
652    /// `Some(n)` when the caller should emit one summary line, where `n` is the
653    /// number of failures suppressed since the previous emission. `None` means
654    /// this failure is inside the current rate-limit window and must stay silent.
655    log_suppressed: Option<u64>,
656}
657
658/// Bounded exponential retry policy for the accept loop, kept deliberately pure:
659/// it reads no clock, performs no I/O and never sleeps. The caller supplies the
660/// current `Instant` and performs the sleep, which is what lets the retry
661/// schedule and the log-rate decision be unit-tested without reproducing the
662/// host's file-descriptor limit. Vocabulary mirrors `transports::etcd::connector`'s
663/// `BackoffState`.
664#[derive(Debug)]
665struct AcceptBackoff {
666    initial_delay: Duration,
667    max_delay: Duration,
668    log_interval: Duration,
669    current_delay: Duration,
670    suppressed: u64,
671    last_log_at: Option<std::time::Instant>,
672    /// Whether the loop is currently inside an exhaustion episode — the flag
673    /// `record_success` checks to tell a real recovery from an ordinary
674    /// steady-state accept.
675    in_backoff: bool,
676}
677
678impl Default for AcceptBackoff {
679    fn default() -> Self {
680        Self {
681            initial_delay: ACCEPT_BACKOFF_INITIAL_DELAY,
682            max_delay: ACCEPT_BACKOFF_MAX_DELAY,
683            log_interval: ACCEPT_BACKOFF_LOG_INTERVAL,
684            current_delay: ACCEPT_BACKOFF_INITIAL_DELAY,
685            suppressed: 0,
686            last_log_at: None,
687            in_backoff: false,
688        }
689    }
690}
691
692impl AcceptBackoff {
693    /// Classify an `accept()` error. Only descriptor or kernel-memory exhaustion
694    /// gets the backoff treatment; everything else stays on the pre-existing
695    /// path so genuinely unexpected failures are not hidden or slowed down.
696    fn classify(err: &std::io::Error) -> AcceptFailure {
697        #[cfg(unix)]
698        {
699            // `std::io::ErrorKind` has no stable variant for these errnos, so
700            // the raw value is the portable-with-cfg way to recognize them.
701            // `EMFILE`/`ENFILE` are descriptor exhaustion; `ENOBUFS`/`ENOMEM`
702            // are kernel out-of-memory for the socket. All four make an
703            // immediate retry deterministic, so they get the backoff path.
704            if matches!(
705                err.raw_os_error(),
706                Some(libc::EMFILE) | Some(libc::ENFILE) | Some(libc::ENOBUFS) | Some(libc::ENOMEM)
707            ) {
708                return AcceptFailure::Exhaustion;
709            }
710        }
711        #[cfg(not(unix))]
712        {
713            let _ = err;
714        }
715        AcceptFailure::Ordinary
716    }
717
718    /// Record one exhaustion failure and return the delay to sleep plus the
719    /// rate-limited logging decision. The delay doubles per consecutive failure
720    /// and saturates at `max_delay`.
721    fn record_exhaustion(&mut self, now: std::time::Instant) -> AcceptBackoffAction {
722        self.in_backoff = true;
723
724        let delay = self.current_delay;
725        self.current_delay = self.current_delay.saturating_mul(2).min(self.max_delay);
726
727        let due = match self.last_log_at {
728            None => true,
729            Some(prev) => now.saturating_duration_since(prev) >= self.log_interval,
730        };
731
732        let log_suppressed = if due {
733            self.last_log_at = Some(now);
734            Some(std::mem::take(&mut self.suppressed))
735        } else {
736            self.suppressed += 1;
737            None
738        };
739
740        AcceptBackoffAction {
741            delay,
742            log_suppressed,
743        }
744    }
745
746    /// Record a successful accept. `Some(suppressed)` only when this success
747    /// both ended an exhaustion episode and the shared log-rate window allows
748    /// a line — so a listener flapping at the descriptor ceiling cannot emit
749    /// an unbounded stream of recovery lines, and a suppressed recovery keeps
750    /// its count for the next emission rather than discarding it. The clock
751    /// arrives as a closure, not an `Instant`, so the steady-state accept
752    /// (every ordinary connection) hits the early return below without
753    /// reading a clock at all.
754    fn record_success(&mut self, now: impl FnOnce() -> std::time::Instant) -> Option<u64> {
755        if !self.in_backoff {
756            return None;
757        }
758        self.current_delay = self.initial_delay;
759        self.in_backoff = false;
760        let now = now();
761
762        let due = match self.last_log_at {
763            None => true,
764            Some(prev) => now.saturating_duration_since(prev) >= self.log_interval,
765        };
766        if !due {
767            return None;
768        }
769        self.last_log_at = Some(now);
770        Some(std::mem::take(&mut self.suppressed))
771    }
772}
773
774/// Apply the accept-loop error policy to one failed `accept()`. Returns the
775/// delay that was actually slept — `Duration::ZERO` for ordinary errors, which
776/// keep the historical immediate-retry behavior.
777async fn handle_accept_error(err: &std::io::Error, backoff: &mut AcceptBackoff) -> Duration {
778    match AcceptBackoff::classify(err) {
779        AcceptFailure::Ordinary => {
780            // the client should retry, so we don't need to abort
781            tracing::warn!(error = %err, "failed to accept tcp connection");
782            // Gated like the sibling in `handle_connection`: an unconditional
783            // stderr write here doubles the log volume of any accept-error storm.
784            #[cfg(debug_assertions)]
785            eprintln!("failed to accept tcp connection: {}", err);
786            Duration::ZERO
787        }
788        AcceptFailure::Exhaustion => {
789            crate::metrics::transport_metrics::TCP_ACCEPT_BACKOFF_TOTAL.inc();
790            let action = backoff.record_exhaustion(std::time::Instant::now());
791            if let Some(suppressed) = action.log_suppressed {
792                tracing::warn!(
793                    error = %err,
794                    retry_delay_ms = action.delay.as_millis() as u64,
795                    suppressed_failures = suppressed,
796                    "tcp accept failed: out of file descriptors or kernel memory; backing off before retry"
797                );
798            }
799            time::sleep(action.delay).await;
800            action.delay
801        }
802    }
803}
804
805// Type aliases for boxed split halves used throughout the nested handlers below.
806type BoxRead = Box<dyn tokio::io::AsyncRead + Unpin + Send>;
807type BoxWrite = Box<dyn tokio::io::AsyncWrite + Unpin + Send>;
808
809/// Unblock the requester before attempting any network cleanup. A stalled
810/// worker must not keep either the requester or this socket task alive.
811async fn cancel_response_handshake(
812    connection: oneshot::Sender<Result<StreamReceiver, StreamPrologueError>>,
813    registration_guard: Option<ResponseRegistrationGuard>,
814    mut writer: FramedWrite<BoxWrite, TwoPartCodec>,
815) {
816    drop(registration_guard);
817    drop(connection);
818    let cleanup = async move {
819        if let Ok(bytes) = serde_json::to_vec(&ControlMessage::Kill) {
820            let _ = writer.send(TwoPartMessage::from_header(bytes.into())).await;
821        }
822        let _ = writer.into_inner().shutdown().await;
823    };
824    if time::timeout(HANDSHAKE_CANCEL_CLEANUP_TIMEOUT, cleanup)
825        .await
826        .is_err()
827    {
828        tracing::debug!("timed out notifying worker of cancelled response handshake");
829    }
830}
831
832// this method listens on a tcp port for incoming connections
833// new connections are expected to send a protocol specific handshake
834// for us to determine the subject they are interested in, in this case,
835// we expect the first message to be [`FirstMessage`] from which we find
836// the sender, then we spawn a task to forward all bytes from the tcp stream
837// to the sender
838async fn tcp_listener(
839    addr: SocketAddr,
840    state: Arc<Mutex<State>>,
841    tls_acceptor: Option<TlsAcceptor>,
842    read_tx: tokio::sync::oneshot::Sender<Result<SocketAddr>>,
843) -> Result<()> {
844    let listener = bind_listener(addr)
845        .map_err(|e| anyhow::anyhow!("Failed to start TcpListender on {}: {}", addr, e));
846
847    let listener = match listener {
848        Ok(listener) => {
849            let addr = listener
850                .local_addr()
851                .map_err(|e| anyhow::anyhow!("Failed get SocketAddr: {:?}", e))
852                .unwrap();
853
854            read_tx.send(Ok(addr)).expect("Failed to send ready signal");
855
856            listener
857        }
858        Err(e) => {
859            read_tx.send(Err(e)).expect("Failed to send ready signal");
860            return Err(anyhow::anyhow!("Failed to start TcpListender on {}", addr));
861        }
862    };
863
864    let mut accept_backoff = AcceptBackoff::default();
865
866    loop {
867        // todo - add instrumentation
868        // todo - add counter for all accepted connections
869        // todo - add gauge for all inflight connections
870        // todo - add counter for incoming bytes
871        // todo - add counter for outgoing bytes
872        let (stream, _addr) = match listener.accept().await {
873            Ok((stream, _addr)) => {
874                if let Some(suppressed) = accept_backoff.record_success(std::time::Instant::now) {
875                    tracing::warn!(
876                        suppressed_failures = suppressed,
877                        "tcp accept recovered from resource exhaustion"
878                    );
879                }
880                (stream, _addr)
881            }
882            Err(e) => {
883                handle_accept_error(&e, &mut accept_backoff).await;
884                continue;
885            }
886        };
887
888        match stream.set_nodelay(true) {
889            Ok(_) => (),
890            Err(e) => {
891                tracing::warn!("failed to set tcp stream to nodelay: {e}");
892            }
893        }
894
895        match SockRef::from(&stream).set_linger(Some(std::time::Duration::from_secs(0))) {
896            Ok(_) => (),
897            Err(e) => {
898                tracing::warn!("failed to set tcp stream to linger: {e}");
899            }
900        }
901
902        // Spawn per-connection so the accept loop is never blocked by a slow
903        // TLS handshake. The handshake is bounded by DYN_TCP_TLS_HANDSHAKE_TIMEOUT_SECS (default 3s).
904        let state_clone = state.clone();
905        let tls_acceptor_clone = tls_acceptor.clone();
906        tokio::spawn(async move {
907            let (reader, writer) = if let Some(ref tls) = tls_acceptor_clone {
908                match tokio::time::timeout(
909                    crate::tls_utils::handshake_timeout(),
910                    tls.accept(stream),
911                )
912                .await
913                {
914                    Ok(Ok(tls_stream)) => {
915                        let (r, w) = tokio::io::split(tls_stream);
916                        (Box::new(r) as BoxRead, Box::new(w) as BoxWrite)
917                    }
918                    Ok(Err(e)) => {
919                        tracing::warn!("TLS handshake failed: {e}");
920                        return;
921                    }
922                    Err(_) => {
923                        tracing::warn!("TLS handshake timed out");
924                        return;
925                    }
926                }
927            } else {
928                let (r, w) = tokio::io::split(stream);
929                (Box::new(r) as BoxRead, Box::new(w) as BoxWrite)
930            };
931            handle_connection(reader, writer, state_clone).await;
932        });
933    }
934
935    // #[instrument(level = "trace"), skip(state)]
936    // todo - clone before spawn and trace process_stream
937    async fn handle_connection(reader: BoxRead, writer: BoxWrite, state: Arc<Mutex<State>>) {
938        let result = process_stream(reader, writer, state).await;
939        match result {
940            Ok(_) => tracing::trace!("successfully processed tcp connection"),
941            Err(e) => {
942                tracing::warn!("failed to handle tcp connection: {e}");
943                #[cfg(debug_assertions)]
944                eprintln!("failed to handle tcp connection: {}", e);
945            }
946        }
947    }
948
949    /// This method is responsible for the internal tcp stream handshake
950    /// The handshake will specialize the stream as a request/sender or response/receiver stream
951    async fn process_stream(
952        read_half: BoxRead,
953        write_half: BoxWrite,
954        state: Arc<Mutex<State>>,
955    ) -> Result<()> {
956        // attach the codec to the reader and writer to get framed readers and writers
957        let mut framed_reader = FramedRead::new(read_half, TwoPartCodec::default());
958        let framed_writer = FramedWrite::new(write_half, TwoPartCodec::default());
959
960        // the internal tcp [`CallHomeHandshake`] connects the socket to the requester
961        // here we await this first message as a raw bytes two part message
962        let first_message =
963            tokio::time::timeout(std::time::Duration::from_secs(10), framed_reader.next())
964                .await
965                .map_err(|_| error!("Timed out waiting for CallHomeHandshake"))?
966                .ok_or(error!("Connection closed without a ControlMessage"))??;
967
968        // we await on the raw bytes which should come in as a header only message
969        // todo - improve error handling - check for no data
970        let handshake: CallHomeHandshake = match first_message.header() {
971            Some(header) => serde_json::from_slice(header).map_err(|e| {
972                error!(
973                    "Failed to deserialize the first message as a valid `CallHomeHandshake`: {e}",
974                )
975            })?,
976            None => {
977                return Err(error!("Expected ControlMessage, got DataMessage"));
978            }
979        };
980
981        // branch here to handle sender stream or receiver stream
982        match handshake.stream_type {
983            StreamType::Request => {
984                process_request_stream(handshake.subject, state, framed_reader, framed_writer).await
985            }
986            StreamType::Response => {
987                process_response_stream(handshake.subject, state, framed_reader, framed_writer)
988                    .await
989            }
990        }
991    }
992
993    /// Symmetric to [`process_response_stream`] for the upstream→downstream
994    /// data direction: deliver the [`StreamSender`] half registered by the
995    /// upstream to whoever awaits it, then pump every frame the upstream pushes
996    /// into the now-connected TCP socket.
997    ///
998    /// One difference is that the request stream is **unidirectional**:
999    /// the upstream writes data + one closing control message, and the
1000    /// downstream is not expected to reply: downstream response or inference
1001    /// error should be returned through response stream. We therefore drop the
1002    /// read half, on fatal error, downstream should drop the request stream.
1003    async fn process_request_stream(
1004        subject: String,
1005        state: Arc<Mutex<State>>,
1006        reader: FramedRead<BoxRead, TwoPartCodec>,
1007        writer: FramedWrite<BoxWrite, TwoPartCodec>,
1008    ) -> Result<()> {
1009        // Request stream is unidirectional; we don't read from the downstream.
1010        drop(reader);
1011
1012        let request_stream = TcpStreamServer::take_request_stream(&state, &subject).ok_or_else(|| {
1013            error!(
1014                "Subject not found: {}; downstream subscriber specified a subject unknown to the upstream publisher",
1015                subject
1016            )
1017        })?;
1018
1019        let RequestedSendConnection {
1020            context,
1021            connection,
1022            send_buffer_count,
1023        } = request_stream;
1024
1025        // Buffer size is driven by the registration options
1026        // ([`StreamOptions::send_buffer_count`]) rather than hard-coded; the
1027        // same applies to `process_response_stream`. See #10293.
1028        let (request_tx, request_rx) = data_plane_channel(send_buffer_count);
1029
1030        if connection
1031            .send(Ok(crate::pipeline::network::StreamSender {
1032                tx: request_tx,
1033                // Request streams don't carry a downstream-prologue today; the
1034                // upstream may begin sending immediately.
1035                prologue: None,
1036            }))
1037            .is_err()
1038        {
1039            return Err(error!(
1040                "The requester of the request stream has been dropped before the connection was established"
1041            ));
1042        }
1043
1044        request_stream_send_handler(writer, request_rx, context).await;
1045        Ok(())
1046    }
1047
1048    /// Pump frames the upstream queued on its `StreamSender` into the TCP socket.
1049    /// The closing control message depends on why the loop exited:
1050    /// - `context.killed()` → [`ControlMessage::Kill`] (hard cancel notification)
1051    /// - `context.stopped()` → [`ControlMessage::Stop`] (graceful cancel notification)
1052    /// - `request_rx` returns `None` → [`ControlMessage::Sentinel`] (clean EOS)
1053    /// - write error → no control message, the socket is already broken
1054    ///
1055    /// The downstream `handle_request_reader` matches on the received variant
1056    /// and reacts accordingly.
1057    async fn request_stream_send_handler(
1058        mut framed_writer: FramedWrite<BoxWrite, TwoPartCodec>,
1059        mut request_rx: mpsc::Receiver<TwoPartMessage>,
1060        context: Arc<dyn AsyncEngineContext>,
1061    ) {
1062        // Construct the cancellation futures once. Recreating them for every frame clones
1063        // the context's watch receivers and repeatedly registers/drops Tokio notifications.
1064        let killed = context.killed();
1065        let stopped = context.stopped();
1066        tokio::pin!(killed, stopped);
1067
1068        let closing_msg: Option<ControlMessage> = loop {
1069            tokio::select! {
1070                biased;
1071
1072                _ = &mut killed => {
1073                    tracing::trace!("context kill received in request-stream send handler");
1074                    break Some(ControlMessage::Kill);
1075                }
1076
1077                _ = &mut stopped => {
1078                    tracing::trace!("context stop received in request-stream send handler");
1079                    break Some(ControlMessage::Stop);
1080                }
1081
1082                msg = request_rx.recv() => {
1083                    match msg {
1084                        Some(msg) => {
1085                            if let Err(e) = framed_writer.send(msg).await {
1086                                tracing::trace!(
1087                                    "failed to send request-stream frame to downstream: {:?}",
1088                                    e
1089                                );
1090                                break None;
1091                            }
1092                        }
1093                        None => {
1094                            tracing::trace!("upstream request-stream sender closed; sending sentinel");
1095                            break Some(ControlMessage::Sentinel);
1096                        }
1097                    }
1098                }
1099            }
1100        };
1101
1102        if let Some(ctrl) = closing_msg
1103            && let Ok(bytes) = serde_json::to_vec(&ctrl)
1104            && let Err(err) = framed_writer
1105                .send(TwoPartMessage::from_header(bytes.into()))
1106                .await
1107        {
1108            tracing::trace!(?err, ?ctrl, "request-stream closing-frame send failed");
1109        }
1110
1111        let mut inner = framed_writer.into_inner();
1112        if let Err(err) = inner.flush().await {
1113            tracing::trace!(?err, "request-stream socket flush failed");
1114        }
1115        if let Err(err) = inner.shutdown().await {
1116            tracing::trace!(?err, "request-stream socket shutdown failed");
1117        }
1118    }
1119
1120    async fn process_response_stream(
1121        subject: String,
1122        state: Arc<Mutex<State>>,
1123        mut reader: FramedRead<BoxRead, TwoPartCodec>,
1124        mut writer: FramedWrite<BoxWrite, TwoPartCodec>,
1125    ) -> Result<()> {
1126        let response_stream = TcpStreamServer::take_response_stream(&state, &subject).ok_or_else(|| {
1127            error!("Subject not found: {}; upstream publisher specified a subject unknown to the downsteam subscriber", subject)
1128        })?;
1129        let registration_guard = ResponseRegistrationGuard {
1130            state,
1131            subject,
1132            is_active: true,
1133        };
1134
1135        // unwrap response_stream
1136        let RequestedRecvConnection {
1137            context,
1138            mut connection,
1139            cancellation,
1140            send_buffer_count,
1141            defer_cancellation_until_prologue,
1142        } = response_stream;
1143
1144        // the [`Prologue`]
1145        // there must be a second control message it indicate the other segment's generate method was successful
1146        // No timeout here: the worker sends the prologue only after generate() setup completes,
1147        // which can take arbitrarily long (model load, queue delay, cold start).
1148        let prologue = tokio::select! {
1149            biased;
1150            _ = cancellation.cancelled() => {
1151                cancel_response_handshake(connection, Some(registration_guard), writer).await;
1152                return Ok(());
1153            }
1154            _ = context.killed(), if !defer_cancellation_until_prologue => Err(ControlMessage::Kill),
1155            _ = connection.closed() => Err(ControlMessage::Kill),
1156            _ = context.stopped(), if !defer_cancellation_until_prologue => Err(ControlMessage::Stop),
1157            prologue = reader.next() => Ok(prologue),
1158        };
1159        let prologue = match prologue {
1160            Ok(prologue) => {
1161                prologue.ok_or(error!("Connection closed without a ControlMessage"))??
1162            }
1163            Err(control) => {
1164                drop(registration_guard);
1165                let message = "Request cancelled before response stream was established";
1166                let error = DynamoError::builder()
1167                    .error_type(ErrorType::Cancelled)
1168                    .message(message)
1169                    .build();
1170                let _ = connection.send(Err(StreamPrologueError::new(message, error)));
1171                writer
1172                    .send(TwoPartMessage::from_header(
1173                        serde_json::to_vec(&control)?.into(),
1174                    ))
1175                    .await?;
1176                writer.close().await?;
1177                return Ok(());
1178            }
1179        };
1180
1181        // deserialize prologue
1182        let prologue = match prologue.into_message_type() {
1183            TwoPartMessageType::HeaderOnly(header) => {
1184                match serde_json::from_slice::<ResponseStreamPrologue>(&header) {
1185                    Ok(prologue) => prologue,
1186                    Err(e) => {
1187                        // Notify the requester as the sibling arm does. Returning on
1188                        // `?` alone drops the oneshot un-sent, and the requester then
1189                        // reports a bare disconnect that names neither the worker's
1190                        // failure nor this one.
1191                        let msg = format!("malformed prologue: {e}");
1192                        let _ =
1193                            connection.send(Err(StreamPrologueError::from_message(msg.clone())));
1194                        return Err(error!(msg));
1195                    }
1196                }
1197            }
1198            _ => {
1199                // Worker sent a non-HeaderOnly frame in the prologue slot
1200                // (protocol violation, version skew, corruption). Notify the
1201                // requester so the generate call chain fails cleanly, then
1202                // return Err so the connection task ends without panicking.
1203                let msg = "malformed prologue: expected HeaderOnly ControlMessage";
1204                let _ = connection.send(Err(StreamPrologueError::from_message(msg)));
1205                return Err(error!(msg));
1206            }
1207        };
1208
1209        // await the control message of GTG or Error, if error, then connection.send(Err(String)), which should fail the
1210        // generate call chain
1211        //
1212        // note: this second control message might be delayed, but the expensive part of setting up the connection
1213        // is both complete and ready for data flow; awaiting here is not a performance hit or problem and it allows
1214        // us to trace the initial setup time vs the time to prologue
1215        if let Some(error) = prologue.error {
1216            let returned = error!("Received error prologue: {error}");
1217            // Forward the worker's typed error so the requesting side can classify
1218            // the failure instead of parsing the message. An older worker sends none.
1219            let _ = connection.send(Err(StreamPrologueError {
1220                message: error,
1221                typed_error: prologue.typed_error,
1222            }));
1223            return Err(returned);
1224        }
1225
1226        // Discovery removal prevents new work but does not abort an established
1227        // response stream. From this point normal transport failure or the
1228        // request context owns cancellation.
1229        if !registration_guard.accept() {
1230            cancel_response_handshake(connection, None, writer).await;
1231            return Ok(());
1232        }
1233
1234        // Buffer size is driven by the registration options
1235        // ([`StreamOptions::send_buffer_count`]) rather than hard-coded; the
1236        // same applies to `process_request_stream`. See #10293.
1237        let (response_tx, response_rx) = data_plane_channel(send_buffer_count);
1238
1239        if connection
1240            .send(Ok(crate::pipeline::network::StreamReceiver {
1241                rx: response_rx,
1242            }))
1243            .is_err()
1244        {
1245            return Err(error!(
1246                "The requester of the stream has been dropped before the connection was established"
1247            ));
1248        }
1249
1250        let (control_tx, control_rx) = mpsc::channel::<ControlMessage>(1);
1251
1252        // sender task
1253        // issues control messages to the sender and when finished shuts down the socket
1254        // this should be the last task to finish and must
1255        let send_task = tokio::spawn(network_send_handler(writer, control_rx));
1256
1257        // forward task
1258        let recv_task = tokio::spawn(network_receive_handler(
1259            reader,
1260            response_tx,
1261            control_tx,
1262            context.clone(),
1263        ));
1264
1265        // check the results of each of the tasks
1266        let (monitor_result, forward_result) = tokio::join!(send_task, recv_task);
1267
1268        monitor_result?;
1269        forward_result?;
1270
1271        Ok(())
1272    }
1273
1274    async fn network_receive_handler(
1275        mut framed_reader: FramedRead<BoxRead, TwoPartCodec>,
1276        response_tx: mpsc::Sender<Bytes>,
1277        control_tx: mpsc::Sender<ControlMessage>,
1278        context: Arc<dyn AsyncEngineContext>,
1279    ) {
1280        // These futures stay pending across frames. Constructing them inside the loop clones
1281        // watch receivers and registers/drops notifications for every streamed token.
1282        let response_closed = response_tx.closed();
1283        let killed = context.killed();
1284        let stopped = context.stopped();
1285        tokio::pin!(response_closed, killed, stopped);
1286
1287        // loop over reading the tcp stream and checking if the writer is closed
1288        let mut can_stop = true;
1289        loop {
1290            tokio::select! {
1291                biased;
1292
1293                _ = &mut response_closed => {
1294                    tracing::trace!("response channel closed before the client finished writing data");
1295                    let _ = control_tx.send(ControlMessage::Kill).await;
1296                    break;
1297                }
1298
1299                _ = &mut killed => {
1300                    tracing::trace!("context kill signal received; shutting down");
1301                    let _ = control_tx.send(ControlMessage::Kill).await;
1302                    break;
1303                }
1304
1305                _ = &mut stopped, if can_stop => {
1306                    tracing::trace!("context stop signal received; shutting down");
1307                    // `stopped` is now complete; keep this branch disabled because polling
1308                    // the same completed async future again would panic.
1309                    can_stop = false;
1310                    let _ = control_tx.send(ControlMessage::Stop).await;
1311                }
1312
1313                msg = framed_reader.next() => {
1314                    match msg {
1315                        Some(Ok(msg)) => {
1316                            let (header, data) = msg.into_parts();
1317
1318                            // received a control message
1319                            if !header.is_empty() {
1320                                match process_control_message(header) {
1321                                    Ok(ControlAction::Continue) => {}
1322                                    Ok(ControlAction::Shutdown) => {
1323                                        if !data.is_empty() {
1324                                            // Sentinel-with-data is a protocol
1325                                            // violation; kill this stream, don't
1326                                            // assert!() the process down.
1327                                            tracing::warn!(
1328                                                data_len = data.len(),
1329                                                "client sent Sentinel with data (protocol violation); killing stream"
1330                                            );
1331                                            let _ = control_tx.send(ControlMessage::Kill).await;
1332                                            break;
1333                                        }
1334                                        tracing::trace!("received sentinel message; shutting down");
1335                                        break;
1336                                    }
1337                                    Err(e) => {
1338                                        // Malformed control message — kill only
1339                                        // this stream.
1340                                        tracing::warn!(err = ?e, "malformed control message, closing connection");
1341                                        let _ = control_tx.send(ControlMessage::Kill).await;
1342                                        break;
1343                                    }
1344                                }
1345                            }
1346
1347                            if !data.is_empty()
1348                                && let Err(err) = response_tx.send(data).await {
1349                                    tracing::debug!(?err, "forwarding body/data to response channel failed");
1350                                    let _ = control_tx.send(ControlMessage::Kill).await;
1351                                    break;
1352                                };
1353                        }
1354                        Some(Err(e)) => {
1355                            // TCP RST or decode error from worker — kill only
1356                            // this stream.
1357                            tracing::warn!(err = ?e, "tcp stream read error from worker, closing connection");
1358                            let _ = control_tx.send(ControlMessage::Kill).await;
1359                            break;
1360                        }
1361                        None => {
1362                            // this is allowed but we try to avoid it
1363                            // the logic is that the client will tell us when its is done and the server
1364                            // will close the connection naturally when the sentinel message is received
1365                            // the client closing early represents a transport error outside the control of the
1366                            // transport library
1367                            tracing::trace!("tcp stream was closed by client");
1368                            break;
1369                        }
1370                    }
1371                }
1372
1373            }
1374        }
1375    }
1376
1377    async fn network_send_handler(
1378        socket_tx: FramedWrite<BoxWrite, TwoPartCodec>,
1379        control_rx: mpsc::Receiver<ControlMessage>,
1380    ) {
1381        let mut socket_tx = socket_tx;
1382        let mut control_rx = control_rx;
1383
1384        while let Some(control_msg) = control_rx.recv().await {
1385            // Sentinel is a worker→frontend message; receiving one here means
1386            // a producer is buggy. Skip rather than asserting — a stream-level
1387            // bug must not panic the worker.
1388            if matches!(control_msg, ControlMessage::Sentinel) {
1389                tracing::warn!("received sentinel on send-side control channel; dropping");
1390                continue;
1391            }
1392            let bytes = match serde_json::to_vec(&control_msg) {
1393                Ok(b) => b,
1394                Err(e) => {
1395                    // Closed enum of small variants; serialization shouldn't
1396                    // fail. If it ever does, log and skip rather than panic.
1397                    tracing::warn!(err = ?e, ?control_msg, "failed to serialize control message");
1398                    continue;
1399                }
1400            };
1401            let message = TwoPartMessage::from_header(bytes.into());
1402            match socket_tx.send(message).await {
1403                Ok(_) => tracing::debug!(?control_msg, "issued control message"),
1404                Err(e) => {
1405                    tracing::debug!(err = ?e, ?control_msg, "failed to send control message")
1406                }
1407            }
1408        }
1409
1410        let mut inner = socket_tx.into_inner();
1411        if let Err(e) = inner.flush().await {
1412            tracing::debug!("failed to flush socket: {e}");
1413        }
1414        if let Err(e) = inner.shutdown().await {
1415            tracing::debug!("failed to shutdown socket: {e}");
1416        }
1417    }
1418}
1419
1420enum ControlAction {
1421    Continue,
1422    Shutdown,
1423}
1424
1425fn process_control_message(message: Bytes) -> Result<ControlAction> {
1426    match serde_json::from_slice::<ControlMessage>(&message)? {
1427        ControlMessage::Sentinel => {
1428            // the client issued a sentinel message
1429            // it has finished writing data and is now awaiting the server to close the connection
1430            tracing::trace!("sentinel received; shutting down");
1431            Ok(ControlAction::Shutdown)
1432        }
1433        ControlMessage::Kill | ControlMessage::Stop => {
1434            // Worker→frontend control direction only carries Sentinel. Kill/Stop
1435            // here is a protocol violation; the caller turns this Err into a
1436            // stream-local Kill rather than a process-fatal event.
1437            anyhow::bail!("unexpected control message on response stream");
1438        }
1439    }
1440}
1441
1442#[cfg(test)]
1443mod tests {
1444    use super::*;
1445    use crate::engine::AsyncEngineContextProvider;
1446    use crate::error::{BackendError, DynamoError, ErrorType};
1447    use crate::pipeline::Context;
1448    use crate::pipeline::network::DEFAULT_SEND_BUFFER_COUNT;
1449    use crate::pipeline::network::tcp::client::TcpClient;
1450    use crate::tls_utils::test_certs::self_signed_pair;
1451    use crate::utils::ip_resolver::test_support::{ProbeOutcome, StubResolver};
1452    use tokio::io::{AsyncWriteExt, ReadHalf, WriteHalf};
1453    use tokio::net::TcpStream;
1454
1455    #[test]
1456    fn build_tls_acceptor_no_env_vars_is_plaintext() {
1457        if crate::test_utils::run_isolated(
1458            concat!(
1459                module_path!(),
1460                "::build_tls_acceptor_no_env_vars_is_plaintext"
1461            ),
1462            &[],
1463        ) {
1464            return;
1465        }
1466
1467        // Also clear the client-CA var: ambient it would turn this into an error
1468        // (client CA without a server cert/key) instead of plaintext.
1469        temp_env::with_vars_unset(
1470            [
1471                "DYN_TCP_TLS_CERT_PATH",
1472                "DYN_TCP_TLS_KEY_PATH",
1473                "DYN_TCP_TLS_CLIENT_CA_CERT_PATH",
1474            ],
1475            || {
1476                assert!(TcpStreamServer::build_tls_acceptor().unwrap().is_none());
1477            },
1478        );
1479    }
1480
1481    #[test]
1482    fn build_tls_acceptor_partial_config_errors() {
1483        if crate::test_utils::run_isolated(
1484            concat!(module_path!(), "::build_tls_acceptor_partial_config_errors"),
1485            &[],
1486        ) {
1487            return;
1488        }
1489
1490        let (cert, key) = self_signed_pair();
1491        let cert_str = cert.path().to_str().unwrap();
1492        let key_str = key.path().to_str().unwrap();
1493        // only cert
1494        temp_env::with_vars(
1495            [
1496                ("DYN_TCP_TLS_CERT_PATH", Some(cert_str)),
1497                ("DYN_TCP_TLS_KEY_PATH", None),
1498            ],
1499            || assert!(TcpStreamServer::build_tls_acceptor().is_err()),
1500        );
1501        // only key
1502        temp_env::with_vars(
1503            [
1504                ("DYN_TCP_TLS_CERT_PATH", None),
1505                ("DYN_TCP_TLS_KEY_PATH", Some(key_str)),
1506            ],
1507            || assert!(TcpStreamServer::build_tls_acceptor().is_err()),
1508        );
1509    }
1510
1511    #[test]
1512    fn build_tls_acceptor_both_paths_is_tls() {
1513        if crate::test_utils::run_isolated(
1514            concat!(module_path!(), "::build_tls_acceptor_both_paths_is_tls"),
1515            &[],
1516        ) {
1517            return;
1518        }
1519
1520        let (cert, key) = self_signed_pair();
1521        temp_env::with_vars(
1522            [
1523                ("DYN_TCP_TLS_CERT_PATH", Some(cert.path().to_str().unwrap())),
1524                ("DYN_TCP_TLS_KEY_PATH", Some(key.path().to_str().unwrap())),
1525            ],
1526            || assert!(TcpStreamServer::build_tls_acceptor().unwrap().is_some()),
1527        );
1528    }
1529
1530    #[test]
1531    fn build_tls_acceptor_with_client_ca_is_mtls() {
1532        if crate::test_utils::run_isolated(
1533            concat!(
1534                module_path!(),
1535                "::build_tls_acceptor_with_client_ca_is_mtls"
1536            ),
1537            &[],
1538        ) {
1539            return;
1540        }
1541
1542        // A client CA turns the response-stream server into an mTLS acceptor.
1543        let (cert, key) = self_signed_pair();
1544        temp_env::with_vars(
1545            [
1546                ("DYN_TCP_TLS_CERT_PATH", Some(cert.path().to_str().unwrap())),
1547                ("DYN_TCP_TLS_KEY_PATH", Some(key.path().to_str().unwrap())),
1548                (
1549                    "DYN_TCP_TLS_CLIENT_CA_CERT_PATH",
1550                    Some(cert.path().to_str().unwrap()),
1551                ),
1552            ],
1553            || assert!(TcpStreamServer::build_tls_acceptor().unwrap().is_some()),
1554        );
1555    }
1556
1557    #[test]
1558    fn build_tls_acceptor_client_ca_without_server_identity_errors() {
1559        if crate::test_utils::run_isolated(
1560            concat!(
1561                module_path!(),
1562                "::build_tls_acceptor_client_ca_without_server_identity_errors"
1563            ),
1564            &[],
1565        ) {
1566            return;
1567        }
1568
1569        let (cert, _key) = self_signed_pair();
1570        temp_env::with_vars(
1571            [
1572                ("DYN_TCP_TLS_CERT_PATH", None),
1573                ("DYN_TCP_TLS_KEY_PATH", None),
1574                (
1575                    "DYN_TCP_TLS_CLIENT_CA_CERT_PATH",
1576                    Some(cert.path().to_str().unwrap()),
1577                ),
1578            ],
1579            || assert!(TcpStreamServer::build_tls_acceptor().is_err()),
1580        );
1581    }
1582
1583    async fn registered_tcp_info(server: &TcpStreamServer) -> TcpStreamConnectionInfo {
1584        let context = Context::new(());
1585        let stream_options = StreamOptions::builder()
1586            .context(context.context())
1587            .enable_request_stream(false)
1588            .enable_response_stream(true)
1589            .build()
1590            .unwrap();
1591
1592        let pending_connection = server.register(stream_options).await;
1593        let connection_info = pending_connection
1594            .recv_stream
1595            .as_ref()
1596            .unwrap()
1597            .connection_info
1598            .clone();
1599        connection_info.try_into().unwrap()
1600    }
1601
1602    #[tokio::test]
1603    async fn wildcard_bind_advertises_concrete_ipv4_with_bound_port() {
1604        for host in ["0.0.0.0", "::ffff:0.0.0.0", " [::ffff:0.0.0.0] "] {
1605            let mut resolver = StubResolver::not_found();
1606            resolver
1607                .interfaces
1608                .push(("eth0", "192.0.2.20".parse().unwrap()));
1609            let options = ServerOptions::builder()
1610                .port(0)
1611                .interface(Some(host.to_string()))
1612                .build()
1613                .unwrap();
1614            let server = TcpStreamServer::new_with_resolver(options, resolver)
1615                .await
1616                .unwrap();
1617
1618            let tcp_info = registered_tcp_info(&server).await;
1619            let socket_addr: SocketAddr = tcp_info.address.parse().unwrap();
1620            assert_eq!(socket_addr.ip(), "192.0.2.20".parse::<IpAddr>().unwrap());
1621            assert_ne!(socket_addr.port(), 0);
1622            assert!(!socket_addr.ip().is_unspecified());
1623            // The advertised port must belong to the real wildcard listener.
1624            let _connection = tokio::net::TcpStream::connect(SocketAddr::new(
1625                std::net::Ipv4Addr::LOCALHOST.into(),
1626                socket_addr.port(),
1627            ))
1628            .await
1629            .unwrap();
1630        }
1631    }
1632
1633    #[tokio::test]
1634    async fn real_bracketed_ipv6_host_binds_and_registers() {
1635        if let Err(error) = std::net::TcpListener::bind("[::1]:0") {
1636            eprintln!("Skipping IPv6 loopback bind test: {error}");
1637            return;
1638        }
1639
1640        let options = ServerOptions::builder()
1641            .port(0)
1642            .interface(Some("[::1]".to_string()))
1643            .build()
1644            .unwrap();
1645        let server = TcpStreamServer::new_with_resolver(options, StubResolver::not_found())
1646            .await
1647            .unwrap();
1648
1649        let tcp_info = registered_tcp_info(&server).await;
1650        let socket_addr: SocketAddr = tcp_info.address.parse().unwrap();
1651        assert_eq!(socket_addr.ip(), IpAddr::V6(std::net::Ipv6Addr::LOCALHOST));
1652        assert_ne!(socket_addr.port(), 0);
1653        assert!(
1654            tcp_info.address.starts_with("[::1]:"),
1655            "{}",
1656            tcp_info.address
1657        );
1658    }
1659
1660    #[tokio::test]
1661    async fn tcp_server_preserves_interface_enumeration_error() {
1662        let mut resolver = StubResolver::not_found();
1663        resolver.interface_error = Some(ProbeOutcome::Platform("test-platform"));
1664        let options = ServerOptions::builder().port(0).build().unwrap();
1665        let error = TcpStreamServer::new_with_resolver(options, resolver)
1666            .await
1667            .err()
1668            .expect("an interface enumeration failure must be returned");
1669
1670        let error = error.to_string();
1671        assert!(
1672            error.contains("failed to enumerate network interfaces"),
1673            "{error}"
1674        );
1675        assert!(error.contains("test-platform"), "{error}");
1676    }
1677
1678    #[tokio::test]
1679    async fn test_tcp_stream_server_default_behavior() {
1680        // Test that TcpStreamServer::new works with default options
1681        // This verifies normal operation when IP detection succeeds
1682        let options = ServerOptions::default();
1683        let result = TcpStreamServer::new(options).await;
1684
1685        assert!(
1686            result.is_ok(),
1687            "TcpStreamServer::new should succeed with default options"
1688        );
1689
1690        let server = result.unwrap();
1691
1692        let tcp_info = registered_tcp_info(&server).await;
1693        let socket_addr = tcp_info.address.parse::<std::net::SocketAddr>().unwrap();
1694
1695        // Should have a valid port assigned
1696        assert!(
1697            socket_addr.port() > 0,
1698            "Server should be assigned a valid port number"
1699        );
1700
1701        println!(
1702            "Server created successfully with address: {}",
1703            tcp_info.address
1704        );
1705    }
1706
1707    /// The data-plane channel helper sizes the mpsc buffer from
1708    /// `send_buffer_count` — this is the value `process_request_stream` /
1709    /// `process_response_stream` feed it. `max_capacity()` reflects the
1710    /// channel's configured buffer, so a custom value and the default both
1711    /// reach the channel. Guards against regressing back to a hard-coded 64.
1712    #[test]
1713    fn data_plane_channel_capacity_matches_send_buffer_count() {
1714        let (tx, _rx) = data_plane_channel::<()>(7);
1715        assert_eq!(tx.max_capacity(), 7);
1716
1717        let (tx, _rx) = data_plane_channel::<()>(DEFAULT_SEND_BUFFER_COUNT);
1718        assert_eq!(tx.max_capacity(), 64);
1719
1720        // A misconfigured 0 must clamp to 1, not panic (mpsc::channel(0) panics).
1721        let (tx, _rx) = data_plane_channel::<()>(0);
1722        assert_eq!(tx.max_capacity(), 1);
1723    }
1724
1725    /// `register` must thread `StreamOptions::send_buffer_count` through to the
1726    /// stored `RequestedSendConnection` / `RequestedRecvConnection` (the
1727    /// registration structs `process_*_stream` later destructure to size the
1728    /// channel). Verified here against the real registration path.
1729    #[tokio::test]
1730    async fn register_threads_send_buffer_count_into_connection_structs() {
1731        let server = TcpStreamServer::new(ServerOptions::default())
1732            .await
1733            .expect("server");
1734        let context = Context::new(());
1735        let options = StreamOptions::builder()
1736            .context(context.context())
1737            .enable_request_stream(true)
1738            .enable_response_stream(true)
1739            .send_buffer_count(7)
1740            .build()
1741            .unwrap();
1742
1743        let _pending = server.register(options).await;
1744
1745        let state = server.state.lock();
1746        assert_eq!(state.tx_subjects.len(), 1, "one request stream registered");
1747        assert_eq!(state.rx_subjects.len(), 1, "one response stream registered");
1748        assert!(
1749            state.tx_subjects.values().all(|c| c.send_buffer_count == 7),
1750            "send_buffer_count must reach RequestedSendConnection"
1751        );
1752        assert!(
1753            state.rx_subjects.values().all(|c| c.send_buffer_count == 7),
1754            "send_buffer_count must reach RequestedRecvConnection"
1755        );
1756    }
1757
1758    #[tokio::test]
1759    async fn test_tcp_stream_server_fallback_to_loopback() {
1760        // Test fallback behavior using a mock resolver that always fails
1761        // This guarantees the fallback logic is triggered
1762
1763        let options = ServerOptions::builder().port(0).build().unwrap();
1764
1765        // Use the failing resolver to force the fallback
1766        let result = TcpStreamServer::new_with_resolver(options, StubResolver::not_found()).await;
1767        assert!(
1768            result.is_ok(),
1769            "Server creation should succeed with fallback even when IP detection fails"
1770        );
1771
1772        let server = result.unwrap();
1773
1774        let tcp_info = registered_tcp_info(&server).await;
1775        let socket_addr = tcp_info.address.parse::<std::net::SocketAddr>().unwrap();
1776
1777        // With the failing resolver, fallback should ALWAYS be used
1778        let ip = socket_addr.ip();
1779        assert!(
1780            ip.is_loopback(),
1781            "Should use loopback when IP detection fails"
1782        );
1783
1784        // Verify it's specifically 127.0.0.1 (the fallback value from the patch)
1785        assert_eq!(
1786            ip,
1787            std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
1788            "Fallback should use exactly 127.0.0.1, got: {}",
1789            ip
1790        );
1791
1792        println!("SUCCESS: Fallback to 127.0.0.1 was confirmed: {}", ip);
1793
1794        // The server should work with the fallback IP
1795        assert!(socket_addr.port() > 0, "Server should have a valid port");
1796    }
1797
1798    #[tokio::test]
1799    async fn configured_ip_literals_bind_and_format_addresses() {
1800        let ipv6_available = std::net::TcpListener::bind("[::1]:0").is_ok();
1801
1802        for (host, expected_ip) in [
1803            ("127.0.0.1", "127.0.0.1".parse::<IpAddr>().unwrap()),
1804            ("::1", "::1".parse::<IpAddr>().unwrap()),
1805            (" 127.0.0.1 ", "127.0.0.1".parse::<IpAddr>().unwrap()),
1806            (" [::1]\t", "::1".parse::<IpAddr>().unwrap()),
1807            ("::ffff:127.0.0.1", "127.0.0.1".parse::<IpAddr>().unwrap()),
1808            (" lo ", "127.0.0.1".parse::<IpAddr>().unwrap()),
1809            (" lo:1 ", "127.0.0.1".parse::<IpAddr>().unwrap()),
1810        ] {
1811            if expected_ip.is_ipv6() && !ipv6_available {
1812                eprintln!("skipping IPv6 bind because this host does not support IPv6 loopback");
1813                continue;
1814            }
1815
1816            let mut resolver = StubResolver::not_found();
1817            resolver
1818                .interfaces
1819                .push(("lo:1", "127.0.0.1".parse().unwrap()));
1820            let server = TcpStreamServer::new_with_resolver(
1821                ServerOptions {
1822                    port: 0,
1823                    interface: Some(host.to_string()),
1824                },
1825                resolver,
1826            )
1827            .await
1828            .unwrap();
1829            let context = Context::new(());
1830            let pending = server
1831                .register(
1832                    StreamOptions::builder()
1833                        .context(context.context())
1834                        .enable_request_stream(false)
1835                        .enable_response_stream(true)
1836                        .build()
1837                        .unwrap(),
1838                )
1839                .await;
1840            let connection_info = pending.recv_stream.unwrap().connection_info;
1841            let tcp_info: TcpStreamConnectionInfo = connection_info.try_into().unwrap();
1842            let address = tcp_info.address.parse::<SocketAddr>().unwrap();
1843
1844            assert_eq!(address, server.local_address().unwrap());
1845            assert_eq!(address.ip(), expected_ip);
1846            assert_ne!(address.port(), 0);
1847            if expected_ip.is_ipv6() {
1848                assert!(tcp_info.address.starts_with('['));
1849            }
1850        }
1851    }
1852
1853    /// Create a test server using the failing IP resolver (falls back to loopback).
1854    async fn test_server() -> Arc<TcpStreamServer> {
1855        TcpStreamServer::new_with_resolver(
1856            ServerOptions::builder().port(0).build().unwrap(),
1857            StubResolver::not_found(),
1858        )
1859        .await
1860        .unwrap()
1861    }
1862
1863    /// Helper: register a response stream and extract its subject string.
1864    async fn register_and_get_subject(
1865        server: &TcpStreamServer,
1866    ) -> (
1867        String,
1868        tokio::sync::oneshot::Receiver<Result<super::StreamReceiver, StreamPrologueError>>,
1869    ) {
1870        let context = Context::new(());
1871        let options = StreamOptions::builder()
1872            .context(context.context())
1873            .enable_request_stream(false)
1874            .enable_response_stream(true)
1875            .build()
1876            .unwrap();
1877
1878        let pending = server.register(options).await;
1879        let recv_stream = pending.recv_stream.unwrap();
1880        let (conn_info, provider) = recv_stream.into_parts();
1881        let tcp_info: TcpStreamConnectionInfo = conn_info.try_into().unwrap();
1882        (tcp_info.subject, provider)
1883    }
1884
1885    /// Convenience constructor so tests don't repeat the struct literal.
1886    fn make_eid(
1887        namespace: &str,
1888        component: &str,
1889        endpoint: &str,
1890        instance_id: u64,
1891    ) -> EndpointInstanceId {
1892        EndpointInstanceId {
1893            namespace: namespace.to_string(),
1894            component: component.to_string(),
1895            endpoint: endpoint.to_string(),
1896            instance_id,
1897        }
1898    }
1899
1900    /// Helper: register a bidirectional pair (both request + response halves)
1901    /// and return both subjects + their providers.
1902    async fn register_and_get_bidi_subjects(
1903        server: &TcpStreamServer,
1904    ) -> (
1905        String,
1906        tokio::sync::oneshot::Receiver<Result<super::StreamSender, StreamPrologueError>>,
1907        String,
1908        tokio::sync::oneshot::Receiver<Result<super::StreamReceiver, StreamPrologueError>>,
1909    ) {
1910        let context = Context::new(());
1911        let options = StreamOptions::builder()
1912            .context(context.context())
1913            .enable_request_stream(true)
1914            .enable_response_stream(true)
1915            .build()
1916            .unwrap();
1917
1918        let pending = server.register(options).await;
1919        let send_stream = pending.send_stream.unwrap();
1920        let recv_stream = pending.recv_stream.unwrap();
1921        let (send_info, send_provider) = send_stream.into_parts();
1922        let (recv_info, recv_provider) = recv_stream.into_parts();
1923        let send_tcp_info: TcpStreamConnectionInfo = send_info.try_into().unwrap();
1924        let recv_tcp_info: TcpStreamConnectionInfo = recv_info.try_into().unwrap();
1925        (
1926            send_tcp_info.subject,
1927            send_provider,
1928            recv_tcp_info.subject,
1929            recv_provider,
1930        )
1931    }
1932
1933    /// `cancel_instance_streams` must drop the request-stream oneshot too,
1934    /// not just the response-stream one. Without the tagged tracker this test
1935    /// would hang on `send_provider.await` because the tx_subjects entry
1936    /// would leak past instance removal.
1937    #[tokio::test]
1938    async fn test_cancel_instance_streams_drops_both_bidi_halves() {
1939        let server = test_server().await;
1940        let (send_subj, send_provider, recv_subj, recv_provider) =
1941            register_and_get_bidi_subjects(&server).await;
1942
1943        let id = make_eid("ns", "comp", "generate", 7);
1944        assert!(
1945            server
1946                .associate_instance(&recv_subj, Some(&send_subj), &id)
1947                .await,
1948            "fresh instance must not be tombstoned"
1949        );
1950
1951        let cancelled = server.cancel_instance_streams(&id).await;
1952        assert_eq!(cancelled, 2, "both request + response halves must count");
1953
1954        assert!(
1955            recv_provider.await.is_err(),
1956            "recv provider should resolve with RecvError"
1957        );
1958        assert!(
1959            send_provider.await.is_err(),
1960            "send provider should resolve with RecvError after instance cancellation"
1961        );
1962    }
1963
1964    /// Pre-tombstoning an instance must drop both halves of a later
1965    /// `associate_instance(recv, Some(send), id)` call, not just the recv.
1966    #[tokio::test]
1967    async fn test_associate_instance_tombstone_cancels_both_bidi_halves() {
1968        let server = test_server().await;
1969        let id = make_eid("ns", "comp", "generate", 8);
1970        // Pre-tombstone the instance.
1971        server.cancel_instance_streams(&id).await;
1972
1973        let (send_subj, send_provider, recv_subj, recv_provider) =
1974            register_and_get_bidi_subjects(&server).await;
1975
1976        assert!(
1977            !server
1978                .associate_instance(&recv_subj, Some(&send_subj), &id)
1979                .await,
1980            "tombstoned instance must reject association"
1981        );
1982
1983        assert!(recv_provider.await.is_err());
1984        assert!(send_provider.await.is_err());
1985    }
1986
1987    #[tokio::test]
1988    async fn test_cancel_instance_streams_unblocks_receiver() {
1989        let server = test_server().await;
1990
1991        let (subject, provider) = register_and_get_subject(&server).await;
1992
1993        let id = make_eid("ns", "comp", "generate", 42);
1994        assert!(server.associate_instance(&subject, None, &id).await);
1995
1996        let cancelled = server.cancel_instance_streams(&id).await;
1997        assert_eq!(cancelled, 1);
1998
1999        // The oneshot receiver should now resolve with an error (sender dropped)
2000        let result = provider.await;
2001        assert!(result.is_err(), "Expected RecvError after cancellation");
2002    }
2003
2004    #[tokio::test]
2005    async fn test_cancel_instance_streams_multiple_subjects() {
2006        let server = test_server().await;
2007
2008        let (subj1, prov1) = register_and_get_subject(&server).await;
2009        let (subj2, prov2) = register_and_get_subject(&server).await;
2010        let (subj3, prov3) = register_and_get_subject(&server).await;
2011
2012        let id10 = make_eid("ns", "comp", "generate", 10);
2013        let id20 = make_eid("ns", "comp", "generate", 20);
2014
2015        // Associate first two with instance 10, third with instance 20
2016        assert!(server.associate_instance(&subj1, None, &id10).await);
2017        assert!(server.associate_instance(&subj2, None, &id10).await);
2018        assert!(server.associate_instance(&subj3, None, &id20).await);
2019
2020        // Cancel instance 10 -- should cancel 2 subjects
2021        let cancelled = server.cancel_instance_streams(&id10).await;
2022        assert_eq!(cancelled, 2);
2023
2024        assert!(prov1.await.is_err());
2025        assert!(prov2.await.is_err());
2026
2027        // Instance 20 should be unaffected -- cancel it separately
2028        let cancelled = server.cancel_instance_streams(&id20).await;
2029        assert_eq!(cancelled, 1);
2030        assert!(prov3.await.is_err());
2031    }
2032
2033    #[tokio::test]
2034    async fn test_cancel_instance_streams_nonexistent_instance() {
2035        let server = test_server().await;
2036
2037        let id = make_eid("ns", "comp", "generate", 999);
2038        let cancelled = server.cancel_instance_streams(&id).await;
2039        assert_eq!(cancelled, 0);
2040    }
2041
2042    #[tokio::test]
2043    async fn test_cancel_recv_stream_cleans_up_instance_tracking() {
2044        let server = test_server().await;
2045
2046        let (subject, _provider) = register_and_get_subject(&server).await;
2047        let id = make_eid("ns", "comp", "generate", 42);
2048        assert!(server.associate_instance(&subject, None, &id).await);
2049
2050        // Cancel the individual subject
2051        server.cancel_recv_stream(&subject).await;
2052
2053        // Instance should have no remaining subjects
2054        let cancelled = server.cancel_instance_streams(&id).await;
2055        assert_eq!(
2056            cancelled, 0,
2057            "Instance tracking should have been cleaned up"
2058        );
2059    }
2060
2061    #[tokio::test]
2062    async fn test_registered_stream_drop_runs_cleanup() {
2063        let server = test_server().await;
2064
2065        // Register a response stream but DON'T call into_parts -- just drop it
2066        let context = Context::new(());
2067        let options = StreamOptions::builder()
2068            .context(context.context())
2069            .enable_request_stream(false)
2070            .enable_response_stream(true)
2071            .build()
2072            .unwrap();
2073
2074        let pending = server.register(options).await;
2075        let recv_stream = pending.recv_stream.unwrap();
2076
2077        // Get the subject before dropping
2078        let tcp_info: TcpStreamConnectionInfo =
2079            recv_stream.connection_info.clone().try_into().unwrap();
2080        let subject = tcp_info.subject.clone();
2081
2082        // Verify it's in rx_subjects
2083        {
2084            let state = server.state.lock();
2085            assert!(state.rx_subjects.contains_key(&subject));
2086        }
2087
2088        // Drop the RegisteredStream -- RAII cleanup should fire
2089        drop(recv_stream);
2090
2091        // Give the spawned cleanup task a moment to run
2092        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
2093
2094        // Verify it's been removed from rx_subjects
2095        {
2096            let state = server.state.lock();
2097            assert!(
2098                !state.rx_subjects.contains_key(&subject),
2099                "RAII cleanup should have removed the rx_subjects entry"
2100            );
2101        }
2102    }
2103
2104    #[tokio::test]
2105    async fn test_registered_stream_into_parts_disarms_cleanup() {
2106        let server = test_server().await;
2107
2108        let context = Context::new(());
2109        let options = StreamOptions::builder()
2110            .context(context.context())
2111            .enable_request_stream(false)
2112            .enable_response_stream(true)
2113            .build()
2114            .unwrap();
2115
2116        let pending = server.register(options).await;
2117        let recv_stream = pending.recv_stream.unwrap();
2118
2119        let tcp_info: TcpStreamConnectionInfo =
2120            recv_stream.connection_info.clone().try_into().unwrap();
2121        let subject = tcp_info.subject.clone();
2122
2123        // Call into_parts to disarm the cleanup
2124        let (_conn_info, _provider) = recv_stream.into_parts();
2125
2126        // Give any potential cleanup a moment to run
2127        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
2128
2129        // The entry should still be in rx_subjects (cleanup was disarmed)
2130        {
2131            let state = server.state.lock();
2132            assert!(
2133                state.rx_subjects.contains_key(&subject),
2134                "into_parts() should disarm the RAII cleanup"
2135            );
2136        }
2137    }
2138
2139    #[tokio::test]
2140    async fn test_dropped_registered_stream_wait_cleans_instance_indexes() {
2141        let server = test_server().await;
2142        let context = Context::new(());
2143        let options = StreamOptions::builder()
2144            .context(context.context())
2145            .enable_request_stream(false)
2146            .enable_response_stream(true)
2147            .build()
2148            .unwrap();
2149
2150        let pending = server.register(options).await;
2151        let recv_stream = pending.recv_stream.unwrap();
2152        let tcp_info: TcpStreamConnectionInfo =
2153            recv_stream.connection_info.clone().try_into().unwrap();
2154        let subject = tcp_info.subject;
2155        let instance = make_eid("ns", "comp", "generate", 42);
2156        assert!(server.associate_instance(&subject, None, &instance).await);
2157
2158        let mut wait = Box::pin(recv_stream.wait());
2159        assert!(futures::poll!(wait.as_mut()).is_pending());
2160        drop(wait);
2161
2162        tokio::time::timeout(std::time::Duration::from_secs(1), async {
2163            loop {
2164                let cleaned = {
2165                    let state = server.state.lock();
2166                    !state.rx_subjects.contains_key(&subject)
2167                        && !state.rx_cancellations.contains_key(&subject)
2168                        && !state.subject_instance.contains_key(&subject)
2169                        && !state.instance_subjects.contains_key(&instance)
2170                };
2171                if cleaned {
2172                    break;
2173                }
2174                tokio::task::yield_now().await;
2175            }
2176        })
2177        .await
2178        .expect("cancelled wait did not clean response registration indexes");
2179    }
2180
2181    #[tokio::test]
2182    async fn test_associate_after_cancel_is_immediately_cancelled() {
2183        // Simulates the race: cancel_instance_streams fires before associate_instance.
2184        let server = test_server().await;
2185
2186        let id = make_eid("ns", "comp", "generate", 42);
2187
2188        // Cancel BEFORE any subject is registered (tombstone).
2189        let cancelled = server.cancel_instance_streams(&id).await;
2190        assert_eq!(cancelled, 0);
2191
2192        // Now register a subject and try to associate it with the tombstoned instance.
2193        let (subject, provider) = register_and_get_subject(&server).await;
2194        let associated = server.associate_instance(&subject, None, &id).await;
2195
2196        // associate_instance should return false when the instance is tombstoned.
2197        assert!(
2198            !associated,
2199            "associate_instance on a tombstoned instance should return false"
2200        );
2201
2202        // The provider should resolve with an error because associate_instance
2203        // found the tombstone and immediately cancelled the subject.
2204        let result = provider.await;
2205        assert!(
2206            result.is_err(),
2207            "Late associate_instance on a tombstoned instance should immediately cancel"
2208        );
2209    }
2210
2211    #[tokio::test]
2212    async fn test_clear_tombstone_allows_new_associations() {
2213        let server = test_server().await;
2214
2215        let id = make_eid("ns", "comp", "generate", 42);
2216
2217        server.cancel_instance_streams(&id).await;
2218        server.clear_instance_tombstone(&id).await;
2219
2220        // Now associate should work normally (subject NOT cancelled).
2221        let (subject, _provider) = register_and_get_subject(&server).await;
2222        assert!(server.associate_instance(&subject, None, &id).await);
2223
2224        // Subject should be tracked, not cancelled.
2225        let cancelled = server.cancel_instance_streams(&id).await;
2226        assert_eq!(
2227            cancelled, 1,
2228            "After clearing tombstone, subjects should be tracked normally"
2229        );
2230    }
2231
2232    #[tokio::test]
2233    async fn test_cancel_does_not_affect_sibling_endpoint() {
2234        // Regression: cancelling "generate" must not cancel "prefill" subjects
2235        // that share the same instance_id (same backend runtime).
2236        let server = test_server().await;
2237
2238        let (gen_subj, gen_prov) = register_and_get_subject(&server).await;
2239        let (pre_subj, pre_prov) = register_and_get_subject(&server).await;
2240
2241        let gen_id = make_eid("ns", "comp", "generate", 42);
2242        let pre_id = make_eid("ns", "comp", "prefill", 42);
2243
2244        assert!(server.associate_instance(&gen_subj, None, &gen_id).await);
2245        assert!(server.associate_instance(&pre_subj, None, &pre_id).await);
2246
2247        // Cancel only the "generate" endpoint's subjects.
2248        let cancelled = server.cancel_instance_streams(&gen_id).await;
2249        assert_eq!(
2250            cancelled, 1,
2251            "Only the generate subject should be cancelled"
2252        );
2253        assert!(gen_prov.await.is_err());
2254
2255        // prefill must still be tracked.
2256        let still_pending = server.cancel_instance_streams(&pre_id).await;
2257        assert_eq!(still_pending, 1, "prefill subject should still be tracked");
2258        assert!(pre_prov.await.is_err());
2259    }
2260
2261    #[tokio::test]
2262    async fn test_tombstone_is_endpoint_scoped() {
2263        // Tombstoning "generate" must not prevent new associations on "prefill"
2264        // for the same instance_id.
2265        let server = test_server().await;
2266
2267        let gen_id = make_eid("ns", "comp", "generate", 42);
2268        let pre_id = make_eid("ns", "comp", "prefill", 42);
2269
2270        server.cancel_instance_streams(&gen_id).await;
2271
2272        // A new subject for "generate" should be rejected.
2273        let (gen_subj, gen_prov) = register_and_get_subject(&server).await;
2274        assert!(
2275            !server.associate_instance(&gen_subj, None, &gen_id).await,
2276            "generate should be tombstoned"
2277        );
2278        assert!(gen_prov.await.is_err());
2279
2280        // A new subject for "prefill" with the same instance_id should be accepted.
2281        let (pre_subj, _pre_prov) = register_and_get_subject(&server).await;
2282        assert!(
2283            server.associate_instance(&pre_subj, None, &pre_id).await,
2284            "prefill tombstone is independent; subject should be tracked"
2285        );
2286        let count = server.cancel_instance_streams(&pre_id).await;
2287        assert_eq!(count, 1, "prefill subject should be tracked normally");
2288    }
2289
2290    #[tokio::test]
2291    async fn test_cancel_does_not_affect_different_component() {
2292        // Regression: two services with different (namespace, component) but the
2293        // same endpoint name and the same pod-backed instance_id must not interfere,
2294        // even though they share a single TcpStreamServer runtime.
2295        let server = test_server().await;
2296
2297        let (subj_a, prov_a) = register_and_get_subject(&server).await;
2298        let (subj_b, prov_b) = register_and_get_subject(&server).await;
2299
2300        // Same endpoint name + instance_id, different namespace/component.
2301        let id_a = make_eid("ns-a", "comp-a", "generate", 42);
2302        let id_b = make_eid("ns-b", "comp-b", "generate", 42);
2303
2304        assert!(server.associate_instance(&subj_a, None, &id_a).await);
2305        assert!(server.associate_instance(&subj_b, None, &id_b).await);
2306
2307        // Cancel service A -- only subj_a should be affected.
2308        let cancelled = server.cancel_instance_streams(&id_a).await;
2309        assert_eq!(cancelled, 1, "Only service-A subject should be cancelled");
2310        assert!(prov_a.await.is_err());
2311
2312        // Service B subject must still be pending.
2313        let still_tracked = server.cancel_instance_streams(&id_b).await;
2314        assert_eq!(still_tracked, 1, "Service-B subject should be unaffected");
2315        assert!(prov_b.await.is_err());
2316    }
2317
2318    #[tokio::test(start_paused = true)]
2319    async fn test_tombstone_expires_after_ttl() {
2320        // After TOMBSTONE_TTL elapses, a previously-tombstoned identity must
2321        // accept new associations again, AND the entry must be physically
2322        // pruned from `removed_instances` so the set remains bounded.
2323        let server = test_server().await;
2324
2325        let id = make_eid("ns", "comp", "generate", 42);
2326
2327        // Tombstone the identity.
2328        server.cancel_instance_streams(&id).await;
2329        {
2330            let state = server.state.lock();
2331            assert!(state.removed_instances.contains_key(&id));
2332        }
2333
2334        // Advance past the TTL.
2335        tokio::time::advance(TOMBSTONE_TTL + Duration::from_secs(1)).await;
2336
2337        // associate_instance for the same identity should now succeed (no
2338        // longer tombstoned). Any new subject must be tracked normally.
2339        let (subject, _provider) = register_and_get_subject(&server).await;
2340        assert!(
2341            server.associate_instance(&subject, None, &id).await,
2342            "tombstone older than TTL should not block association"
2343        );
2344
2345        // The expired tombstone must have been pruned (lazy pruning fires on
2346        // every associate_instance/cancel_instance_streams call).
2347        {
2348            let state = server.state.lock();
2349            assert!(
2350                !state.removed_instances.contains_key(&id),
2351                "expired tombstone should be pruned, not retained"
2352            );
2353        }
2354    }
2355
2356    #[tokio::test(start_paused = true)]
2357    async fn test_tombstone_within_ttl_blocks_associate() {
2358        // Regression net for the original tombstone fix: a tombstone younger
2359        // than TTL must still cancel late-arriving associate_instance() calls.
2360        let server = test_server().await;
2361
2362        let id = make_eid("ns", "comp", "generate", 42);
2363        server.cancel_instance_streams(&id).await;
2364
2365        // Advance only a small fraction of the TTL.
2366        tokio::time::advance(Duration::from_secs(1)).await;
2367
2368        let (subject, provider) = register_and_get_subject(&server).await;
2369        assert!(
2370            !server.associate_instance(&subject, None, &id).await,
2371            "tombstone within TTL must still block association"
2372        );
2373        assert!(provider.await.is_err());
2374    }
2375
2376    #[tokio::test(start_paused = true)]
2377    async fn test_tombstone_lazy_prune_on_cancel() {
2378        // Old tombstones must be pruned on the next cancel_instance_streams
2379        // call, regardless of which identity is being tombstoned.
2380        let server = test_server().await;
2381
2382        let id_old = make_eid("ns", "comp", "generate", 1);
2383        let id_new = make_eid("ns", "comp", "generate", 2);
2384
2385        server.cancel_instance_streams(&id_old).await;
2386        tokio::time::advance(TOMBSTONE_TTL + Duration::from_secs(1)).await;
2387        server.cancel_instance_streams(&id_new).await;
2388
2389        let state = server.state.lock();
2390        assert!(
2391            !state.removed_instances.contains_key(&id_old),
2392            "old tombstone should be pruned by the next cancel_instance_streams call"
2393        );
2394        assert!(
2395            state.removed_instances.contains_key(&id_new),
2396            "fresh tombstone should be retained"
2397        );
2398        assert_eq!(state.removed_instances.len(), 1);
2399    }
2400
2401    #[tokio::test]
2402    async fn test_clear_tombstone_only_affects_named_identity() {
2403        // Documents the monotonic-lease invariant: `clear_instance_tombstone`
2404        // for one EndpointInstanceId must not touch a sibling entry. With etcd
2405        // lease IDs this defensive code rarely fires (new lease = new
2406        // EndpointInstanceId), but the per-key scope must hold.
2407        let server = test_server().await;
2408
2409        let id_a = make_eid("ns", "comp", "generate", 1);
2410        let id_b = make_eid("ns", "comp", "generate", 2);
2411
2412        server.cancel_instance_streams(&id_a).await;
2413        server.clear_instance_tombstone(&id_b).await;
2414
2415        let state = server.state.lock();
2416        assert!(
2417            state.removed_instances.contains_key(&id_a),
2418            "clearing a different identity must not remove id_a's tombstone"
2419        );
2420    }
2421
2422    #[tokio::test]
2423    async fn test_tombstone_scoped_to_full_identity() {
2424        // A tombstone on (ns-a, comp-a, generate, 42) must not block
2425        // associations on (ns-b, comp-b, generate, 42).
2426        let server = test_server().await;
2427
2428        let id_a = make_eid("ns-a", "comp-a", "generate", 42);
2429        let id_b = make_eid("ns-b", "comp-b", "generate", 42);
2430
2431        // Tombstone only service A.
2432        server.cancel_instance_streams(&id_a).await;
2433
2434        // Service A is tombstoned — new association is rejected.
2435        let (subj_a, prov_a) = register_and_get_subject(&server).await;
2436        assert!(!server.associate_instance(&subj_a, None, &id_a).await);
2437        assert!(prov_a.await.is_err());
2438
2439        // Service B with same endpoint name + instance_id must be accepted.
2440        let (subj_b, _prov_b) = register_and_get_subject(&server).await;
2441        assert!(
2442            server.associate_instance(&subj_b, None, &id_b).await,
2443            "Different namespace/component must not be tombstoned"
2444        );
2445        assert_eq!(server.cancel_instance_streams(&id_b).await, 1);
2446    }
2447
2448    type TestFramedRead = FramedRead<ReadHalf<TcpStream>, TwoPartCodec>;
2449    type TestFramedWrite = FramedWrite<WriteHalf<TcpStream>, TwoPartCodec>;
2450    type TestResponseStream = (TestFramedRead, TestFramedWrite, StreamReceiver);
2451
2452    /// Stand up a TcpStreamServer, register a response stream, connect a
2453    /// client, drive the handshake + prologue, and return the client-side
2454    /// framed reader/writer along with the receiver.
2455    async fn open_registered_response_stream() -> TestResponseStream {
2456        let options = ServerOptions::builder().port(0).build().unwrap();
2457        let server = TcpStreamServer::new_with_resolver(options, StubResolver::not_found())
2458            .await
2459            .unwrap();
2460        let context = Context::new(());
2461        let stream_options = StreamOptions::builder()
2462            .context(context.context())
2463            .enable_request_stream(false)
2464            .enable_response_stream(true)
2465            .build()
2466            .unwrap();
2467        let pending_connection = server.register(stream_options).await;
2468        let registered_stream = pending_connection.recv_stream.unwrap();
2469        let (connection_info, stream_provider) = registered_stream.into_parts();
2470        let tcp_info: TcpStreamConnectionInfo = connection_info.try_into().unwrap();
2471
2472        let stream = TcpStream::connect(&tcp_info.address).await.unwrap();
2473        let (read_half, write_half) = tokio::io::split(stream);
2474        let framed_reader = FramedRead::new(read_half, TwoPartCodec::default());
2475        let mut framed_writer = FramedWrite::new(write_half, TwoPartCodec::default());
2476
2477        let handshake = CallHomeHandshake {
2478            subject: tcp_info.subject,
2479            stream_type: StreamType::Response,
2480        };
2481        framed_writer
2482            .send(TwoPartMessage::from_header(
2483                serde_json::to_vec(&handshake).unwrap().into(),
2484            ))
2485            .await
2486            .unwrap();
2487        framed_writer
2488            .send(TwoPartMessage::from_header(
2489                serde_json::to_vec(&ResponseStreamPrologue {
2490                    error: None,
2491                    typed_error: None,
2492                })
2493                .unwrap()
2494                .into(),
2495            ))
2496            .await
2497            .unwrap();
2498
2499        // SAFETY (test-only): healthy localhost handshake always resolves all
2500        // three layers; a panic here means the harness is broken.
2501        let receiver = tokio::time::timeout(std::time::Duration::from_secs(1), stream_provider)
2502            .await
2503            .expect("server should establish response stream within timeout")
2504            .expect("stream provider should not be dropped")
2505            .expect("response stream should be accepted");
2506
2507        (framed_reader, framed_writer, receiver)
2508    }
2509
2510    async fn recv_control_message(framed_reader: &mut TestFramedRead) -> ControlMessage {
2511        // SAFETY (test-only): a misbehaving server in any of these layers is
2512        // exactly the harness failure we want surfaced as a test panic.
2513        let message = tokio::time::timeout(std::time::Duration::from_secs(1), framed_reader.next())
2514            .await
2515            .expect("server should send a control message within timeout")
2516            .expect("server should not close before sending control")
2517            .expect("control message should decode");
2518        let (header, data) = message.optional_parts();
2519        assert!(data.is_none(), "control message should not contain data");
2520        serde_json::from_slice(header.expect("control header missing").as_ref()).unwrap()
2521    }
2522
2523    /// Sending an unexpected control message (Stop or Kill from the data
2524    /// direction) is a protocol violation. The server's
2525    /// network_receive_handler must reply with ControlMessage::Kill on
2526    /// that stream alone, not panic.
2527    #[tokio::test]
2528    async fn test_tcp_stream_server_sends_kill_on_unexpected_control_message() {
2529        let (mut framed_reader, mut framed_writer, _receiver) =
2530            open_registered_response_stream().await;
2531
2532        framed_writer
2533            .send(TwoPartMessage::from_header(
2534                serde_json::to_vec(&ControlMessage::Stop).unwrap().into(),
2535            ))
2536            .await
2537            .unwrap();
2538
2539        assert_eq!(
2540            recv_control_message(&mut framed_reader).await,
2541            ControlMessage::Kill,
2542            "unexpected control message should kill only this stream"
2543        );
2544    }
2545
2546    #[tokio::test]
2547    async fn test_tcp_stream_server_sends_kill_when_prologue_wait_is_cancelled() {
2548        let server = test_server().await;
2549        let context = Context::new(());
2550        let options = StreamOptions::builder()
2551            .context(context.context())
2552            .enable_request_stream(false)
2553            .enable_response_stream(true)
2554            .build()
2555            .unwrap();
2556        let pending = server.register(options).await;
2557        let registered_stream = pending.recv_stream.unwrap();
2558        let (connection_info, stream_provider) = registered_stream.into_parts();
2559        let tcp_info: TcpStreamConnectionInfo = connection_info.try_into().unwrap();
2560        let subject = tcp_info.subject.clone();
2561        let instance = make_eid("ns", "comp", "generate", 42);
2562        assert!(server.associate_instance(&subject, None, &instance).await);
2563
2564        let stream = TcpStream::connect(&tcp_info.address).await.unwrap();
2565        let (read_half, write_half) = tokio::io::split(stream);
2566        let mut framed_reader = FramedRead::new(read_half, TwoPartCodec::default());
2567        let mut framed_writer = FramedWrite::new(write_half, TwoPartCodec::default());
2568        let handshake = CallHomeHandshake {
2569            subject: subject.clone(),
2570            stream_type: StreamType::Response,
2571        };
2572        framed_writer
2573            .send(TwoPartMessage::from_header(
2574                serde_json::to_vec(&handshake).unwrap().into(),
2575            ))
2576            .await
2577            .unwrap();
2578
2579        tokio::time::timeout(std::time::Duration::from_secs(1), async {
2580            while server.state.lock().rx_subjects.contains_key(&subject) {
2581                tokio::task::yield_now().await;
2582            }
2583        })
2584        .await
2585        .expect("server did not accept response call-home");
2586
2587        assert_eq!(server.cancel_instance_streams(&instance).await, 1);
2588        assert!(
2589            time::timeout(Duration::from_secs(1), stream_provider)
2590                .await
2591                .expect("worker removal must unblock the response handshake")
2592                .is_err()
2593        );
2594        assert_eq!(
2595            recv_control_message(&mut framed_reader).await,
2596            ControlMessage::Kill,
2597            "cancelling the prologue wait should stop worker generation"
2598        );
2599        let state = server.state.lock();
2600        assert!(!state.rx_subjects.contains_key(&subject));
2601        assert!(!state.rx_cancellations.contains_key(&subject));
2602        assert!(!state.subject_instance.contains_key(&subject));
2603        assert!(!state.instance_subjects.contains_key(&instance));
2604    }
2605
2606    /// Cover both orderings at the boundary between a pending handshake and an
2607    /// established response. Reading a prologue alone must not defeat removal.
2608    #[tokio::test]
2609    async fn response_prologue_acceptance_is_ordered_with_worker_removal() {
2610        let server = test_server().await;
2611        for removal_first in [true, false] {
2612            let instance = make_eid("ns", "comp", "generate", u64::from(removal_first));
2613            let (subject, _provider) = register_and_get_subject(&server).await;
2614            assert!(server.associate_instance(&subject, None, &instance).await);
2615            let pending = TcpStreamServer::take_response_stream(&server.state, &subject).unwrap();
2616            let guard = ResponseRegistrationGuard {
2617                state: server.state.clone(),
2618                subject: subject.clone(),
2619                is_active: true,
2620            };
2621
2622            if removal_first {
2623                assert_eq!(server.cancel_instance_streams(&instance).await, 1);
2624                assert!(
2625                    !guard.accept(),
2626                    "a cancelled handshake must not be accepted"
2627                );
2628                assert!(pending.cancellation.is_cancelled());
2629            } else {
2630                assert!(guard.accept());
2631                assert_eq!(server.cancel_instance_streams(&instance).await, 0);
2632                assert!(!pending.cancellation.is_cancelled());
2633            }
2634
2635            let state = server.state.lock();
2636            assert!(!state.rx_cancellations.contains_key(&subject));
2637            assert!(!state.subject_instance.contains_key(&subject));
2638            assert!(!state.instance_subjects.contains_key(&instance));
2639        }
2640    }
2641
2642    /// A peer that never reads Kill must not delay the requester or retain the
2643    /// handshake registration. Its socket cleanup must also have a deadline.
2644    #[tokio::test(start_paused = true)]
2645    async fn cancelled_handshake_unblocks_before_stalled_cleanup() {
2646        let server = test_server().await;
2647        let instance = make_eid("ns", "comp", "generate", 42);
2648        let (subject, provider) = register_and_get_subject(&server).await;
2649        assert!(server.associate_instance(&subject, None, &instance).await);
2650        let pending = TcpStreamServer::take_response_stream(&server.state, &subject).unwrap();
2651        let guard = ResponseRegistrationGuard {
2652            state: server.state.clone(),
2653            subject: subject.clone(),
2654            is_active: true,
2655        };
2656        // One byte cannot hold a framed Kill; retaining the unread peer stalls send().
2657        let (socket, _peer) = tokio::io::duplex(1);
2658        let writer = FramedWrite::new(Box::new(socket) as BoxWrite, TwoPartCodec::default());
2659        let cleanup = tokio::spawn(cancel_response_handshake(
2660            pending.connection,
2661            Some(guard),
2662            writer,
2663        ));
2664
2665        assert!(
2666            time::timeout(Duration::from_millis(50), provider)
2667                .await
2668                .expect("requester must be released before network cleanup")
2669                .is_err()
2670        );
2671        assert!(
2672            !cleanup.is_finished(),
2673            "peer should still be blocking the Kill write"
2674        );
2675        {
2676            let state = server.state.lock();
2677            assert!(!state.rx_cancellations.contains_key(&subject));
2678            assert!(!state.subject_instance.contains_key(&subject));
2679            assert!(!state.instance_subjects.contains_key(&instance));
2680        }
2681        time::advance(HANDSHAKE_CANCEL_CLEANUP_TIMEOUT).await;
2682        time::timeout(Duration::from_millis(50), cleanup)
2683            .await
2684            .expect("network cleanup must stop at its deadline")
2685            .unwrap();
2686    }
2687
2688    /// A framing/decode error from the worker side is unrecoverable for
2689    /// this stream but must not panic the worker. Server should send Kill
2690    /// and tear down only this connection.
2691    #[tokio::test]
2692    async fn test_tcp_stream_server_sends_kill_on_read_error() {
2693        let (mut framed_reader, framed_writer, _receiver) = open_registered_response_stream().await;
2694
2695        let mut raw_writer = framed_writer.into_inner();
2696        raw_writer.write_all(&[0u8; 8]).await.unwrap();
2697        raw_writer.shutdown().await.unwrap();
2698
2699        assert_eq!(
2700            recv_control_message(&mut framed_reader).await,
2701            ControlMessage::Kill,
2702            "framing read error should kill only this stream"
2703        );
2704    }
2705
2706    /// Sentinel is supposed to be header-only. A misbehaving client that
2707    /// attaches a data payload must not panic the worker via assert!().
2708    #[tokio::test]
2709    async fn test_tcp_stream_server_sends_kill_on_sentinel_with_data() {
2710        let (mut framed_reader, mut framed_writer, _receiver) =
2711            open_registered_response_stream().await;
2712
2713        let header = serde_json::to_vec(&ControlMessage::Sentinel)
2714            .unwrap()
2715            .into();
2716        framed_writer
2717            .send(TwoPartMessage::from_parts(
2718                header,
2719                Bytes::from_static(b"unexpected payload"),
2720            ))
2721            .await
2722            .unwrap();
2723
2724        assert_eq!(
2725            recv_control_message(&mut framed_reader).await,
2726            ControlMessage::Kill,
2727            "Sentinel with data should kill only this stream"
2728        );
2729    }
2730
2731    /// The prologue must be a HeaderOnly frame. A non-HeaderOnly prologue
2732    /// (data-only or mixed) must surface as Err to the requester rather
2733    /// than panic the worker.
2734    #[tokio::test]
2735    async fn test_tcp_stream_server_returns_error_on_invalid_prologue() {
2736        let options = ServerOptions::builder().port(0).build().unwrap();
2737        let server = TcpStreamServer::new_with_resolver(options, StubResolver::not_found())
2738            .await
2739            .unwrap();
2740        let context = Context::new(());
2741        let stream_options = StreamOptions::builder()
2742            .context(context.context())
2743            .enable_request_stream(false)
2744            .enable_response_stream(true)
2745            .build()
2746            .unwrap();
2747        let pending_connection = server.register(stream_options).await;
2748        let registered_stream = pending_connection.recv_stream.unwrap();
2749        let (connection_info, stream_provider) = registered_stream.into_parts();
2750        let tcp_info: TcpStreamConnectionInfo = connection_info.try_into().unwrap();
2751
2752        let stream = TcpStream::connect(&tcp_info.address).await.unwrap();
2753        let (_read_half, write_half) = tokio::io::split(stream);
2754        let mut framed_writer = FramedWrite::new(write_half, TwoPartCodec::default());
2755
2756        let handshake = CallHomeHandshake {
2757            subject: tcp_info.subject,
2758            stream_type: StreamType::Response,
2759        };
2760        framed_writer
2761            .send(TwoPartMessage::from_header(
2762                serde_json::to_vec(&handshake).unwrap().into(),
2763            ))
2764            .await
2765            .unwrap();
2766
2767        // Send a data-only frame in the prologue slot.
2768        framed_writer
2769            .send(TwoPartMessage::from_data(Bytes::from_static(
2770                b"not a prologue",
2771            )))
2772            .await
2773            .unwrap();
2774
2775        let outcome = tokio::time::timeout(std::time::Duration::from_secs(1), stream_provider)
2776            .await
2777            .expect("stream provider should resolve quickly")
2778            .expect("stream provider channel should not be dropped");
2779        // StreamReceiver doesn't impl Debug, so we can't use `.expect_err`.
2780        match outcome {
2781            Err(err) => assert!(
2782                err.contains("malformed prologue"),
2783                "expected malformed-prologue error, got: {err}"
2784            ),
2785            Ok(_) => panic!("invalid prologue should produce an error, but got Ok"),
2786        }
2787    }
2788
2789    /// A prologue from a newer worker may carry an `ErrorType` this build does
2790    /// not know; its legacy error must still reach the requester.
2791    #[tokio::test]
2792    async fn test_unknown_typed_error_preserves_the_legacy_prologue_error() {
2793        let options = ServerOptions::builder().port(0).build().unwrap();
2794        let server = TcpStreamServer::new_with_resolver(options, StubResolver::not_found())
2795            .await
2796            .unwrap();
2797        let context = Context::new(());
2798        let stream_options = StreamOptions::builder()
2799            .context(context.context())
2800            .enable_request_stream(false)
2801            .enable_response_stream(true)
2802            .build()
2803            .unwrap();
2804        let pending_connection = server.register(stream_options).await;
2805        let (connection_info, stream_provider) =
2806            pending_connection.recv_stream.unwrap().into_parts();
2807        let tcp_info: TcpStreamConnectionInfo = connection_info.try_into().unwrap();
2808
2809        let stream = TcpStream::connect(&tcp_info.address).await.unwrap();
2810        let (_read_half, write_half) = tokio::io::split(stream);
2811        let mut framed_writer = FramedWrite::new(write_half, TwoPartCodec::default());
2812
2813        let handshake = CallHomeHandshake {
2814            subject: tcp_info.subject,
2815            stream_type: StreamType::Response,
2816        };
2817        framed_writer
2818            .send(TwoPartMessage::from_header(
2819                serde_json::to_vec(&handshake).unwrap().into(),
2820            ))
2821            .await
2822            .unwrap();
2823
2824        // Correctly framed HeaderOnly, but the typed error names a variant this
2825        // build does not know.
2826        framed_writer
2827            .send(TwoPartMessage::from_header(Bytes::from_static(
2828                br#"{"error":"Generate Error: boom","typed_error":{"error_type":"VariantFromTheFuture","message":"boom"}}"#,
2829            )))
2830            .await
2831            .unwrap();
2832
2833        let outcome = tokio::time::timeout(std::time::Duration::from_secs(5), stream_provider)
2834            .await
2835            .expect("stream provider should resolve quickly")
2836            .expect("the oneshot must be notified, not dropped");
2837
2838        // `StreamReceiver` is not `Debug`, so match instead of `expect_err`.
2839        match outcome {
2840            Err(err) => {
2841                assert_eq!(err.message, "Generate Error: boom");
2842                assert!(err.typed_error.is_none());
2843            }
2844            Ok(_) => panic!("an error prologue must not yield a usable stream"),
2845        }
2846    }
2847
2848    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
2849    async fn test_concurrent_response_registration_and_call_home() {
2850        const STREAMS: usize = 128;
2851
2852        let result = time::timeout(Duration::from_secs(20), async {
2853            let server = test_server().await;
2854            let mut pending_streams = Vec::with_capacity(STREAMS);
2855            let mut client_tasks = Vec::with_capacity(STREAMS);
2856
2857            for idx in 0..STREAMS {
2858                let context = Context::new(());
2859                let options = StreamOptions::builder()
2860                    .context(context.context())
2861                    .enable_request_stream(false)
2862                    .enable_response_stream(true)
2863                    .build()
2864                    .unwrap();
2865
2866                let pending = server.register(options).await;
2867                let registered_stream = pending.recv_stream.unwrap();
2868                let (connection_info, stream_provider) = registered_stream.into_parts();
2869                let client_context =
2870                    Context::with_id_and_metadata((), context.id().to_string(), Default::default());
2871                let payload = Bytes::from(format!("payload-{idx}"));
2872
2873                pending_streams.push((idx, payload.clone(), stream_provider));
2874                client_tasks.push(tokio::spawn(async move {
2875                    let mut sender = TcpClient::create_response_stream(
2876                        client_context.context(),
2877                        connection_info,
2878                        None,
2879                    )
2880                    .await
2881                    .unwrap();
2882                    sender.send_prologue(None).await.unwrap();
2883                    sender.send(payload).await.unwrap();
2884                }));
2885            }
2886
2887            for task in client_tasks {
2888                task.await.unwrap();
2889            }
2890
2891            for (idx, expected, stream_provider) in pending_streams {
2892                let mut stream = stream_provider.await.unwrap().unwrap();
2893                let actual = stream.rx.recv().await.unwrap();
2894                assert_eq!(actual, expected, "payload mismatch for stream {idx}");
2895            }
2896        })
2897        .await;
2898
2899        assert!(
2900            result.is_ok(),
2901            "concurrent response registration and call-home timed out"
2902        );
2903    }
2904
2905    #[tokio::test]
2906    async fn test_response_stream_cancellation_before_prologue() {
2907        temp_env::async_with_vars(
2908            [
2909                ("DYN_TCP_TLS_CERT_PATH", None::<&str>),
2910                ("DYN_TCP_TLS_KEY_PATH", None),
2911                ("DYN_TCP_TLS_CLIENT_CA_CERT_PATH", None),
2912                ("DYN_TCP_TLS_CA_CERT_PATH", None),
2913                ("DYN_TCP_TLS_INSECURE", None),
2914                ("DYN_TCP_TLS_CLIENT_CERT_PATH", None),
2915                ("DYN_TCP_TLS_CLIENT_KEY_PATH", None),
2916            ],
2917            async {
2918                let server = test_server().await;
2919                for action in [Some(ControlMessage::Stop), Some(ControlMessage::Kill), None] {
2920                    tokio::time::timeout(Duration::from_secs(5), async {
2921                        let context = Context::new(()).context();
2922                        let options = StreamOptions::builder()
2923                            .context(context.clone())
2924                            .enable_request_stream(false)
2925                            .enable_response_stream(true)
2926                            .build()
2927                            .unwrap();
2928                        let pending = server.register(options).await;
2929                        let (connection_info, stream_provider) =
2930                            pending.recv_stream.unwrap().into_parts();
2931                        let worker_context = Context::with_id_and_metadata(
2932                            (),
2933                            context.id().to_string(),
2934                            Default::default(),
2935                        )
2936                        .context();
2937                        let sender = TcpClient::create_response_stream(
2938                            worker_context.clone(),
2939                            connection_info,
2940                            None,
2941                        )
2942                        .await
2943                        .unwrap();
2944
2945                        let stream_provider = match action {
2946                            Some(ControlMessage::Stop) => {
2947                                context.stop_generating();
2948                                Some(stream_provider)
2949                            }
2950                            Some(ControlMessage::Kill) => {
2951                                context.kill();
2952                                Some(stream_provider)
2953                            }
2954                            None => {
2955                                drop(stream_provider);
2956                                assert!(!context.is_stopped());
2957                                None
2958                            }
2959                            Some(ControlMessage::Sentinel) => unreachable!(),
2960                        };
2961                        if let Some(stream_provider) = stream_provider {
2962                            let failure = match stream_provider.await.unwrap() {
2963                                Err(failure) => failure,
2964                                Ok(_) => {
2965                                    panic!("cancellation must not establish a response stream")
2966                                }
2967                            };
2968                            assert_eq!(
2969                                failure.typed_error.as_ref().map(|error| error.error_type()),
2970                                Some(ErrorType::Cancelled)
2971                            );
2972                        }
2973                        worker_context.stopped().await;
2974                        sender.tx.closed().await;
2975                        assert_eq!(
2976                            worker_context.is_killed(),
2977                            action != Some(ControlMessage::Stop)
2978                        );
2979                    })
2980                    .await
2981                    .unwrap_or_else(|_| {
2982                        panic!("cancellation before prologue timed out: {action:?}")
2983                    });
2984                }
2985            },
2986        )
2987        .await;
2988    }
2989
2990    /// A worker that refuses a request before producing any response bytes must
2991    /// keep its error type all the way to the requesting side.
2992    #[tokio::test]
2993    async fn test_typed_prologue_error_survives_to_requester() {
2994        let server = test_server().await;
2995        let context = Context::new(());
2996        let options = StreamOptions::builder()
2997            .context(context.context())
2998            .enable_request_stream(false)
2999            .enable_response_stream(true)
3000            .build()
3001            .unwrap();
3002
3003        let pending = server.register(options).await;
3004        let (connection_info, stream_provider) = pending.recv_stream.unwrap().into_parts();
3005        let client_context =
3006            Context::with_id_and_metadata((), context.id().to_string(), Default::default());
3007
3008        let worker_error = DynamoError::builder()
3009            .error_type(ErrorType::Backend(BackendError::InvalidArgument))
3010            .message("multimodal input is not supported by this backend")
3011            .build();
3012
3013        let mut sender =
3014            TcpClient::create_response_stream(client_context.context(), connection_info, None)
3015                .await
3016                .unwrap();
3017        sender
3018            .send_prologue_typed(Some(StreamPrologueError::new(
3019                "Generate Error: multimodal input is not supported by this backend",
3020                worker_error,
3021            )))
3022            .await
3023            .unwrap();
3024
3025        let outcome = tokio::time::timeout(std::time::Duration::from_secs(5), stream_provider)
3026            .await
3027            .expect("stream provider should resolve quickly")
3028            .expect("stream provider channel should not be dropped");
3029
3030        // `StreamReceiver` is not `Debug`, so match instead of `expect_err`.
3031        let prologue_error = match outcome {
3032            Err(err) => err,
3033            Ok(_) => panic!("an error prologue must not yield a usable stream"),
3034        };
3035        assert_eq!(
3036            prologue_error.typed_error.as_ref().map(|e| e.error_type()),
3037            Some(ErrorType::Backend(BackendError::InvalidArgument)),
3038            "the worker's error type must survive the prologue round trip"
3039        );
3040    }
3041
3042    // ==================== request_stream_send_handler integration tests ====================
3043    //
3044    // These exercise the closing-message contract of `request_stream_send_handler`
3045    // end-to-end: register a request stream, dial it as a raw client (so we can
3046    // inspect frames directly), then trigger each of the exit branches and
3047    // assert which ControlMessage arrives on the wire.
3048
3049    use futures::SinkExt;
3050
3051    /// Register a request stream and dial it with a raw client. Returns the
3052    /// framed reader on the raw client side, the StreamSender held by the
3053    /// upstream, and the upstream's engine context (so the test can drive
3054    /// kill / stop externally).
3055    async fn register_and_dial_request_stream(
3056        server: &TcpStreamServer,
3057    ) -> (
3058        FramedRead<tokio::io::ReadHalf<TcpStream>, TwoPartCodec>,
3059        super::StreamSender,
3060        Arc<dyn AsyncEngineContext>,
3061    ) {
3062        let upstream_ctx = Context::new(()).context();
3063        let options = StreamOptions::builder()
3064            .context(upstream_ctx.clone())
3065            .enable_request_stream(true)
3066            .enable_response_stream(false)
3067            .build()
3068            .unwrap();
3069
3070        let pending = server.register(options).await;
3071        let send_stream = pending.send_stream.unwrap();
3072        let (conn_info, send_provider) = send_stream.into_parts();
3073        let tcp_info: TcpStreamConnectionInfo = conn_info.try_into().unwrap();
3074
3075        let raw = TcpStream::connect(&tcp_info.address).await.unwrap();
3076        let (read_half, write_half) = tokio::io::split(raw);
3077        let framed_reader = FramedRead::new(read_half, TwoPartCodec::default());
3078        let mut framed_writer = FramedWrite::new(write_half, TwoPartCodec::default());
3079
3080        let handshake = super::CallHomeHandshake {
3081            subject: tcp_info.subject.clone(),
3082            stream_type: StreamType::Request,
3083        };
3084        let handshake_bytes = serde_json::to_vec(&handshake).unwrap();
3085        framed_writer
3086            .send(TwoPartMessage::from_header(handshake_bytes.into()))
3087            .await
3088            .unwrap();
3089        drop(framed_writer);
3090
3091        let sender = send_provider.await.unwrap().unwrap();
3092        (framed_reader, sender, upstream_ctx)
3093    }
3094
3095    /// Pull frames off the raw client reader until the first `ControlMessage`
3096    /// arrives, ignoring any DataOnly frames before it. Returns the variant.
3097    async fn next_control_message(
3098        reader: &mut FramedRead<tokio::io::ReadHalf<TcpStream>, TwoPartCodec>,
3099    ) -> ControlMessage {
3100        loop {
3101            let frame = reader
3102                .next()
3103                .await
3104                .expect("socket closed before control message arrived")
3105                .expect("decode error");
3106            if let Some(header) = frame.header() {
3107                return serde_json::from_slice::<ControlMessage>(header)
3108                    .expect("invalid control message bytes");
3109            }
3110            // DataOnly frame — skip and keep reading.
3111        }
3112    }
3113
3114    /// Dropping the upstream's StreamSender drains `request_rx` and the server
3115    /// emits [`ControlMessage::Sentinel`] as the closing frame.
3116    #[tokio::test]
3117    async fn test_request_stream_sends_sentinel_on_clean_drop() {
3118        let server = test_server().await;
3119        let (mut reader, sender, _ctx) = register_and_dial_request_stream(&server).await;
3120
3121        drop(sender);
3122
3123        let ctrl = next_control_message(&mut reader).await;
3124        assert!(
3125            matches!(ctrl, ControlMessage::Sentinel),
3126            "clean drain should emit Sentinel, got {ctrl:?}"
3127        );
3128    }
3129
3130    /// `context.kill()` makes the server emit [`ControlMessage::Kill`] before
3131    /// shutting down the write half.
3132    #[tokio::test]
3133    async fn test_request_stream_sends_kill_on_context_killed() {
3134        let server = test_server().await;
3135        let (mut reader, _sender, ctx) = register_and_dial_request_stream(&server).await;
3136
3137        ctx.kill();
3138
3139        let ctrl = next_control_message(&mut reader).await;
3140        assert!(
3141            matches!(ctrl, ControlMessage::Kill),
3142            "context.kill() should emit Kill, got {ctrl:?}"
3143        );
3144    }
3145
3146    /// `context.stop()` makes the server emit [`ControlMessage::Stop`] before
3147    /// shutting down the write half.
3148    #[tokio::test]
3149    async fn test_request_stream_sends_stop_on_context_stopped() {
3150        let server = test_server().await;
3151        let (mut reader, _sender, ctx) = register_and_dial_request_stream(&server).await;
3152
3153        ctx.stop();
3154
3155        let ctrl = next_control_message(&mut reader).await;
3156        assert!(
3157            matches!(ctrl, ControlMessage::Stop),
3158            "context.stop() should emit Stop, got {ctrl:?}"
3159        );
3160    }
3161
3162    // ---- accept-loop backoff under resource exhaustion (issue #11822) ----
3163
3164    #[cfg(unix)]
3165    fn emfile_error() -> std::io::Error {
3166        std::io::Error::from_raw_os_error(libc::EMFILE)
3167    }
3168
3169    #[cfg(unix)]
3170    #[test]
3171    fn accept_backoff_classifies_all_exhaustion_errnos() {
3172        for errno in [libc::EMFILE, libc::ENFILE, libc::ENOBUFS, libc::ENOMEM] {
3173            let err = std::io::Error::from_raw_os_error(errno);
3174            assert_eq!(
3175                AcceptBackoff::classify(&err),
3176                AcceptFailure::Exhaustion,
3177                "errno {errno} must classify as exhaustion"
3178            );
3179        }
3180        // Ordinary errors stay on the immediate-retry path.
3181        let ordinary = std::io::Error::from(std::io::ErrorKind::ConnectionAborted);
3182        assert_eq!(AcceptBackoff::classify(&ordinary), AcceptFailure::Ordinary,);
3183    }
3184
3185    #[cfg(unix)]
3186    #[test]
3187    fn accept_backoff_grows_resets_and_rate_limits() {
3188        let mut backoff = AcceptBackoff::default();
3189        let now = std::time::Instant::now();
3190
3191        // Delay doubles per consecutive failure and saturates at the ceiling.
3192        let delays: Vec<Duration> = (0..12)
3193            .map(|_| backoff.record_exhaustion(now).delay)
3194            .collect();
3195        assert!(delays[0] > Duration::ZERO);
3196        let sat = delays
3197            .iter()
3198            .position(|d| *d == ACCEPT_BACKOFF_MAX_DELAY)
3199            .expect("delay reaches the ceiling");
3200        for pair in delays[..=sat].windows(2) {
3201            assert!(
3202                pair[1] > pair[0],
3203                "delay must grow: {:?} -> {:?}",
3204                pair[0],
3205                pair[1]
3206            );
3207        }
3208        assert!(delays[sat..].iter().all(|d| *d == ACCEPT_BACKOFF_MAX_DELAY));
3209
3210        // A successful accept resets the schedule to the initial delay.
3211        backoff.record_success(|| now);
3212        assert_eq!(
3213            backoff.record_exhaustion(now).delay,
3214            ACCEPT_BACKOFF_INITIAL_DELAY,
3215        );
3216
3217        // The summary is rate-limited: one emission per interval, carrying the
3218        // count of failures suppressed since the previous one.
3219        let t0 = std::time::Instant::now();
3220        let mut backoff = AcceptBackoff::default();
3221        let emitted: Vec<Option<u64>> = (0..50)
3222            .map(|_| backoff.record_exhaustion(t0).log_suppressed)
3223            .collect();
3224        assert_eq!(emitted.iter().filter(|e| e.is_some()).count(), 1);
3225        assert_eq!(emitted[0], Some(0));
3226
3227        let t1 = t0 + ACCEPT_BACKOFF_LOG_INTERVAL + Duration::from_millis(1);
3228        assert_eq!(
3229            backoff.record_exhaustion(t1).log_suppressed,
3230            Some(49),
3231            "next emission reports every failure suppressed since the last one",
3232        );
3233
3234        // Recovery shares the rate-limit window with the failure summary, so a
3235        // flapping listener cannot emit a recovery line per accept.
3236        let mut backoff = AcceptBackoff::default();
3237        let t0 = std::time::Instant::now();
3238        assert_eq!(backoff.record_exhaustion(t0).log_suppressed, Some(0));
3239        backoff.record_exhaustion(t0);
3240        backoff.record_exhaustion(t0);
3241        assert_eq!(
3242            backoff.record_success(|| t0),
3243            None,
3244            "recovery inside the log window must not emit",
3245        );
3246        backoff.record_exhaustion(t0);
3247        assert_eq!(
3248            backoff.record_success(|| t0 + ACCEPT_BACKOFF_LOG_INTERVAL),
3249            Some(3),
3250            "after the window elapses the recovery emits with the rolled-forward count",
3251        );
3252    }
3253
3254    #[cfg(unix)]
3255    #[tokio::test]
3256    async fn accept_backoff_socket_recovers_after_injected_exhaustion() {
3257        use crate::metrics::transport_metrics::TCP_ACCEPT_BACKOFF_TOTAL;
3258
3259        let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
3260            .await
3261            .expect("bind ephemeral listener");
3262        let addr = listener.local_addr().expect("local_addr");
3263
3264        let mut backoff = AcceptBackoff::default();
3265        let counter_before = TCP_ACCEPT_BACKOFF_TOTAL.get();
3266
3267        let started = std::time::Instant::now();
3268        let mut expected_total = Duration::ZERO;
3269        for _ in 0..3 {
3270            expected_total += handle_accept_error(&emfile_error(), &mut backoff).await;
3271        }
3272        assert!(expected_total >= ACCEPT_BACKOFF_INITIAL_DELAY * 3);
3273        assert!(
3274            started.elapsed() >= expected_total,
3275            "the accept loop must sleep, not spin; elapsed {:?}",
3276            started.elapsed(),
3277        );
3278        assert!(TCP_ACCEPT_BACKOFF_TOTAL.get() >= counter_before + 3.0);
3279
3280        let client = tokio::spawn(async move { tokio::net::TcpStream::connect(addr).await });
3281        let (accepted, _peer) = tokio::time::timeout(Duration::from_secs(5), listener.accept())
3282            .await
3283            .expect("listener should still accept after backing off")
3284            .expect("accept should succeed");
3285        let _client = client.await.expect("client task").expect("client connect");
3286        drop(accepted);
3287
3288        backoff.record_success(std::time::Instant::now);
3289        assert_eq!(
3290            backoff.record_exhaustion(std::time::Instant::now()).delay,
3291            ACCEPT_BACKOFF_INITIAL_DELAY,
3292        );
3293    }
3294}