Skip to main content

snarkos_node_tcp/helpers/
connections.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Objects associated with connection handling.
17
18use std::{collections::HashMap, net::SocketAddr, ops::Not, sync::atomic::AtomicBool};
19
20#[cfg(feature = "locktick")]
21use locktick::parking_lot::RwLock;
22#[cfg(not(feature = "locktick"))]
23use parking_lot::RwLock;
24use tokio::{
25    io::{AsyncRead, AsyncWrite},
26    net::TcpStream,
27    sync::oneshot,
28    task::JoinHandle,
29};
30use tracing::*;
31
32#[cfg(doc)]
33use crate::{
34    Tcp,
35    protocols::{Disconnect, Handshake, OnConnect, Reading, Writing},
36};
37
38/// A map of all currently connected addresses to their associated connection.
39#[derive(Default)]
40pub(crate) struct Connections(pub(crate) RwLock<HashMap<SocketAddr, Connection>>);
41
42impl Connections {
43    /// Adds the given connection to the list of active connections.
44    pub(crate) fn add(&self, conn: Connection) {
45        self.0.write().insert(conn.addr, conn);
46    }
47
48    /// Returns `true` if the given address is connected.
49    pub(crate) fn is_connected(&self, addr: SocketAddr) -> bool {
50        self.0.read().contains_key(&addr)
51    }
52
53    /// Removes the connection associated with the given address.
54    pub(crate) fn remove(&self, addr: SocketAddr) -> Option<Connection> {
55        self.0.write().remove(&addr)
56    }
57
58    /// Returns the number of connected addresses.
59    pub(crate) fn num_connected(&self) -> usize {
60        self.0.read().len()
61    }
62
63    /// Returns the list of connected addresses.
64    pub(crate) fn addrs(&self) -> Vec<SocketAddr> {
65        self.0.read().keys().copied().collect()
66    }
67}
68
69/// A helper trait to facilitate trait-objectification of connection readers.
70pub(crate) trait AR: AsyncRead + Unpin + Send + Sync {}
71impl<T: AsyncRead + Unpin + Send + Sync> AR for T {}
72
73/// A helper trait to facilitate trait-objectification of connection writers.
74pub(crate) trait AW: AsyncWrite + Unpin + Send + Sync {}
75impl<T: AsyncWrite + Unpin + Send + Sync> AW for T {}
76
77/// Created for each active connection; used by the protocols to obtain a handle for
78/// reading and writing, and keeps track of tasks that have been spawned for the connection.
79pub struct Connection {
80    /// The address of the connection.
81    addr: SocketAddr,
82    /// The connection's side in relation to Tcp.
83    side: ConnectionSide,
84    /// Available and used only in the [`Handshake`] protocol.
85    pub(crate) stream: Option<TcpStream>,
86    /// Available and used only in the [`Reading`] protocol.
87    pub(crate) reader: Option<Box<dyn AR>>,
88    /// Available and used only in the [`Writing`] protocol.
89    pub(crate) writer: Option<Box<dyn AW>>,
90    /// Used to notify the [`Reading`] protocol that the connection is fully ready.
91    pub(crate) readiness_notifier: Option<oneshot::Sender<()>>,
92    /// Prevents the OnDisconnect hook from being triggered multiple times.
93    pub(crate) disconnecting: AtomicBool,
94    /// Handles to tasks spawned for the connection.
95    pub(crate) tasks: Vec<JoinHandle<()>>,
96    /// The tracing span.
97    pub(crate) span: Span,
98}
99
100impl Connection {
101    /// Creates a [`Connection`] with placeholders for protocol-related objects.
102    pub(crate) fn new(addr: SocketAddr, stream: TcpStream, side: ConnectionSide, span: Span) -> Self {
103        Self {
104            addr,
105            stream: Some(stream),
106            reader: None,
107            writer: None,
108            readiness_notifier: None,
109            disconnecting: Default::default(),
110            side,
111            tasks: Default::default(),
112            span,
113        }
114    }
115
116    /// Returns the address associated with the connection.
117    pub fn addr(&self) -> SocketAddr {
118        self.addr
119    }
120
121    /// Returns `ConnectionSide::Initiator` if the associated peer initiated the connection
122    /// and `ConnectionSide::Responder` if the connection request was initiated by Tcp.
123    pub fn side(&self) -> ConnectionSide {
124        self.side
125    }
126
127    /// Returns the tracing [`Span`] associated with the connection.
128    #[inline]
129    pub const fn span(&self) -> &Span {
130        &self.span
131    }
132}
133
134/// Indicates who was the initiator and who was the responder when the connection was established.
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136pub enum ConnectionSide {
137    /// The side that initiated the connection.
138    Initiator,
139    /// The side that accepted the connection.
140    Responder,
141}
142
143impl Not for ConnectionSide {
144    type Output = Self;
145
146    fn not(self) -> Self::Output {
147        match self {
148            Self::Initiator => Self::Responder,
149            Self::Responder => Self::Initiator,
150        }
151    }
152}
153
154impl Drop for Connection {
155    fn drop(&mut self) {
156        for task in self.tasks.iter().rev() {
157            task.abort();
158        }
159    }
160}
161
162pub(crate) fn create_connection_span(addr: SocketAddr, parent: &Span) -> Span {
163    macro_rules! try_span {
164        ($lvl:expr) => {
165            let s = span!(parent: parent, $lvl, "conn", addr = %addr);
166            if !s.is_disabled() {
167                return s;
168            }
169        };
170    }
171    try_span!(Level::TRACE);
172    try_span!(Level::DEBUG);
173    try_span!(Level::INFO);
174    try_span!(Level::WARN);
175    error_span!(parent: parent, "conn", addr = %addr)
176}
177
178/// Describes what triggered a disconnect, as delivered to [`Disconnect::handle_disconnect`].
179///
180/// note: Handshake failures do not appear here. A failed handshake prevents the connection
181/// from ever being registered, so there is no connection to disconnect.
182///
183/// note: When several events would race to trigger a disconnect on the same connection,
184/// only the first to claim it is delivered to [`Disconnect::handle_disconnect`]; subsequent
185/// claims are silently dropped.
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
187pub enum DisconnectOrigin {
188    /// The [`OnConnect`] task terminated abnormally before defusing its connection cleanup.
189    /// In practice this almost always means the user's [`OnConnect::on_connect`] implementation
190    /// panicked, and the disconnect is a side effect of that panic unwinding past the cleanup
191    /// guard.
192    OnConnectAbort,
193    /// The reader task for this connection terminated. Typical causes are the peer closing
194    /// its end of the socket, a decode error from the user-supplied [`Reading::Codec`], or
195    /// no message arriving within [`Reading::IDLE_TIMEOUT_MS`]. Often (but not always)
196    /// indicates a peer-side issue.
197    Reading,
198    /// The disconnect was initiated by [`Tcp::shut_down`], which tears down every active
199    /// connection as part of stopping the node. Unlike [`DisconnectOrigin::User`], this
200    /// signals that the entire node is going away - reconnection is not meaningful.
201    Shutdown,
202    /// The disconnect was explicitly requested via [`Tcp::disconnect`]. This is the only
203    /// origin produced directly by user code; the others all reflect events the library
204    /// detected internally.
205    User,
206    /// The writer task for this connection terminated. Typical causes are a [`Writing::TIMEOUT_MS`]
207    /// timeout while flushing, an underlying socket write error, or the message channel being
208    /// closed. Often correlates with the peer disappearing, but can also reflect local-side
209    /// pipeline problems (slow consumer, broken pipe).
210    Writing,
211}