2
\$\begingroup\$

In order to learn rust and concurrency , im trying to share a common hashmap between actix web server and a parallel thread

  • Theres one thread which updates a hashmap with the current timestamp every 10 seconds
  • theres a GET API which returns the latest timestamp provided in the hashmap

heres my code

use actix_web::{web, App, HttpServer};
use std::collections::HashMap;
use chrono::prelude::*;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

async fn get_latest_timestamp(data: web::Data<Arc<Mutex<HashMap<String, String>>>>) -> String {
    
    let wrapped_data = data.lock().unwrap();

    match wrapped_data.get("time_now") {
        Some(i) => String::from(i),
        None => String::from("None")
    }
}

fn update_timestamps(data: Arc<Mutex<HashMap<String, String>>>) -> () {

    loop {
        thread::sleep(Duration::from_secs(10));
        println!("Updating time.... ");
        let now: DateTime<Local> = Local::now();

        let mut data = data.lock().unwrap();
    
        data.insert(String::from("time_now"), now.to_rfc2822());
    }
}


#[actix_web::main]
async fn main() -> std::io::Result<()> {

    let mut hash = HashMap::new();
    hash.insert(String::from("time_now"), Local::now().to_rfc2822());

    let data = Arc::new(Mutex::new(hash));

    let hash_clone = Arc::clone(&data);
    thread::spawn(move || {
        update_timestamps(hash_clone)
    });

    HttpServer::new( move || {
        App::new()
            .data(data.clone())
            .route("/", web::get().to(get_latest_timestamp))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Is there anything i could do handle better. A couple of questions i do have

  1. How does one generally handle errors that occur on the parallel thread? is the main thread notified in someway?

  2. the code in update_timestamps runs in a continous loop - is there a better way to handle it so that it cleanly exits?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Answering your questions:

  1. Depends on the nature of the error.

Panics will make the thread exit. If the creator called join on the result of thread::spawn, the result of that join will be Err(boxed_any). The boxed_any is the argument given to panic!. This is one common way to signal and handle an error.

If the panic occurs while a mutex's lock is in scope, the mutex will be poisoned and therefore return Err for all locking attempts on all threads indefinitely.

In case you'd like to notify another thread about an event, let's say, an occurence of a meaningful error, it's best to use channels. You can read more in "Using Message Passing to Transfer Data Between Threads": https://doc.rust-lang.org/book/ch16-02-message-passing.html

  1. I'm not sure what you meant by exiting cleanly. You need the thread to run indefinitely. The thread will exit "cleanly" when the server is closed (and so when the main thread exits).

Minor points:

  • calls to Arc::clone can be simply .clone(). Quoting the docs:
// The two syntaxes below are equivalent.
let a = foo.clone();
let b = Arc::clone(&foo);
  • the second (and last) data.clone() is unnecessary. This is often seen in code. Just pass the value.

You still have ownership of that original value. That original value can be spent by passing it to actix.

  • -> () in a function signature is never meaningful; just cut it out.
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Personally, I prefer Arc::clone to .clone(), to stress the fact that we are calling a method of Arc and not going through Deref, and also for consistency with other associated functions of Arc, which aren't methods for the same reason. \$\endgroup\$
    – L. F.
    Commented Feb 12, 2021 at 2:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.