Skip to content

Commit 150b470

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 71d465d commit 150b470

File tree

9 files changed

+90
-22
lines changed

9 files changed

+90
-22
lines changed

.github/workflows/cln-integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ jobs:
2727
socat -d -d UNIX-LISTEN:/tmp/lightning-rpc,reuseaddr,fork TCP:127.0.0.1:9937&
2828
2929
- name: Run CLN integration tests
30-
run: RUSTFLAGS="--cfg cln_test" cargo test --test integration_tests_cln
30+
run: RUSTFLAGS="--cfg cln_test --cfg cycle_tests" cargo test --test integration_tests_cln

.github/workflows/lnd-integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ jobs:
5151

5252
- name: Run LND integration tests
5353
run: LND_CERT_PATH=$LND_DATA_DIR/tls.cert LND_MACAROON_PATH=$LND_DATA_DIR/data/chain/bitcoin/regtest/admin.macaroon
54-
RUSTFLAGS="--cfg lnd_test" cargo test --test integration_tests_lnd -- --exact --show-output
54+
RUSTFLAGS="--cfg lnd_test --cfg cycle_tests" cargo test --test integration_tests_lnd -- --exact --show-output
5555
env:
5656
LND_DATA_DIR: ${{ env.LND_DATA_DIR }}

.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/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1761,6 +1761,12 @@ impl Node {
17611761
Error::PersistenceFailed
17621762
})
17631763
}
1764+
1765+
#[cfg(cycle_tests)]
1766+
/// Fetch a reference to the inner NetworkGraph, for Arc cycle detection
1767+
pub fn fetch_ref(&self) -> std::sync::Weak<Graph> {
1768+
Arc::downgrade(&self.network_graph)
1769+
}
17641770
}
17651771

17661772
impl Drop for Node {

src/logger.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,14 @@ impl LogWriter for Writer {
171171
}
172172
}
173173

