Skip to content

impl Clone for InterfaceRef

Jonas Bosse requested to merge JonasBoss/zbus:clone_interface_ref into main

Given that all the fields of InterfaceRef implement Clone, I would expect InterfaceRef itself to be also clone.

I encountered a deadlock on a project I am working on, which would be solved by this.

"Minimal" example
use std::{error::Error};

use tokio::sync::{mpsc, oneshot};
use zbus::{ConnectionBuilder, InterfaceRef};

/// commands that can be sent to the main thread
pub enum Command {
    ComputeAnwser(oneshot::Sender<String>),
}

pub struct DeepThought {
    iface: InterfaceRef<interface::DeepThought>,
}
impl DeepThought {
    pub async fn compute_anwser(&self) -> u64 {
        Self::set_compute_time_async(self.iface.clone(), 7_500_000_000);
        42
    }
}

/// Interface adapter methods
impl DeepThought {
    // this mehtod deadlocks if it is called as a sideeffect while a interface method is running
    async fn set_compute_time(&self, years: u64) {
        let mut iface = self.iface.get_mut().await;
        iface.set_years(years);
        iface
            .compute_time_changed(self.iface.signal_context())
            .await;
    }

    // this method does not deadlock, but will only execute while the interface is idle
    // it also requires a owned InterfaceRef, which is only feasable if InterfaceRef implements Clone
    fn set_compute_time_async(iface: InterfaceRef<interface::DeepThought>, years: u64) {
        tokio::spawn(async move {
            let mut iface_deref = iface.get_mut().await;
            iface_deref.set_years(years);
            iface_deref
                .compute_time_changed(iface.signal_context())
                .await;
        });
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let (cmd_tx, mut cmd_rx) = mpsc::channel(16);
    let iface = interface::DeepThought::new(cmd_tx);
    let connection = ConnectionBuilder::session()?
        .name("Krikkit.DeepTought")?
        .serve_at("/Krikkit/DeepTought", iface)?
        .build()
        .await?;
    let iface = connection
        .object_server()
        .interface::<_, interface::DeepThought>("/Krikkit/DeepTought")
        .await?;
    let deep_thought = DeepThought { iface };

    // command handling
    loop {
        match cmd_rx.recv().await {
            None => continue,
            Some(Command::ComputeAnwser(tx)) => {
                let answer = deep_thought.compute_anwser().await;
                tx.send(format!("The answer is: {answer}"));
            }
        }
    }
}

/// dbus interface definitions
mod interface {
    use tokio::sync::{mpsc, oneshot};
    use zbus::dbus_interface;

    use crate::Command;

    pub struct DeepThought {
        years: u64,
        cmd_channel: mpsc::Sender<Command>,
    }
    impl DeepThought {
        pub fn new(cmd_channel: mpsc::Sender<Command>) -> Self {
            DeepThought {
                years: 0,
                cmd_channel,
            }
        }
        pub fn set_years(&mut self, years: u64) {
            self.years = years
        }
    }

    #[dbus_interface(name = "Krikkit.DeepTought")]
    impl DeepThought {
        #[dbus_interface(property)]
        fn compute_time(&self) -> u64 {
            self.years
        }

        async fn what_is_the_answer(&mut self) -> String {
            let (tx, rx) = oneshot::channel();
            self.cmd_channel.send(Command::ComputeAnwser(tx)).await;
            rx.await.unwrap()
        }
    }
}

Merge request reports