GenServer

Trait GenServer 

Source
pub trait GenServer:
    Sized
    + Send
    + 'static {
    type Message: Receivable;

    // Required method
    fn init(&mut self) -> impl Future<Output = Result<(), ExitReason>> + Send;

    // Provided methods
    fn start(
        self,
        options: GenServerOptions,
    ) -> impl Future<Output = Result<Pid, ExitReason>> + Send { ... }
    fn start_link(
        self,
        options: GenServerOptions,
    ) -> impl Future<Output = Result<Pid, ExitReason>> + Send { ... }
    fn stop<T: Into<Dest>>(
        server: T,
        reason: ExitReason,
        timeout: Option<Duration>,
    ) -> impl Future<Output = Result<(), ExitReason>> { ... }
    fn cast<T: Into<Dests>>(servers: T, message: Self::Message) { ... }
    fn cast_after<T: Into<Dests>>(
        servers: T,
        message: Self::Message,
        duration: Duration,
    ) -> Reference { ... }
    fn call<T: Into<Dest>>(
        server: T,
        message: Self::Message,
        timeout: Option<Duration>,
    ) -> impl Future<Output = Result<Self::Message, CallError>> + Send { ... }
    fn reply(from: From, message: Self::Message) { ... }
    fn terminate(
        &mut self,
        reason: ExitReason,
    ) -> impl Future<Output = ()> + Send { ... }
    fn handle_cast(
        &mut self,
        message: Self::Message,
    ) -> impl Future<Output = Result<(), ExitReason>> + Send { ... }
    fn handle_info(
        &mut self,
        info: Message<Self::Message>,
    ) -> impl Future<Output = Result<(), ExitReason>> + Send { ... }
    fn handle_call(
        &mut self,
        message: Self::Message,
        from: From,
    ) -> impl Future<Output = Result<Option<Self::Message>, ExitReason>> + Send { ... }
}
Expand description

A trait for implementing the server of a client-server relation.

A GenServer is a process like any other hydra process and it can be used to keep state, execute code asynchronously and so on.

The advantage of using a generic server process (GenServer) implemented using this trait is that it will have a standard set of trait functions and include functionality for tracing and error reporting.

It will also fit into a supervision tree.

§Example

Let’s start with a code example and then explore the available callbacks. Imagine we want to implement a service with a GenServer that works like a stack, allowing us to push and pop elements. We’ll customize a generic GenServer with our own module by implementing three callbacks.

#[derive(Debug, Serialize, Deserialize)]
enum StackMessage {
    Pop,
    PopResult(String),
    Push(String),
}

struct Stack {
    stack: Vec<String>,
}

impl Stack {
    pub fn with_entries(entries: Vec<&'static str>) -> Self {
        Self {
            stack: Vec::from_iter(entries.into_iter().map(Into::into)),
        }
    }
}

impl GenServer for Stack {
    type Message = StackMessage;

    async fn init(&mut self) -> Result<(), ExitReason> {
        Ok(())
    }

    async fn handle_call(&mut self, message: Self::Message, _from: From) -> Result<Option<Self::Message>, ExitReason> {
        match message {
            StackMessage::Pop => Ok(Some(StackMessage::PopResult(self.stack.remove(0)))),
            _ => unreachable!(),
        }
    }

    async fn handle_cast(&mut self, message: Self::Message) -> Result<(), ExitReason> {
        match message {
            StackMessage::Push(value) => self.stack.insert(0, value),
            _ => unreachable!(),
        }
        Ok(())
    }
}

We leave the process machinery of startup, message passing, and the message loop to the GenServer. We can now use the GenServer methods to interact with the service by creating a process and sending it messages:

// Start the server.
let pid = Stack::with_entries(vec![String::from("hello"), String::from("world")])
            .start_link(GenServerOptions::new())
            .await
            .expect("Failed to start stack!");

// This is the client.
Stack::call(pid, StackMessage::Pop, None)
        .await
        .expect("Stack call failed!");
// => StackMessage::PopResult("hello")

Stack::cast(pid, StackMessage::Push(String::from("rust")))

Stack::call(pid, StackMessage::Pop, None)
        .await
        .expect("Stack call failed!");
// => StackMessage::PopResult("rust")

Required Associated Types§

Source

type Message: Receivable

The message type that this server will use.

Required Methods§

Source

fn init(&mut self) -> impl Future<Output = Result<(), ExitReason>> + Send

Invoked when the server is started. start_link or start will block until it returns.

Provided Methods§

Source

fn start( self, options: GenServerOptions, ) -> impl Future<Output = Result<Pid, ExitReason>> + Send

Starts a GenServer process without links.

Starts a GenServer process linked to the current process.

Examples found in repository?
examples/supervisor.rs (line 67)
65    pub fn child_spec(self) -> ChildSpec {
66        ChildSpec::new("MyServer")
67            .start(move || MyServer::start_link(MyServer, GenServerOptions::new()))
68    }
More examples
Hide additional examples
examples/application.rs (line 63)
61    async fn start(&self) -> Result<Pid, ExitReason> {
62        let pid = Stack::with_entries(vec!["hello", "world"])
63            .start_link(GenServerOptions::new())
64            .await
65            .expect("Failed to start stack!");
66
67        let result = Stack::call(pid, StackMessage::Pop, None)
68            .await
69            .expect("Stack call failed!");
70
71        tracing::info!("{:?}", result);
72
73        Stack::cast(pid, StackMessage::Push(String::from("rust")));
74
75        let result = Stack::call(pid, StackMessage::Pop, None)
76            .await
77            .expect("Stack call failed!");
78
79        tracing::info!("{:?}", result);
80
81        // Otherwise, the application will run forever waiting for Stack to terminate.
82        Stack::stop(pid, ExitReason::Normal, None).await?;
83
84        Ok(pid)
85    }
examples/registry.rs (line 67)
58    async fn start(&self) -> Result<Pid, ExitReason> {
59        // Spawn a registry that will take care of registering 'MySpace'.
60        let children = [
61            Registry::new("space-registry")
62                .with_start(|key| {
63                    let RegistryKey::String(id) = key else {
64                        panic!()
65                    };
66
67                    MySpace::new(id).start_link(GenServerOptions::new())
68                })
69                .with_shutdown(Shutdown::Infinity)
70                .child_spec(RegistryOptions::new())
71                .id("space-registry"),
72            ChildSpec::new("test-registry")
73                .start(move || async { Ok(Process::spawn(test_registry())) }),
74        ];
75
76        // Restart only the terminated child.
77        Supervisor::with_children(children)
78            .strategy(SupervisionStrategy::OneForOne)
79            .start_link(SupervisorOptions::new())
80            .await
81    }
Source

fn stop<T: Into<Dest>>( server: T, reason: ExitReason, timeout: Option<Duration>, ) -> impl Future<Output = Result<(), ExitReason>>

Synchronously stops the server with the given reason.

The terminate callback of the given server will be invoked before exiting. This function returns an error if the process exits with a reason other than the given reason.

The default timeout is infinity.

Examples found in repository?
examples/application.rs (line 82)
61    async fn start(&self) -> Result<Pid, ExitReason> {
62        let pid = Stack::with_entries(vec!["hello", "world"])
63            .start_link(GenServerOptions::new())
64            .await
65            .expect("Failed to start stack!");
66
67        let result = Stack::call(pid, StackMessage::Pop, None)
68            .await
69            .expect("Stack call failed!");
70
71        tracing::info!("{:?}", result);
72
73        Stack::cast(pid, StackMessage::Push(String::from("rust")));
74
75        let result = Stack::call(pid, StackMessage::Pop, None)
76            .await
77            .expect("Stack call failed!");
78
79        tracing::info!("{:?}", result);
80
81        // Otherwise, the application will run forever waiting for Stack to terminate.
82        Stack::stop(pid, ExitReason::Normal, None).await?;
83
84        Ok(pid)
85    }
Source

fn cast<T: Into<Dests>>(servers: T, message: Self::Message)

Casts a request to the servers without waiting for a response.

It is unknown whether the destination server successfully handled the request.

See Process::send for performance trade-offs.

Examples found in repository?
examples/supervisor.rs (line 89)
74    async fn init(&mut self) -> Result<(), ExitReason> {
75        let server = Process::current();
76
77        Process::spawn(async move {
78            // Ask for a formatted string.
79            let hello_world = MyServer::hello(server, "hello")
80                .await
81                .expect("Failed to call server!");
82
83            tracing::info!("Got: {:?}", hello_world);
84
85            // Wait before crashing.
86            Process::sleep(Duration::from_secs(1)).await;
87
88            // Crash the process so the supervisor restarts it.
89            MyServer::cast(server, MyMessage::Crash);
90        });
91
92        Ok(())
93    }
More examples
Hide additional examples
examples/application.rs (line 73)
61    async fn start(&self) -> Result<Pid, ExitReason> {
62        let pid = Stack::with_entries(vec!["hello", "world"])
63            .start_link(GenServerOptions::new())
64            .await
65            .expect("Failed to start stack!");
66
67        let result = Stack::call(pid, StackMessage::Pop, None)
68            .await
69            .expect("Stack call failed!");
70
71        tracing::info!("{:?}", result);
72
73        Stack::cast(pid, StackMessage::Push(String::from("rust")));
74
75        let result = Stack::call(pid, StackMessage::Pop, None)
76            .await
77            .expect("Stack call failed!");
78
79        tracing::info!("{:?}", result);
80
81        // Otherwise, the application will run forever waiting for Stack to terminate.
82        Stack::stop(pid, ExitReason::Normal, None).await?;
83
84        Ok(pid)
85    }
Source

fn cast_after<T: Into<Dests>>( servers: T, message: Self::Message, duration: Duration, ) -> Reference

Casts a request to the servers after the given duration without waiting for a response.

It is unknown whether the destination server successfully handled the request.

See Process::send for performance trade-offs.

Source

fn call<T: Into<Dest>>( server: T, message: Self::Message, timeout: Option<Duration>, ) -> impl Future<Output = Result<Self::Message, CallError>> + Send

Makes a synchronous call to the server and waits for it’s reply.

The client sends the given message to the server and waits until a reply arrives or a timeout occurs. handle_call will be called on the server to handle the request.

The default timeout is 5000ms.

Examples found in repository?
examples/supervisor.rs (line 58)
55    pub async fn hello<T: Into<Dest>>(server: T, string: &str) -> Result<String, CallError> {
56        use MyMessage::*;
57
58        match MyServer::call(server, Hello(string.to_owned()), None).await? {
59            HelloResponse(response) => Ok(response),
60            _ => unreachable!(),
61        }
62    }
More examples
Hide additional examples
examples/application.rs (line 67)
61    async fn start(&self) -> Result<Pid, ExitReason> {
62        let pid = Stack::with_entries(vec!["hello", "world"])
63            .start_link(GenServerOptions::new())
64            .await
65            .expect("Failed to start stack!");
66
67        let result = Stack::call(pid, StackMessage::Pop, None)
68            .await
69            .expect("Stack call failed!");
70
71        tracing::info!("{:?}", result);
72
73        Stack::cast(pid, StackMessage::Push(String::from("rust")));
74
75        let result = Stack::call(pid, StackMessage::Pop, None)
76            .await
77            .expect("Stack call failed!");
78
79        tracing::info!("{:?}", result);
80
81        // Otherwise, the application will run forever waiting for Stack to terminate.
82        Stack::stop(pid, ExitReason::Normal, None).await?;
83
84        Ok(pid)
85    }
Source

fn reply(from: From, message: Self::Message)

Replies to a client.

This function can be used to explicitly send a reply to a client that called call when the reply cannot be specified in the return value of handle_call.

client must be the from argument accepted by handle_call callbacks.

Note that reply can be called from any process, not just the GenServer that originally received the call (as long as the GenServer communicated the from argument somehow).

Source

fn terminate(&mut self, reason: ExitReason) -> impl Future<Output = ()> + Send

Invoked when the server is about to exit. It should do any cleanup required.

terminate is useful for cleanup that requires access to the GenServer’s state. However, it is not guaranteed that terminate is called when a GenServer exits. Therefore, important cleanup should be done using process links and/or monitors. A monitoring process will receive the same reason that would be passed to terminate.

terminate is called if:

  • The GenServer traps exits (using Process::flags) and the parent process sends an exit signal.
  • A callback (except init) returns stop with a given reason.
  • The stop method is called on a GenServer.
Source

fn handle_cast( &mut self, message: Self::Message, ) -> impl Future<Output = Result<(), ExitReason>> + Send

Invoked to handle asynchronous cast messages.

Source

fn handle_info( &mut self, info: Message<Self::Message>, ) -> impl Future<Output = Result<(), ExitReason>> + Send

Invoked to handle all other messages.

Source

fn handle_call( &mut self, message: Self::Message, from: From, ) -> impl Future<Output = Result<Option<Self::Message>, ExitReason>> + Send

Invoked to handle synchronous call messages. call will block until a reply is received (unless the call times out or nodes are disconnected).

from is a struct containing the callers Pid and a Reference that uniquely identifies the call.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§

Source§

impl GenServer for Registry

Source§

type Message = RegistryMessage

Source§

impl GenServer for Supervisor

Source§

type Message = SupervisorMessage