174+
#[cfg(cycle_tests)]
175+
/// Our log writer
176+
pub struct Logger {
177+
/// Specifies the logger's writer.
178+
writer: Writer,
179+
}
180+
181+
#[cfg(not(cycle_tests))]
174182
pub(crate) struct Logger {
175183
/// Specifies the logger's writer.
176184
writer: Writer,
@@ -195,10 +203,12 @@ impl Logger {
195203
Ok(Self { writer: Writer::FileWriter { file_path, max_log_level } })
196204
}
197205

206+
/// Creates a new logger writing to the `log` crate
198207
pub fn new_log_facade() -> Self {
199208
Self { writer: Writer::LogFacadeWriter }
200209
}
201210

211+
/// Creates a new logger writing to an underlying [`LogWriter`]
202212
pub fn new_custom_writer(log_writer: Arc<dyn LogWriter>) -> Self {
203213
Self { writer: Writer::CustomWriter(log_writer) }
204214
}

tests/common/mod.rs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub(crate) mod logging;
1313
use std::collections::{HashMap, HashSet};
1414
use std::env;
1515
use std::future::Future;
16+
use std::ops::Deref;
1617
use std::path::PathBuf;
1718
use std::sync::{Arc, RwLock};
1819
use std::time::Duration;
@@ -261,10 +262,51 @@ pub(crate) fn random_config(anchor_channels: bool) -> TestConfig {
261262
TestConfig { node_config, ..Default::default() }
262263
}
263264

265+
pub struct TestNode {
266+
#[cfg(feature = "uniffi")]
267+
inner: Option<Arc<Node>>,
268+
#[cfg(not(feature = "uniffi"))]
269+
inner: Option<Node>,
270+
}
271+
264272
#[cfg(feature = "uniffi")]
265-
type TestNode = Arc<Node>;
273+
impl From<Arc<Node>> for TestNode {
274+
fn from(inner: Arc<Node>) -> Self {
275+
Self { inner: Some(inner) }
276+
}
277+
}
278+
266279
#[cfg(not(feature = "uniffi"))]
267-
type TestNode = Node;
280+
impl From<Node> for TestNode {
281+
fn from(inner: Node) -> Self {
282+
Self { inner: Some(inner) }
283+
}
284+
}
285+
286+
impl Deref for TestNode {
287+
type Target = Node;
288+
fn deref(&self) -> &Node {
289+
#[cfg(feature = "uniffi")]
290+
{
291+
self.inner.as_ref().unwrap().deref()
292+
}
293+
#[cfg(not(feature = "uniffi"))]
294+
{
295+
&self.inner.as_ref().unwrap()
296+
}
297+
}
298+
}
299+
300+
#[cfg(cycle_tests)]
301+
impl Drop for TestNode {
302+
fn drop(&mut self) {
303+
if !std::thread::panicking() {
304+
let graph_ref = (**self).fetch_ref();
305+
self.inner.take();
306+
assert_eq!(graph_ref.strong_count(), 0);
307+
}
308+
}
309+
}
268310

269311
#[derive(Clone)]
270312
pub(crate) enum TestChainSource<'a> {
@@ -430,7 +472,7 @@ pub(crate) fn setup_node_for_async_payments(
430472
node.start().unwrap();
431473
assert!(node.status().is_running);
432474
assert!(node.status().latest_fee_rate_cache_update_timestamp.is_some());
433-
node
475+
node.into()
434476
}
435477

436478
pub(crate) async fn generate_blocks_and_wait<E: ElectrumApi>(

tests/integration_tests_rust.rs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use common::{
2323
expect_splice_pending_event, generate_blocks_and_wait, open_channel, open_channel_push_amt,
2424
premine_and_distribute_funds, premine_blocks, prepare_rbf, random_config,
2525
random_listening_addresses, setup_bitcoind_and_electrsd, setup_builder, setup_node,
26-
setup_node_for_async_payments, setup_two_nodes, wait_for_tx, TestChainSource, TestStoreType,
27-
TestSyncStore,
26+
setup_node_for_async_payments, setup_two_nodes, wait_for_tx, TestChainSource, TestNode,
27+
TestStoreType, TestSyncStore,
2828
};
2929
use ldk_node::config::{AsyncPaymentsRole, EsploraSyncConfig};
3030
use ldk_node::liquidity::LSPS2ServiceConfig;
@@ -154,15 +154,15 @@ async fn multi_hop_sending() {
154154
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
155155

156156
// Setup and fund 5 nodes
157-
let mut nodes = Vec::new();
157+
let mut nodes: Vec<TestNode> = Vec::new();
158158
for _ in 0..5 {
159159
let config = random_config(true);
160160
let sync_config = EsploraSyncConfig { background_sync_config: None };
161161
setup_builder!(builder, config.node_config);
162162
builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
163-
let node = builder.build(config.node_entropy.into()).unwrap();
163+
let node: TestNode = builder.build(config.node_entropy.into()).unwrap().into();
164164
node.start().unwrap();
165-
nodes.push(node);
165+
nodes.push(node.into());
166166
}
167167

168168
let addresses = nodes.iter().map(|n| n.onchain_payment().new_address().unwrap()).collect();
@@ -1730,7 +1730,8 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) {
17301730
setup_builder!(service_builder, service_config.node_config);
17311731
service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
17321732
service_builder.set_liquidity_provider_lsps2(lsps2_service_config);
1733-
let service_node = service_builder.build(service_config.node_entropy.into()).unwrap();
1733+
let service_node: TestNode =
1734+
service_builder.build(service_config.node_entropy.into()).unwrap().into();
17341735
service_node.start().unwrap();
17351736

17361737
let service_node_id = service_node.node_id();
@@ -1740,13 +1741,15 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) {
17401741
setup_builder!(client_builder, client_config.node_config);
17411742
client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
17421743
client_builder.set_liquidity_source_lsps2(service_node_id, service_addr, None);
1743-
let client_node = client_builder.build(client_config.node_entropy.into()).unwrap();
1744+
let client_node: TestNode =
1745+
client_builder.build(client_config.node_entropy.into()).unwrap().into();
17441746
client_node.start().unwrap();
17451747

17461748
let payer_config = random_config(true);
17471749
setup_builder!(payer_builder, payer_config.node_config);
17481750
payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
1749-
let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap();
1751+
let payer_node: TestNode =
1752+
payer_builder.build(payer_config.node_entropy.into()).unwrap().into();
17501753
payer_node.start().unwrap();
17511754

17521755
let service_addr = service_node.onchain_payment().new_address().unwrap();
@@ -2047,7 +2050,8 @@ async fn lsps2_client_trusts_lsp() {
20472050
setup_builder!(service_builder, service_config.node_config);
20482051
service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
20492052
service_builder.set_liquidity_provider_lsps2(lsps2_service_config);
2050-
let service_node = service_builder.build(service_config.node_entropy.into()).unwrap();
2053+
let service_node: TestNode =
2054+
service_builder.build(service_config.node_entropy.into()).unwrap().into();
20512055
service_node.start().unwrap();
20522056
let service_node_id = service_node.node_id();
20532057
let service_addr = service_node.listening_addresses().unwrap().first().unwrap().clone();
@@ -2056,14 +2060,16 @@ async fn lsps2_client_trusts_lsp() {
20562060
setup_builder!(client_builder, client_config.node_config);
20572061
client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
20582062
client_builder.set_liquidity_source_lsps2(service_node_id, service_addr.clone(), None);
2059-
let client_node = client_builder.build(client_config.node_entropy.into()).unwrap();
2063+
let client_node: TestNode =
2064+
client_builder.build(client_config.node_entropy.into()).unwrap().into();
20602065
client_node.start().unwrap();
20612066
let client_node_id = client_node.node_id();
20622067

20632068
let payer_config = random_config(true);
20642069
setup_builder!(payer_builder, payer_config.node_config);
20652070
payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
2066-
let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap();
2071+
let payer_node: TestNode =
2072+
payer_builder.build(payer_config.node_entropy.into()).unwrap().into();
20672073
payer_node.start().unwrap();
20682074

20692075
let service_addr_onchain = service_node.onchain_payment().new_address().unwrap();
@@ -2220,7 +2226,8 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() {
22202226
setup_builder!(service_builder, service_config.node_config);
22212227
service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
22222228
service_builder.set_liquidity_provider_lsps2(lsps2_service_config);
2223-
let service_node = service_builder.build(service_config.node_entropy.into()).unwrap();
2229+
let service_node: TestNode =
2230+
service_builder.build(service_config.node_entropy.into()).unwrap().into();
22242231
service_node.start().unwrap();
22252232

22262233
let service_node_id = service_node.node_id();
@@ -2230,15 +2237,17 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() {
22302237
setup_builder!(client_builder, client_config.node_config);
22312238
client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
22322239
client_builder.set_liquidity_source_lsps2(service_node_id, service_addr.clone(), None);
2233-
let client_node = client_builder.build(client_config.node_entropy.into()).unwrap();
2240+
let client_node: TestNode =
2241+
client_builder.build(client_config.node_entropy.into()).unwrap().into();
22342242
client_node.start().unwrap();
22352243

22362244
let client_node_id = client_node.node_id();
22372245

22382246
let payer_config = random_config(true);
22392247
setup_builder!(payer_builder, payer_config.node_config);
22402248
payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
2241-
let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap();
2249+
let payer_node: TestNode =
2250+
payer_builder.build(payer_config.node_entropy.into()).unwrap().into();
22422251
payer_node.start().unwrap();
22432252

22442253
let service_addr_onchain = service_node.onchain_payment().new_address().unwrap();

0 commit comments

Comments
 (0)