Skip to content

Commit 40ea4ea

Browse files
authored
manager: Fix connection limits tracking of rejected connections (#286)
This PR ensures that connection IDs are properly tracked to enforce connection limits. After ~1/2 days the substrate litep2p running node is starting to reject multiple pending socket connections. This is related to the fact that accepting of established connection is a two-part process: - step 1. Can we accept the connection based on number of connections? - step 2. The transport manager transitions the peer state - step 3. If the transition succeeds, the connection is tracked (inserted in a hashmap). If step 2 fails, we were previously leaking the connection ID. The connection ID is also counting towards the maximum number of connections the node can sustain. This PR effectively modifies the connection limits API, `on_connection_established` is split into two methods: - `can_accept_connection()` - `accept_established_connection()` This fixes a subtle memory leak bounded at 10000 Connection IDs. However, even more important, this fixes connection stability for long-running nodes. Discovered during the investigation of: - #282 cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile <[email protected]>
1 parent e7cb35e commit 40ea4ea

File tree

2 files changed

+33
-17
lines changed

2 files changed

+33
-17
lines changed

src/transport/manager/limits.rs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,10 @@ impl ConnectionLimits {
113113
}
114114

115115
/// Called when a new connection is established.
116-
pub fn on_connection_established(
116+
///
117+
/// Returns an error if the connection cannot be accepted due to connection limits.
118+
pub fn can_accept_connection(
117119
&mut self,
118-
connection_id: ConnectionId,
119120
is_listener: bool,
120121
) -> Result<(), ConnectionLimitsError> {
121122
// Check connection limits.
@@ -131,16 +132,27 @@ impl ConnectionLimits {
131132
}
132133
}
133134

134-
// Keep track of the connection.
135+
Ok(())
136+
}
137+
138+
/// Accept an established connection.
139+
///
140+
/// # Note
141+
///
142+
/// This method should be called after the `Self::can_accept_connection` method
143+
/// to ensure that the connection can be accepted.
144+
pub fn accept_established_connection(
145+
&mut self,
146+
connection_id: ConnectionId,
147+
is_listener: bool,
148+
) {
135149
if is_listener {
136150
if self.config.max_incoming_connections.is_some() {
137151
self.incoming_connections.insert(connection_id);
138152
}
139153
} else if self.config.max_outgoing_connections.is_some() {
140154
self.outgoing_connections.insert(connection_id);
141155
}
142-
143-
Ok(())
144156
}
145157

146158
/// Called when a connection is closed.
@@ -167,35 +179,39 @@ mod tests {
167179
let connection_id_out_1 = ConnectionId::random();
168180
let connection_id_out_2 = ConnectionId::random();
169181
let connection_id_in_3 = ConnectionId::random();
170-
let connection_id_out_3 = ConnectionId::random();
171182

172183
// Establish incoming connection.
173-
assert!(limits.on_connection_established(connection_id_in_1, true).is_ok());
184+
assert!(limits.can_accept_connection(true).is_ok());
185+
limits.accept_established_connection(connection_id_in_1, true);
174186
assert_eq!(limits.incoming_connections.len(), 1);
175187

176-
assert!(limits.on_connection_established(connection_id_in_2, true).is_ok());
188+
assert!(limits.can_accept_connection(true).is_ok());
189+
limits.accept_established_connection(connection_id_in_2, true);
177190
assert_eq!(limits.incoming_connections.len(), 2);
178191

179-
assert!(limits.on_connection_established(connection_id_in_3, true).is_ok());
192+
assert!(limits.can_accept_connection(true).is_ok());
193+
limits.accept_established_connection(connection_id_in_3, true);
180194
assert_eq!(limits.incoming_connections.len(), 3);
181195

182196
assert_eq!(
183-
limits.on_connection_established(ConnectionId::random(), true).unwrap_err(),
197+
limits.can_accept_connection(true).unwrap_err(),
184198
ConnectionLimitsError::MaxIncomingConnectionsExceeded
185199
);
186200
assert_eq!(limits.incoming_connections.len(), 3);
187201

188202
// Establish outgoing connection.
189-
assert!(limits.on_connection_established(connection_id_out_1, false).is_ok());
203+
assert!(limits.can_accept_connection(false).is_ok());
204+
limits.accept_established_connection(connection_id_out_1, false);
190205
assert_eq!(limits.incoming_connections.len(), 3);
191206
assert_eq!(limits.outgoing_connections.len(), 1);
192207

193-
assert!(limits.on_connection_established(connection_id_out_2, false).is_ok());
208+
assert!(limits.can_accept_connection(false).is_ok());
209+
limits.accept_established_connection(connection_id_out_2, false);
194210
assert_eq!(limits.incoming_connections.len(), 3);
195211
assert_eq!(limits.outgoing_connections.len(), 2);
196212

197213
assert_eq!(
198-
limits.on_connection_established(connection_id_out_3, false).unwrap_err(),
214+
limits.can_accept_connection(false).unwrap_err(),
199215
ConnectionLimitsError::MaxOutgoingConnectionsExceeded
200216
);
201217

src/transport/manager/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -774,10 +774,7 @@ impl TransportManager {
774774
};
775775

776776
// Reject the connection if exceeded limits.
777-
if let Err(error) = self
778-
.connection_limits
779-
.on_connection_established(endpoint.connection_id(), endpoint.is_listener())
780-
{
777+
if let Err(error) = self.connection_limits.can_accept_connection(endpoint.is_listener()) {
781778
tracing::debug!(
782779
target: LOG_TARGET,
783780
?peer,
@@ -806,6 +803,9 @@ impl TransportManager {
806803
);
807804

808805
if connection_accepted {
806+
self.connection_limits
807+
.accept_established_connection(endpoint.connection_id(), endpoint.is_listener());
808+
809809
// Cancel all pending dials if the connection was established.
810810
if let PeerState::Opening {
811811
connection_id,

0 commit comments

Comments
 (0)