2

I'm creating a basic Minecraft server in Rust. As soon as a player logs in to my server, I need to send a KeepAlive packet every 20 seconds to ensure the client is still connected.

I thought this would be solved by spawning a new thread that sends the packet after sleeping 20 seconds. (line 35). Unfortunately, the Server type doesn't implement Copy.

Error

error[E0382]: borrow of moved value: `server`
  --> src/main.rs:22:9
   |
20 |     let mut server = Server::from_tcpstream(stream).unwrap();
   |         ---------- move occurs because `server` has type `ozelot::server::Server`, which does not implement the `Copy` trait
21 |     'listenloop: loop {
22 |         server.update_inbuf().unwrap();
   |         ^^^^^^ value borrowed here after move
...
35 |                     thread::spawn(move || {
   |                                   ------- value moved into closure here, in previous iteration of loop

Code:

use std::thread;
use std::io::Read;
use std::fs::File;
use std::time::Duration;
use std::io::BufReader;
use ozelot::{Server, clientbound, ClientState, utils, read};
use ozelot::serverbound::ServerboundPacket;

use std::net::{TcpListener, TcpStream};

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("0.0.0.0:25565")?;
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

fn handle_client(stream: TcpStream) {
    let mut server = Server::from_tcpstream(stream).unwrap();
    'listenloop: loop {
        server.update_inbuf().unwrap();
        let packets = server.read_packet().unwrap_or_default();

        'packetloop: for packet in packets {

            match packet {

                //...
                ServerboundPacket::LoginStart(ref p) => {

                    //...

                    //send keep_alive every 20 seconds
                    thread::spawn(move || {
                        thread::sleep(Duration::from_secs(20));
                        send_keep_alive(server)
                    });

                    fn send_keep_alive(mut server: Server) {

                        server.send(clientbound::KeepAlive::new(728)).unwrap();

                    }

                    //...

                },
                //...
                _ => {},

            }
        }
        thread::sleep(Duration::from_millis(50));
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • See also [What do I have to do to solve a “use of moved value” error?](https://stackoverflow.com/q/28800121/155423); [How can I solve “use of moved value” and “which does not implement the `Copy` trait”?](https://stackoverflow.com/q/49309847/155423); – Shepmaster May 18 '20 at 19:28
  • Thank you for the information. I just replaced the error message. –  May 18 '20 at 19:32
  • 1
    [Option Moved in Previous Loop Iteration](https://stackoverflow.com/q/54993331/155423); [Rust tells ''value moved here, in previous iteration of loop"](https://stackoverflow.com/q/58740016/155423); [How to get ownership of a moved value back from a closure?](https://stackoverflow.com/q/47646256/155423); [How to prevent a value from being moved?](https://stackoverflow.com/q/41664099/155423) – Shepmaster May 18 '20 at 19:33

1 Answers1

1

Notice how most of the functions in ozelot::Server take &mut self as a parameter. This means that they are called on a mutable reference to a ozelot::Server.

From Rust docs:

you can only have one mutable reference to a particular piece of data in a particular scope

The issue is - how can we share ozelot::Server between the main thread and the new thread?

A lot of things in std::sync module solve exactly these kinds of problems. In your case, you want read access from the main thread and write access from the thread calling send_keep_alive. This calls for Mutex - exclusive read/write access at a time, and


Wrap the server in an Arc<Mutex>:

use std::sync::{Arc, Mutex};

// --snip--

fn handle_client(stream: TcpStream) {
    let mut server = Server::from_tcpstream(stream).unwrap();
    let mut server = Arc::new(Mutex::new(server));

Move a clone of Arc into the thread, and lock the Mutex to gain access:

let server = Arc::clone(server);
thread::spawn(move || {
    thread::sleep(Duration::from_secs(20));
    send_keep_alive(&mut server)
});

And make send_keep_alive receive server by mutable reference:

fn send_keep_alive(server: &mut Arc<Mutex<Server>>) {
    loop {
        let mut server = server.lock().unwrap();
        server.send(clientbound::KeepAlive::new(728)).unwrap();
    }
}
Oleksii Filonenko
  • 1,551
  • 1
  • 17
  • 27
  • 1
    thanks for your help I think that's exactly what I was looking for. Just one issue I'm facing here: I can't listen to other packets once I'm inside the keep_alive loop. This has probably something to do with the .lock() method. [Current Code](https://gist.github.com/ImuustMINE/0df1f1292b66c4b75575435791937fbf) –  May 19 '20 at 13:12
  • 1
    Move `server.lock().unwrap()` from `thread::spawn` to `loop` in `send_keep_alive`, and update the type signature of `send_keep_alive` to use `Arc>` instead of `server`. This will ensure that server will get unlocked between sending keepalive packets. I updated code in the answer. – Oleksii Filonenko May 22 '20 at 16:35