Skip to content

Commit 05d7a7d

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 cd4e6e5 commit 05d7a7d

File tree

9 files changed

+87
-20
lines changed

9 files changed

+87
-20
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
@@ -122,6 +122,7 @@ check-cfg = [
122122
"cfg(tokio_unstable)",
123123
"cfg(cln_test)",
124124
"cfg(lnd_test)",
125+
"cfg(cycle_tests)",
125126
]
126127

127128
[[bench]]

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,6 +1754,12 @@ impl Node {
17541754
Error::PersistenceFailed
17551755
})
17561756
}
1757+
1758+
#[cfg(cycle_tests)]
1759+
/// Fetch a reference to the inner NetworkGraph, for Arc cycle detection
1760+
pub fn fetch_ref(&self) -> std::sync::Weak<Graph> {
1761+
Arc::downgrade(&self.network_graph)
1762+
}
17571763
}
17581764

17591765
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: 43 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,49 @@ 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+
let graph_ref = (**self).fetch_ref();
304+
self.inner.take();
305+
assert_eq!(graph_ref.strong_count(), 0);
306+
}
307+
}
268308

269309
#[derive(Clone)]
270310
pub(crate) enum TestChainSource<'a> {
@@ -430,7 +470,7 @@ pub(crate) fn setup_node_for_async_payments(
430470
node.start().unwrap();
431471
assert!(node.status().is_running);
432472
assert!(node.status().latest_fee_rate_cache_update_timestamp.is_some());
433-
node
473+
node.into()
434474
}
435475

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

tests/integration_tests_rust.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +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, TestSyncStore,
26+
setup_node_for_async_payments, setup_two_nodes, wait_for_tx, TestChainSource, TestNode,
27+
TestSyncStore,
2728
};
2829
use ldk_node::config::{AsyncPaymentsRole, EsploraSyncConfig};
2930
use ldk_node::liquidity::LSPS2ServiceConfig;
@@ -153,15 +154,15 @@ async fn multi_hop_sending() {
153154
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
154155

155156
// Setup and fund 5 nodes
156-
let mut nodes = Vec::new();
157+
let mut nodes: Vec<TestNode> = Vec::new();
157158
for _ in 0..5 {
158159
let config = random_config(true);
159160
let sync_config = EsploraSyncConfig { background_sync_config: None };
160161
setup_builder!(builder, config.node_config);
161162
builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
162163
let node = builder.build(config.node_entropy.into()).unwrap();
163164
node.start().unwrap();
164-
nodes.push(node);
165+
nodes.push(node.into());
165166
}
166167

167168
let addresses = nodes.iter().map(|n| n.onchain_payment().new_address().unwrap()).collect();
@@ -1728,7 +1729,8 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) {
17281729
setup_builder!(service_builder, service_config.node_config);
17291730
service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
17301731
service_builder.set_liquidity_provider_lsps2(lsps2_service_config);
1731-
let service_node = service_builder.build(service_config.node_entropy.into()).unwrap();
1732+
let service_node: TestNode =
1733+
service_builder.build(service_config.node_entropy.into()).unwrap().into();
17321734
service_node.start().unwrap();
17331735

17341736
let service_node_id = service_node.node_id();
@@ -1738,13 +1740,15 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) {
17381740
setup_builder!(client_builder, client_config.node_config);
17391741
client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
17401742
client_builder.set_liquidity_source_lsps2(service_node_id, service_addr, None);
1741-
let client_node = client_builder.build(client_config.node_entropy.into()).unwrap();
1743+
let client_node: TestNode =
1744+
client_builder.build(client_config.node_entropy.into()).unwrap().into();
17421745
client_node.start().unwrap();
17431746

17441747
let payer_config = random_config(true);
17451748
setup_builder!(payer_builder, payer_config.node_config);
17461749
payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
1747-
let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap();
1750+
let payer_node: TestNode =
1751+
payer_builder.build(payer_config.node_entropy.into()).unwrap().into();
17481752
payer_node.start().unwrap();
17491753

17501754
let service_addr = service_node.onchain_payment().new_address().unwrap();
@@ -2045,7 +2049,8 @@ async fn lsps2_client_trusts_lsp() {
20452049
setup_builder!(service_builder, service_config.node_config);
20462050
service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
20472051
service_builder.set_liquidity_provider_lsps2(lsps2_service_config);
2048-
let service_node = service_builder.build(service_config.node_entropy.into()).unwrap();
2052+
let service_node: TestNode =
2053+
service_builder.build(service_config.node_entropy.into()).unwrap().into();
20492054
service_node.start().unwrap();
20502055
let service_node_id = service_node.node_id();
20512056
let service_addr = service_node.listening_addresses().unwrap().first().unwrap().clone();
@@ -2054,14 +2059,16 @@ async fn lsps2_client_trusts_lsp() {
20542059
setup_builder!(client_builder, client_config.node_config);
20552060
client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
20562061
client_builder.set_liquidity_source_lsps2(service_node_id, service_addr.clone(), None);
2057-
let client_node = client_builder.build(client_config.node_entropy.into()).unwrap();
2062+
let client_node: TestNode =
2063+
client_builder.build(client_config.node_entropy.into()).unwrap().into();
20582064
client_node.start().unwrap();
20592065
let client_node_id = client_node.node_id();
20602066

20612067
let payer_config = random_config(true);
20622068
setup_builder!(payer_builder, payer_config.node_config);
20632069
payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
2064-
let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap();
2070+
let payer_node: TestNode =
2071+
payer_builder.build(payer_config.node_entropy.into()).unwrap().into();
20652072
payer_node.start().unwrap();
20662073

20672074
let service_addr_onchain = service_node.onchain_payment().new_address().unwrap();
@@ -2218,7 +2225,8 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() {
22182225
setup_builder!(service_builder, service_config.node_config);
22192226
service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
22202227
service_builder.set_liquidity_provider_lsps2(lsps2_service_config);
2221-
let service_node = service_builder.build(service_config.node_entropy.into()).unwrap();
2228+
let service_node: TestNode =
2229+
service_builder.build(service_config.node_entropy.into()).unwrap().into();
22222230
service_node.start().unwrap();
22232231

22242232
let service_node_id = service_node.node_id();
@@ -2228,15 +2236,17 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() {
22282236
setup_builder!(client_builder, client_config.node_config);
22292237
client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
22302238
client_builder.set_liquidity_source_lsps2(service_node_id, service_addr.clone(), None);
2231-
let client_node = client_builder.build(client_config.node_entropy.into()).unwrap();
2239+
let client_node: TestNode =
2240+
client_builder.build(client_config.node_entropy.into()).unwrap().into();
22322241
client_node.start().unwrap();
22332242

22342243
let client_node_id = client_node.node_id();
22352244

22362245
let payer_config = random_config(true);
22372246
setup_builder!(payer_builder, payer_config.node_config);
22382247
payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
2239-
let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap();
2248+
let payer_node: TestNode =
2249+
payer_builder.build(payer_config.node_entropy.into()).unwrap().into();
22402250
payer_node.start().unwrap();
22412251

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

0 commit comments

Comments
 (0)