Parcourir la source

net/protocol: introduce new generic protocol acting as a message queue

skoupidi il y a 1 an
Parent
commit
392a26ce6a

+ 8 - 0
src/net/protocol/mod.rs

@@ -71,6 +71,14 @@ pub use protocol_address::ProtocolAddress;
 pub mod protocol_seed;
 pub mod protocol_seed;
 pub use protocol_seed::ProtocolSeed;
 pub use protocol_seed::ProtocolSeed;
 
 
+/// Generic protocol to receive specified structure messages.
+///
+/// Acts as a simple message queue, where we listen for the specified
+/// structure message, and when one is received, we send it to the provided
+/// smol channel. Afterwards, we wait for an action signal, specifying whether
+/// or not we should propagate the message to rest nodes or skip it.
+pub mod protocol_generic;
+
 /// Base trait for implementing P2P protocols
 /// Base trait for implementing P2P protocols
 pub mod protocol_base;
 pub mod protocol_base;
 /// Interface for registering arbitrary P2P protocols
 /// Interface for registering arbitrary P2P protocols

+ 260 - 0
src/net/protocol/protocol_generic.rs

@@ -0,0 +1,260 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{clone::Clone, collections::HashMap, sync::Arc};
+
+use async_trait::async_trait;
+use log::debug;
+use smol::{
+    channel::{Receiver, Sender},
+    lock::RwLock,
+    Executor,
+};
+
+use super::{
+    super::{
+        channel::ChannelPtr, message::Message, message_publisher::MessageSubscription,
+        session::SessionBitFlag,
+    },
+    protocol_base::{ProtocolBase, ProtocolBasePtr},
+    protocol_jobs_manager::{ProtocolJobsManager, ProtocolJobsManagerPtr},
+    P2pPtr,
+};
+use crate::{system::StoppableTask, Error, Result};
+
+/// Defines generic messages protocol action signal.
+#[derive(Debug)]
+pub enum ProtocolGenericAction {
+    /// Broadcast message to rest nodes
+    Broadcast,
+    /// Skip message broadcast
+    Skip,
+    /// Stop the channel entirely
+    Stop,
+}
+
+pub type ProtocolGenericHandlerPtr<M> = Arc<ProtocolGenericHandler<M>>;
+
+/// Defines a hanlder for generic messages, consisting of
+/// a message receiver, action signal senders mapped by each
+/// channel ID, and a stoppable task to run the handler in
+/// the background.
+pub struct ProtocolGenericHandler<M: Message + Clone> {
+    // Since smol channels close if all senders or all receivers
+    // get dropped, we will keep one here to remain alive with the
+    // handler.
+    sender: Sender<(u32, M)>,
+    pub receiver: Receiver<(u32, M)>,
+    senders: RwLock<HashMap<u32, Sender<ProtocolGenericAction>>>,
+    pub task: Arc<StoppableTask>,
+}
+
+impl<M: Message + Clone> ProtocolGenericHandler<M> {
+    pub async fn new(
+        p2p: &P2pPtr,
+        name: &'static str,
+        session: SessionBitFlag,
+    ) -> ProtocolGenericHandlerPtr<M> {
+        let (sender, receiver) = smol::channel::unbounded::<(u32, M)>();
+        let senders = RwLock::new(HashMap::new());
+        let task = StoppableTask::new();
+        let handler = Arc::new(Self { sender, receiver, senders, task });
+
+        let _handler = handler.clone();
+        p2p.protocol_registry()
+            .register(session, move |channel, p2p| {
+                let handler = _handler.clone();
+                async move { ProtocolGeneric::init(channel, name, handler, p2p).await.unwrap() }
+            })
+            .await;
+
+        handler
+    }
+
+    /// Registers a new channel sender to the handler map.
+    /// Additionally, looks for stale(closed) channels and prunes then from it.
+    async fn register_channel_sender(&self, channel: u32, sender: Sender<ProtocolGenericAction>) {
+        let mut lock = self.senders.write().await;
+        lock.insert(channel, sender);
+        let mut stale = vec![];
+        for (channel, sender) in lock.iter() {
+            if sender.is_closed() {
+                stale.push(*channel);
+            }
+        }
+        for channel in stale {
+            lock.remove(&channel);
+        }
+        drop(lock);
+    }
+
+    /// Sends provided protocol generic action to requested channel, if it exists.
+    pub async fn send_action(&self, channel: u32, action: ProtocolGenericAction) {
+        debug!(
+            target: "net::protocol_generic::ProtocolGenericHandler::send_action",
+            "Sending action {action:?} to channel {channel}..."
+        );
+        let mut lock = self.senders.write().await;
+        let Some(sender) = lock.get(&channel) else {
+            debug!(
+                target: "net::protocol_generic::ProtocolGenericHandler::send_action",
+                "Channel wasn't found."
+            );
+            drop(lock);
+            return
+        };
+        if let Err(e) = sender.send(action).await {
+            debug!(
+                target: "net::protocol_generic::ProtocolGenericHandler::send_action",
+                "Channel {channel} send fail: {e}"
+            );
+            lock.remove(&channel);
+        };
+        drop(lock);
+    }
+}
+
+/// Defines generic messages protocol.
+pub struct ProtocolGeneric<M: Message + Clone> {
+    /// The P2P channel message subcription
+    msg_sub: MessageSubscription<M>,
+    /// The generic message smol channel sender
+    sender: Sender<(u32, M)>,
+    /// Action signal smol channel receiver
+    receiver: Receiver<ProtocolGenericAction>,
+    /// The P2P channel the protocol is serving
+    channel: ChannelPtr,
+    /// Pointer to the whole P2P instance
+    p2p: P2pPtr,
+    /// Pointer to the protocol job manager
+    jobsman: ProtocolJobsManagerPtr,
+}
+
+impl<M: Message + Clone> ProtocolGeneric<M> {
+    /// Initialize a new generic protocol.
+    pub async fn init(
+        channel: ChannelPtr,
+        name: &'static str,
+        handler: ProtocolGenericHandlerPtr<M>,
+        p2p: P2pPtr,
+    ) -> Result<ProtocolBasePtr> {
+        debug!(
+            target: "net::protocol_generic::init",
+            "Adding generic protocol for message {name} to the protocol registry"
+        );
+        // Add the message dispatcher
+        let msg_subsystem = channel.message_subsystem();
+        msg_subsystem.add_dispatch::<M>().await;
+
+        // Create the message subscription
+        let msg_sub = channel.subscribe_msg::<M>().await?;
+
+        // Create a new sender channel
+        let (action_sender, receiver) = smol::channel::bounded(1);
+        handler.register_channel_sender(channel.info.id, action_sender).await;
+
+        Ok(Arc::new(Self {
+            msg_sub,
+            sender: handler.sender.clone(),
+            receiver,
+            channel: channel.clone(),
+            p2p,
+            jobsman: ProtocolJobsManager::new(name, channel),
+        }))
+    }
+
+    /// Runs the message queue. We listen for the specified structure message,
+    /// and when one is received, we send it to our smol channel. Afterwards,
+    /// we wait for an action signal, specifying whether or not we should
+    /// propagate the message to rest nodes or skip it.
+    async fn handle_receive_message(self: Arc<Self>) -> Result<()> {
+        debug!(
+            target: "net::protocol_generic::handle_receive_message",
+            "START"
+        );
+        let exclude_list = vec![self.channel.address().clone()];
+        loop {
+            // Wait for a new message
+            let msg = match self.msg_sub.receive().await {
+                Ok(m) => m,
+                Err(e) => {
+                    debug!(
+                        target: "net::protocol_generic::handle_receive_message",
+                        "[{}] recv fail: {e}", self.jobsman.clone().name()
+                    );
+                    continue
+                }
+            };
+
+            let msg_copy = (*msg).clone();
+
+            // Send the message across the smol channel
+            if let Err(e) = self.sender.send((self.channel.info.id, msg_copy.clone())).await {
+                debug!(
+                    target: "net::protocol_generic::handle_receive_message",
+                    "[{}] sending to channel fail: {e}", self.jobsman.clone().name()
+                );
+                continue
+            }
+
+            // Wait for action signal
+            let action = match self.receiver.recv().await {
+                Ok(a) => a,
+                Err(e) => {
+                    debug!(
+                        target: "net::protocol_generic::handle_receive_message",
+                        "[{}] action signal recv fail: {e}", self.jobsman.clone().name()
+                    );
+                    continue
+                }
+            };
+
+            // Handle action signal
+            match action {
+                ProtocolGenericAction::Broadcast => {
+                    self.p2p.broadcast_with_exclude(&msg_copy, &exclude_list).await
+                }
+                ProtocolGenericAction::Skip => {
+                    debug!(
+                        target: "net::protocol_generic::handle_receive_message",
+                        "[{}] Skip action signal received.", self.jobsman.clone().name()
+                    );
+                }
+                ProtocolGenericAction::Stop => {
+                    self.channel.stop().await;
+                    return Err(Error::ChannelStopped)
+                }
+            }
+        }
+    }
+}
+
+#[async_trait]
+impl<M: Message + Clone> ProtocolBase for ProtocolGeneric<M> {
+    async fn start(self: Arc<Self>, ex: Arc<Executor<'_>>) -> Result<()> {
+        debug!(target: "net::protocol_generic::start", "START");
+        self.jobsman.clone().start(ex.clone());
+        self.jobsman.clone().spawn(self.clone().handle_receive_message(), ex).await;
+        debug!(target: "net::protocol_generic::start", "END");
+        Ok(())
+    }
+
+    fn name(&self) -> &'static str {
+        self.jobsman.clone().name()
+    }
+}

+ 5 - 0
src/net/protocol/protocol_jobs_manager.rs

@@ -39,6 +39,11 @@ impl ProtocolJobsManager {
         Arc::new(Self { name, channel, tasks: Mutex::new(vec![]) })
         Arc::new(Self { name, channel, tasks: Mutex::new(vec![]) })
     }
     }
 
 
+    /// Returns configured name
+    pub fn name(self: Arc<Self>) -> &'static str {
+        self.name
+    }
+
     /// Runs the task on an executor
     /// Runs the task on an executor
     pub fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) {
     pub fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) {
         executor.spawn(self.handle_stop()).detach()
         executor.spawn(self.handle_stop()).detach()