Skip to content

Commit 6a32f36

Browse files
committed
Add test for circular references leading to NetworkGraph leaks
Due to two circular `Arc` references, after `stop`ping and `drop`ping the `Node` instance the bulk of ldk-node's memory (in the form of the `NetworkGraph`) would hang around. Here we add a test for this in our integration tests, checking if the `NetworkGraph` (as a proxy for other objects held in reference by the `PeerManager`) hangs around after `Node`s are `drop`ped.
1 parent 809a227 commit 6a32f36

File tree

5 files changed

+39
-3
lines changed

5 files changed

+39
-3
lines changed

.github/workflows/rust.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ jobs:
8080
- name: Test on Rust ${{ matrix.toolchain }}
8181
if: "matrix.platform != 'windows-latest'"
8282
run: |
83-
RUSTFLAGS="--cfg no_download" cargo test
83+
RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test
8484
- name: Test with UniFFI support on Rust ${{ matrix.toolchain }}
8585
if: "matrix.platform != 'windows-latest' && matrix.build-uniffi"
8686
run: |
87-
RUSTFLAGS="--cfg no_download" cargo test --features uniffi
87+
RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test --features uniffi
8888
8989
doc:
9090
name: Documentation

.github/workflows/vss-integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ jobs:
4545
cd ldk-node
4646
export TEST_VSS_BASE_URL="http://localhost:8080/vss"
4747
RUSTFLAGS="--cfg vss_test" cargo test io::vss_store
48-
RUSTFLAGS="--cfg vss_test" cargo test --test integration_tests_vss
48+
RUSTFLAGS="--cfg vss_test --cfg cycle_tests" cargo test --test integration_tests_vss

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ check-cfg = [
124124
"cfg(tokio_unstable)",
125125
"cfg(cln_test)",
126126
"cfg(lnd_test)",
127+
"cfg(cycle_tests)",
127128
]
128129

129130
[[bench]]

src/builder.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,18 @@ fn build_with_store_internal(
16841684

16851685
let pathfinding_scores_sync_url = pathfinding_scores_sync_config.map(|c| c.url.clone());
16861686

1687+
#[cfg(cycle_tests)]
1688+
let mut _leak_checker = crate::LeakChecker(Vec::new());
1689+
#[cfg(cycle_tests)]
1690+
{
1691+
use std::any::Any;
1692+
use std::sync::Weak;
1693+
1694+
_leak_checker.0.push(Arc::downgrade(&channel_manager) as Weak<dyn Any + Send + Sync>);
1695+
_leak_checker.0.push(Arc::downgrade(&network_graph) as Weak<dyn Any + Send + Sync>);
1696+
_leak_checker.0.push(Arc::downgrade(&wallet) as Weak<dyn Any + Send + Sync>);
1697+
}
1698+
16871699
Ok(Node {
16881700
runtime,
16891701
stop_sender,
@@ -1716,6 +1728,8 @@ fn build_with_store_internal(
17161728
om_mailbox,
17171729
async_payments_role,
17181730
hrn_resolver,
1731+
#[cfg(cycle_tests)]
1732+
_leak_checker,
17191733
})
17201734
}
17211735

src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ use std::default::Default;
110110
use std::net::ToSocketAddrs;
111111
use std::sync::{Arc, Mutex, RwLock};
112112
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
113+
#[cfg(cycle_tests)]
114+
use std::{any::Any, sync::Weak};
113115

114116
pub use balance::{BalanceDetails, LightningBalance, PendingSweepBalance};
115117
use bitcoin::secp256k1::PublicKey;
@@ -173,6 +175,23 @@ use crate::scoring::setup_background_pathfinding_scores_sync;
173175
#[cfg(feature = "uniffi")]
174176
uniffi::include_scaffolding!("ldk_node");
175177

178+
#[cfg(cycle_tests)]
179+
/// A list of [`Weak`]s which can be used to check that a [`Node`]'s inner fields are being
180+
/// properly released after the [`Node`] is dropped.
181+
pub struct LeakChecker(Vec<Weak<dyn Any + Send + Sync>>);
182+
183+
#[cfg(cycle_tests)]
184+
impl LeakChecker {
185+
/// Asserts that all the stored [`Weak`]s point to contents which have been freed.
186+
///
187+
/// This will (obviously) panic if the [`Node`] has not yet been dropped.
188+
pub fn assert_no_leaks(&self) {
189+
for weak in self.0.iter() {
190+
assert_eq!(weak.strong_count(), 0);
191+
}
192+
}
193+
}
194+
176195
/// The main interface object of LDK Node, wrapping the necessary LDK and BDK functionalities.
177196
///
178197
/// Needs to be initialized and instantiated through [`Builder::build`].
@@ -208,6 +227,8 @@ pub struct Node {
208227
om_mailbox: Option<Arc<OnionMessageMailbox>>,
209228
async_payments_role: Option<AsyncPaymentsRole>,
210229
hrn_resolver: Arc<HRNResolver>,
230+
#[cfg(cycle_tests)]
231+
_leak_checker: LeakChecker,
211232
}
212233

213234
impl Node {

0 commit comments

Comments
 (0)