dbus2 / zbus-old

Rust D-Bus crate.
https://gitlab.freedesktop.org/dbus/zbus
Other
49 stars 13 forks source link

Can many connections share one interface? #293

Closed zeenix closed 1 year ago

zeenix commented 1 year ago

In GitLab by @Einsler on Oct 28, 2022, 03:43

Hi, I'm trying to write a p2p dbus server (like systemd does, which listens on /run/systemd/private). When a client sends "SayHello" to the server, the server's count increase 1.

But I don't know how to do that. I can only build the server connection by stream, so when a new client tries to connect the server, I have to create a new connection, then I have to create a new interface for this connection. As a result, eachtime a client sends "SayHello", the count it gets is always 1 (not 2, 3, 4...)

Can many connections share one interface? Or could you please provide any suggestion?

Sorry if it's a wrong place to look for help, but I don't know where else can solve my problem. Thank you very much for your time!

The code demo is as follows:

use std::{error::Error};

use zbus::{ConnectionBuilder, Guid, dbus_interface};

#[derive(Clone, Copy)]
struct Greeter {
    count: u64
}

#[dbus_interface(name = "net.foo.bar")]
impl Greeter {
    fn say_hello(&mut self, name: &str) -> String {
        self.count += 1;
        format!("Hello {}! I have been called {} times.", name, self.count)
    }
}

#[tokio::main]
#[cfg(unix)]
async fn main() -> Result<(), Box<dyn Error>> {
    use futures::TryStreamExt;
    use tokio::net::UnixListener;
    use zbus::MessageStream;
    let greeter = Greeter{count: 0};
    let listener = UnixListener::bind("/run/user/1000/test").unwrap();
    loop {
        let (stream, _) = listener.accept().await?;

        let guid = Guid::generate();
        let server = ConnectionBuilder::unix_stream(stream)
            .server(&guid)
            .internal_executor(true)
            .serve_at("/net/foo/bar", greeter)?
            .p2p()
            .build().await?;
        println!("server built!");

        let mut m_stream = MessageStream::from(&server);
        let m = m_stream.try_next().await?.unwrap();
        println!("Got {}", m.to_string());
    }
    Ok(())
}
zeenix commented 1 year ago

In GitLab by @Einsler on Oct 28, 2022, 05:03

I have a temporary workaround. change Greeter to

struct Greeter {
    count: Arc<Mutex<u64>>
}

When the server accept a client's connect request, it always create a new interface (not copy). Now everything works fine. But I think it's ugly, and wonder if there is a better way.

zeenix commented 1 year ago

In case of p2p, you'll have to separate connection for each client, there is no way around that. However, you're on the right track with last your comment. You either need to share the state between multiple interface instances or maintain the actual state in another task/thread and communicate with that from the interface impl using channels.

zeenix commented 1 year ago

But I think it's ugly, and wonder if there is a better way.

I disagree. The only thing is that there will be a performance penalty (although in 99.99% cases it's not even worth mentioning). We do the same in zbus itself. Keep in mind that it's best to have fine-grained locks so if you decided to have more fields and have them all in another struct, best to have Mutex on the actual fields themselves and the Arc only around the whole struct in the main struct (which would be Greeter here).

zeenix commented 1 year ago

In GitLab by @Einsler on Oct 31, 2022, 03:52

Thank you very much for your reply.