-
Notifications
You must be signed in to change notification settings - Fork 158
Release completed cancellable tasks #997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,12 +18,44 @@ use crate::logger::{log_debug, log_error, log_info, LdkLogger}; | |
| use crate::types::{KeysManager, PeerManager}; | ||
| use crate::Error; | ||
|
|
||
| type PendingConnections = | ||
| Mutex<HashMap<PublicKey, Vec<tokio::sync::oneshot::Sender<Result<(), Error>>>>>; | ||
|
|
||
| struct PendingConnectionGuard<'a> { | ||
| pending_connections: &'a PendingConnections, | ||
| node_id: PublicKey, | ||
| active: bool, | ||
| } | ||
|
|
||
| impl<'a> PendingConnectionGuard<'a> { | ||
| fn new(pending_connections: &'a PendingConnections, node_id: PublicKey) -> Self { | ||
| Self { pending_connections, node_id, active: true } | ||
| } | ||
|
|
||
| fn disarm(&mut self) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Is this really needed ? On drop, we'd briefly lock the pending connections, see that we have no subscribers for the node_id, and do nothing. |
||
| self.active = false; | ||
| } | ||
| } | ||
|
|
||
| impl Drop for PendingConnectionGuard<'_> { | ||
| fn drop(&mut self) { | ||
| if !self.active { | ||
| return; | ||
| } | ||
| let mut pending_connections = self.pending_connections.lock().expect("lock"); | ||
| if let Some(subscribers) = pending_connections.remove(&self.node_id) { | ||
| for subscriber in subscribers { | ||
| let _ = subscriber.send(Err(Error::ConnectionFailed)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub(crate) struct ConnectionManager<L: Deref + Clone + Sync + Send> | ||
| where | ||
| L::Target: LdkLogger, | ||
| { | ||
| pending_connections: | ||
| Mutex<HashMap<PublicKey, Vec<tokio::sync::oneshot::Sender<Result<(), Error>>>>>, | ||
| pending_connections: PendingConnections, | ||
| peer_manager: Arc<PeerManager>, | ||
| tor_proxy_config: Option<TorConfig>, | ||
| keys_manager: Arc<KeysManager>, | ||
|
|
@@ -60,26 +92,29 @@ where | |
| pub(crate) async fn do_connect_peer( | ||
| &self, node_id: PublicKey, addr: SocketAddress, | ||
| ) -> Result<(), Error> { | ||
| // If another task is already connecting, subscribe to its result instead of starting a | ||
| // duplicate attempt. | ||
| if let Some(pending_connection_ready_receiver) = | ||
| self.register_or_subscribe_pending_connection(&node_id) | ||
| { | ||
| return pending_connection_ready_receiver.await.map_err(|e| { | ||
| debug_assert!(false, "Failed to receive connection result: {:?}", e); | ||
| log_error!(self.logger, "Failed to receive connection result: {:?}", e); | ||
| Error::ConnectionFailed | ||
| })?; | ||
| } | ||
|
|
||
| let mut pending_connection = | ||
| PendingConnectionGuard::new(&self.pending_connections, node_id); | ||
| let res = self.do_connect_peer_internal(node_id, addr).await; | ||
| self.propagate_result_to_subscribers(&node_id, res); | ||
| pending_connection.disarm(); | ||
| res | ||
| } | ||
|
|
||
| async fn do_connect_peer_internal( | ||
| &self, node_id: PublicKey, addr: SocketAddress, | ||
| ) -> Result<(), Error> { | ||
| // First, we check if there is already an outbound connection in flight, if so, we just | ||
| // await on the corresponding watch channel. The task driving the connection future will | ||
| // send us the result.. | ||
| let pending_ready_receiver_opt = self.register_or_subscribe_pending_connection(&node_id); | ||
| if let Some(pending_connection_ready_receiver) = pending_ready_receiver_opt { | ||
| return pending_connection_ready_receiver.await.map_err(|e| { | ||
| debug_assert!(false, "Failed to receive connection result: {:?}", e); | ||
| log_error!(self.logger, "Failed to receive connection result: {:?}", e); | ||
| Error::ConnectionFailed | ||
| })?; | ||
| } | ||
|
|
||
| log_info!(self.logger, "Connecting to peer: {}@{}", node_id, addr); | ||
|
|
||
| match addr { | ||
|
|
@@ -246,6 +281,7 @@ where | |
| match pending_connections_lock.entry(*node_id) { | ||
| hash_map::Entry::Occupied(mut entry) => { | ||
| let (tx, rx) = tokio::sync::oneshot::channel(); | ||
| entry.get_mut().retain(|subscriber| !subscriber.is_closed()); | ||
| entry.get_mut().push(tx); | ||
| Some(rx) | ||
| }, | ||
|
|
@@ -277,3 +313,26 @@ where | |
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn pending_connection_guard_notifies_subscribers_when_abandoned() { | ||
| let node_id: PublicKey = | ||
| "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".parse().unwrap(); | ||
| let pending_connections = Mutex::new(HashMap::new()); | ||
| let (sender, mut receiver) = tokio::sync::oneshot::channel(); | ||
| pending_connections.lock().expect("lock").insert(node_id, vec![sender]); | ||
|
|
||
| let connection_guard = PendingConnectionGuard::new(&pending_connections, node_id); | ||
| drop(connection_guard); | ||
|
|
||
| assert!( | ||
| pending_connections.lock().expect("lock").is_empty(), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Should we also make sure that key-values for a different node_id are not dropped ? |
||
| "abandoned connection attempt should remove pending state" | ||
| ); | ||
| assert_eq!(receiver.try_recv(), Ok(Err(Error::ConnectionFailed))); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codex:
If a manual bitcoind Node::sync_wallets() call owns wallet_polling_status when this background task starts (for example immediately after start() before the spawned task has registered), this branch now returns from continuously_sync_wallets and permanently stops background chain polling/fee
updates for that run. The task should wait for or otherwise handle the in-progress sync instead of exiting.