Skip to content

Commit 57271cd

Browse files
committed
Decouple LSPS4 webhooks from liquidity loop
Send LSPS4 wake-up webhooks in the background instead of awaiting the HTTP response on the liquidity event loop. Direct webhook delivery was meant to reduce wake-up latency, but waiting for the response made unrelated liquidity work depend on the client's webhook handler. When that handler keeps the request open while the node wakes and starts processing payments, follow-up actions such as opening a channel can be delayed behind the webhook. Keep a long timeout on the background request as cleanup for genuinely stuck connections. The timeout is not part of LSPS4 control flow; it is there to bound resource usage while still treating the webhook as best-effort.
1 parent e4caeaa commit 57271cd

1 file changed

Lines changed: 28 additions & 3 deletions

File tree

src/liquidity.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ use crate::fee_estimator::{self, ConfirmationTarget, FeeEstimator, OnchainFeeEst
6161
use crate::{total_anchor_channels_reserve_sats, Config, Error};
6262

6363
const LIQUIDITY_REQUEST_TIMEOUT_SECS: u64 = 5;
64+
// Client webhooks may intentionally keep the HTTP response open while the node wakes
65+
// and processes incoming payments. This timeout is only to clean up genuinely stuck
66+
// requests without blocking LSPS4 liquidity actions.
67+
const DIRECT_WEBHOOK_TIMEOUT_SECS: u64 = 70;
6468

6569
const LSPS2_GETINFO_REQUEST_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);
6670
pub(crate) const LSPS2_CHANNEL_CLTV_EXPIRY_DELTA: u32 = 72;
@@ -1336,15 +1340,36 @@ where
13361340
let mut json_body = HashMap::new();
13371341
json_body.insert("nodeId", counterparty_node_id.to_string());
13381342
json_body.insert("paymentHash", payment_hash.to_string());
1343+
let counterparty_node_id = counterparty_node_id.to_string();
1344+
let payment_hash = payment_hash.to_string();
1345+
let client = client.clone();
1346+
let url = url.clone();
13391347
log_info!(
13401348
self.logger,
13411349
"Sending webhook directly for payment_hash={} node={}",
13421350
payment_hash,
13431351
counterparty_node_id
13441352
);
1345-
if let Err(e) = client.post(url).json(&json_body).send().await {
1346-
log_error!(self.logger, "Direct webhook POST failed: {:?}", e);
1347-
}
1353+
let _ = tokio::spawn(async move {
1354+
match tokio::time::timeout(
1355+
Duration::from_secs(DIRECT_WEBHOOK_TIMEOUT_SECS),
1356+
client.post(&url).json(&json_body).send(),
1357+
)
1358+
.await
1359+
{
1360+
Ok(Ok(_)) => {},
1361+
Ok(Err(e)) => {
1362+
log::error!("Direct webhook POST failed: {:?}", e);
1363+
},
1364+
Err(_) => {
1365+
log::error!(
1366+
"Direct webhook POST timed out for payment_hash={} node={}",
1367+
payment_hash,
1368+
counterparty_node_id
1369+
);
1370+
},
1371+
}
1372+
});
13481373
} else {
13491374
// Fallback: route through event queue (slower path via S3 persistence).
13501375
log_info!(

0 commit comments

Comments
 (0)