Share reference between functions without passing in params

I'm looking at building a ui that starts/stops a http server using tauri.

I'm using this guide Calling Rust from the frontend | Tauri Apps

I'm new to rust, this code does not work, but it sort of shows what im trying to achieve


pub struct ServerThread {
    server_thread: JoinHandle<T>
}

enum Message {
    Terminate,
}

impl ServerThread {

    pub fn create(port: i16) -> ServerThread {
        let (sender, receiver) = mpsc::channel();

        let receiver = Arc::new(Mutex::new(receiver));

        let server_thread = thread::spawn(move || {

            /// start the server
            setup_http_server(port);

            loop {
                let message = receiver.lock().unwrap().recv().unwrap();

                match message {
                    Message::Terminate => {
                        println!("Worker was told to terminate." );

                    },
                }
            }

        });

        Self {
            server_thread
        }
    }

    pub fn stop(&self) -> Result<(), String> {


        Ok(())
    }

}

fn setup_http_server(port: i16) -> Result<(), String> {
    let listener = TcpListener::bind("127.0.0.1:".to_owned() + &*port.to_string()).unwrap();

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        println!("Connection established!");
    }

    Ok(())
}

Using Tauri you can pass commands back from the react frontend


fn main() {

    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            server::instance::startServer,
            server::instance::stopServer,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

The generate handler allows you to pass in functions and params. The issue is, i have no idea to pass the same reference of the impl ServerThread and call the terminate message

#[tauri::command]
pub fn startServer(port: i16) -> Result<(), String> {

    let server_thread = ServerThread::create(port);

    Ok(())
}

#[tauri::command]
pub fn stopServer() -> Result<(), String> {

    let server_thread = ServerThread::stop();

    Ok(())
}

the stop command doesn't work as it asks me to pass "self" when i try ServerThread.stop(); it doesn't have the properties to access the server thread and shut it down

Hoping you guys might have some advice as i seem to hit a roadblock can't find the docs that explain how to solve this.

I'm a bit confused about what you're trying to do. I haven't done a deep dive on tauri but from the page you linked it appears to handle all the details of setting up and running a server.

Can you expand a bit on what you're trying to accomplish?

Assuming you actually do want a totally separate HTTP server running in the same process as tauri:

You can't stop a thread with a thread handle. You already have things set up to use an mpsc channel and a terminate message to exit the loop. Just break out of the loop after your println and the thread will exit.

You'll need to have the mpsc sender accessible from wherever you're defining the stop handler. It looks like the page you linked to has a section about sharing data between handlers here called "managed state"

You'll need to have your ServerThread type stored in managed state. Presumably using an Arc<Mutex<Option<ServerThread>>> or similar.

I don't think you want the mpsc receiver in a Mutex, that should just be moved into the server thread and the sender should be placed in a field on ServerThread so your stop handler can use it to send a terminate message

2 Likes

It sounds like one of the pieces you're after is having the T values contained in your JoinHandle be generic over a variety of types, but for that to be completely hidden within ServerThread. If so you might consider holding a trait object (JoinHandle<Box<dyn TraitT>>) if they all have a common interface. Or, if you know every type T to be held, you could put them all into an enum and go with JoinHandle<EnumT>.

Edit: Just took a look at Tauri, perhaps the return types for the thread are just String and I got caught on the wrong bit.

Thanks alot! i managed to piece it all together, it's not complete but i managed to pass the same server thread to both tauri commands/functions

Still really new with rust, (literally my first app!) just needed pointing in the right direction. Appreciate it :slight_smile:

Learnt a fair bit on Arc Mutex and options, which I'm realising are super important

For yours and others reference, the aim/point of the code is to be able to send a command from a react frontend to start/stop a server

main.rs



use std::sync::{Arc, Mutex};
use crate::server::lib::ServerThread;

mod server;

fn main() {

    tauri::Builder::default()
        .manage(server::instance::ServerState(
            Arc::new(Mutex::new(Option::from(ServerThread::new())))
        ))
        .invoke_handler(tauri::generate_handler![
            server::instance::start_server,
            server::instance::stop_Server,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
use std::borrow::Borrow;
use std::net::TcpListener;
use std::sync::{Arc, Mutex};
use crate::server::lib::ServerThread;


pub struct ServerState(pub Arc<Mutex<Option<ServerThread>>>);

#[tauri::command]
pub fn start_server(port: i16, state: tauri::State<ServerState>) -> Result<(), String> {

    let mut mutex_guard  = state.0.lock().unwrap();

    let server_thread: &ServerThread = match *mutex_guard {

        Some(ref x) => x,
        None => panic!("Error."),
    };

    server_thread.start_server(port);

    Ok(())
}

#[tauri::command]
pub fn stop_Server(state: tauri::State<ServerState>) -> Result<(), String> {

    let serverState = state.0.lock().unwrap();
    let server_thread: &ServerThread = match *mutex_guard {

        Some(ref x) => x,
        None => panic!("Error."),
    };

    server_thread.stop().expect("Something horrible happened");

    Ok(())
}

And finally the server thread itself

use std::net::TcpListener;
use std::sync::{Arc, mpsc, Mutex};
use std::sync::mpsc::Sender;
use std::thread;
use std::thread::JoinHandle;

pub struct ServerThread {
    pub sender: Arc<Mutex<Option<Sender<Message>>>>
}

pub enum Message {
    Terminate,
}

impl ServerThread {

    pub fn new() -> ServerThread {
        Self {
            sender: Arc::new(Mutex::new(None))
        }
    }
    
    pub fn start_server(&self, port: i16) -> ServerThread {
        let (sender, receiver) = mpsc::channel();

        let receiver = Arc::new(Mutex::new(receiver));

        thread::spawn(move || {

            let listener = TcpListener::bind("127.0.0.1:".to_owned() + &*port.to_string()).unwrap();

            for stream in listener.incoming() {
                let stream = stream.unwrap();

                println!("Connection established!");

            }

            let message = receiver.lock().unwrap().recv().unwrap();

            match message {
                Message::Terminate => {
                    println!("Worker was told to terminate." );

                },
            }
        });

        let sender = Arc::new(Mutex::new(Some(sender)));

        Self {
            sender
        }
    }

    pub fn stop(&self) -> Result<(), String> {

        println!("Worker was told to terminate." );
        Ok(())
    }

}

I don't think the server itself is closing down quite right yet but im sure ill find the answer here Graceful Shutdown and Cleanup - The Rust Programming Language

Glad you made progress! :blush:

Is there a reason to not have this be None and then get rid of the Arc Mutex in ServerThread?

The server isn't stopping because this loop will run forever. You'd need to use set_nonblocking to avoid that. Though that can involve some platform specific trickery. If you eventually move to using an existing HTTP server implementation the library will usually handle that stuff for you so you probably don't need to worry about that just yet.

1 Like

Yeah i did try None, it makes sense to not have it created until someone actually does start but i didn't know how to create the code to make the reference and add it to the state in the start_server function

so instead went with something i could get working :sweat_smile: :rofl:

Awesome thank you i'll give it a shot :slight_smile: