0

I want to initialize thread local variables for all 4 threads at the beginning of the program.

thread_local! {
  static local: i32
}

#[tokio::main(worker_threads = 4)]
async fn main() {
   // local = get_local().await;
}

2
  • async tasks and thread-local storage don't usually play nicely together when you're using a multi-threaded executor (via tokio::main) where tasks can be moved between threads. What is the end goal here? Your thread-locals will already be initialized if you use the macro properly. Commented Nov 9, 2022 at 19:41
  • The end goal is to put some Tonic grpc client in thread_local so that I don't need to initialize the client every time. But I guess it's still possible to get a general solution? I have a fixed number of threads here, and my "real" program starts after initialization finishes. Commented Nov 9, 2022 at 19:52

1 Answer 1

1

Your tokio runtime is configured to have 4 worker threads, your thread local is provided to the main thread but not to the worker threads.

If you intend to initialize the gRPC client just once, a OnceCell could be appropriate:

use once_cell::sync::OnceCell;

pub static CLIENT: OnceCell<hello_world::greeter_client::GreeterClient<tonic::transport::Channel>> =
    OnceCell::new();

pub fn client() -> hello_world::greeter_client::GreeterClient<tonic::transport::Channel> {
    CLIENT.get().unwrap().clone()
}

#[tokio::main]
async fn main() {
    let channel = tonic::transport::Endpoint::new("http://helloworld")
        .unwrap()
        .connect_lazy();
    let client = hello_world::greeter_client::GreeterClient::new(channel);
    CLIENT.set(client).unwrap();
    main_().await;
}

async fn main_() {
    let _ = client()
        .say_hello(hello_world::HelloRequest { name: "foo".into() })
        .await;
}

pub mod hello_world {
    tonic::include_proto!("helloworld");
}

If you want to stick to something more similar to a thread local or you need more control over the client values, then you can use tokio's task local. It allows you to provide context to tasks, but keep in mind that tokio::spawn introduces new tasks, so this context is lost when you use tokio::spawn.

The following snippet makes a tonic client available through a client() helper function that internally calls .with() on the task local. This function panics if the task local is not set, there is also try_with() which returns a Result if the value is not provided.

use tokio::task_local;

task_local! {
    pub static CLIENT: hello_world::greeter_client::GreeterClient<tonic::transport::Channel>
}

pub fn client() -> hello_world::greeter_client::GreeterClient<tonic::transport::Channel> {
    CLIENT.with(|c| c.clone())
}

#[tokio::main]
async fn main() {
    let channel = tonic::transport::Endpoint::new("http://helloworld")
        .unwrap()
        .connect_lazy();
    let client = hello_world::greeter_client::GreeterClient::new(channel);
    CLIENT.scope(client, main_()).await;
}

async fn main_() {
    let _ = client()
        .say_hello(hello_world::HelloRequest { name: "foo".into() })
        .await;
}

pub mod hello_world {
    tonic::include_proto!("helloworld");
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.