Skip to content

Conversation

@handewo
Copy link
Contributor

@handewo handewo commented Aug 20, 2025

Note: this is a breaking API change.

New feather:
This is useful if you need to update the handler
without relying on a mutex (or similar synchronization).

here is demo

use tokio::sync::mpsc::Receiver;
use russh::server::Handler;

struct App{
    foo: String
    recv: Receiver<String>,
    trigger: Receiver<String>,
}

impl App for Handler {
    type Error = russh::Error;
    type Data = String;
    async fn trigger(&mut self) -> Result<Self::Data, Self::Error> {
        match self.trigger.recv().await {
            Some(d) => Ok(d),
            None => std::future::pending().await,
        }
    }

    async fn process(&mut self, s: Self::Data) -> Result<(), Self::Error> {
        let s = self.recv.recv().await.unwrap();
        self.foo = s;
        Ok(())
    }
}

New feather:
    Add a hook-trigger to the handler.
    This is useful if you need to update the handler
    without relying on a mutex (or similar synchronization).
@Eugeny
Copy link
Owner

Eugeny commented Aug 20, 2025

IMO this is a bit of a crutch that doesn't belong in the library. Is there any reason for this except running inside the russh event loop? Mutable access to the handler? TBH I'm rather inclined to hand off resources to the task that is supposed to call trigger() or go all the way and introduce a custom E event type, adding Msg::CustomEvent<E> and a fn custom_event handler fn, integrating it into the server event loop

@handewo
Copy link
Contributor Author

handewo commented Aug 20, 2025

Yes, Mutable access to the handler. I don’t know if there’s another way to update the handler’s status without locking it.

@handewo
Copy link
Contributor Author

handewo commented Aug 20, 2025

I think Msg::CustomEvent<E> is only work in situation that ssh client support sending custom Msg. Is it?

@Eugeny
Copy link
Owner

Eugeny commented Aug 20, 2025

No, I meant the event loop messages that are going through server::Session::receiver, not wire messages

@handewo
Copy link
Contributor Author

handewo commented Aug 21, 2025

Below is some pseudo-code whose purpose is to switch apps at runtime.
The send_app_msg argument is passed to the app, and then send() is executed inside a tokio::spawn! block, spawned by app.
In my view, adding Msg::CustomEvent<E> plus a custom_event handler function is not sufficient for this scenario.

pub struct MyHandler {
    app: Application,
    send_app_msg: Sender<Application>,
    recv_app_msg: Receiver<Application>,
}

pub enum Application {
    Foo(Box<Foo>),
    Bar(Box<Bar>),
}

impl ru_server::Handler for MyHandler {
    async fn data(
        &mut self,
        channel: ChannelId,
        data: &[u8],
        session: &mut ru_server::Session,
    ) -> Result<(), Self::Error> {
        match self.app {
            Application::Foo(ref mut app) => app.data(channel, data, session).await,
            Application::Bar(ref mut app) => app.data(channel, data, session).await,
        }
    }

    async fn trigger(&mut self) -> Result<Self::Data, Self::Error> {
        match self.recv_app_msg.recv().await {
            Some(d) => Ok(d),
            None => std::future::pending().await,
        }
    }

    async fn process(&mut self, app: Self::Data) -> Result<(), Self::Error> {
        app.init().await?;
        self.app = app;
    }
}

@Eugeny
Copy link
Owner

Eugeny commented Aug 24, 2025

I think this could work?

enum CustomEvent {
    AppSwap(App)
}

impl ru_server::Handler for MyHandler {
    type CustomEvent = CustomEvent;

    async fn custom_event(&mut self, e: Self::CustomEvent) {
        match e {
            CustomEvent::AppSwap(app) => {
                app.init().await?;
                self.app = app
            }
        }
    }
}

..

// from another task
session_handle.emit_custom_event(CustomEvent::AppSwap(...)) // places Msg::Custom(CustomEvent::AppSwap(...)) on the event queue

@handewo
Copy link
Contributor Author

handewo commented Aug 25, 2025

Got it, I will try it

@handewo
Copy link
Contributor Author

handewo commented Aug 26, 2025

Do you mean something like this?
Please see the code below.
If we introduce a generic type parameter into Msg, the changes required would be significant.

impl Handle {
    ...

    /// Emit custom event.
    pub async fn emit_custom_event<H>(&self, event: H::CustomEvent) -> Result<(), ()>
    where
        H: Handler,
    {
        self.sender
            .send(Msg::CustomEvent(event))
            .await
            .map_err(|_| ())
    }
}

#[derive(Debug)]
pub enum Msg<H:Handler> {
    ......
    Channel(ChannelId, ChannelMsg),
    CustomEvent(H::CustomEvent),
}

    pub(crate) async fn run<H, R>(
        mut self,
        mut stream: SshRead<R>,
        mut handler: H,
    ) -> Result<(), H::Error>
    where
        H: Handler + Send + 'static,
        R: AsyncRead + AsyncWrite + Unpin + Send + 'static,
    {
        .......
                msg = self.receiver.recv(), if !self.kex.active() => {
                    match msg {

                        Some(Msg::Disconnect {reason, description, language_tag}) => {
                            self.common.disconnect(reason, &description, &language_tag)?;
                        }
                        Some(Msg::CustomEvent(event)) => {
                            handler.custom_event(event).await?;
                        }
                     }
                  }
         .....
    }

pub trait Handler: Sized {
    type Error: From<crate::Error> + Send;
    type CustomEvent: Send;
    ...
    ///
    #[allow(unused_variables)]
    fn custom_event(
        &mut self,
        event: Self::CustomEvent,
    ) -> impl Future<Output = Result<(), Self::Error>> + Send {
        async { Ok(()) }
    }
}

Here is compile error

error[E0107]: missing generics for enum `server::session::Msg`
   --> russh/src/server/session.rs:358:25
    |
358 |     ) -> Result<Channel<Msg>, Error> {
    |                         ^^^ expected 1 generic argument
    |
note: enum defined here, with 1 generic parameter: `H`
   --> russh/src/server/session.rs:34:10
    |
34  | pub enum Msg<H: Handler> {
    |          ^^^ -
help: add missing generic argument
    |
358 |     ) -> Result<Channel<Msg<H>>, Error> {
    |                            +++

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants