How can I access database pool outside of Rocket context? - rust

I'm using the recommended rocket_db_pools crate to create a database pool and attach it to Rocket e.g.
#[derive(Database)]
#[database("birthdays")]
struct DB(sqlx::SqlitePool);
// in main
rocket::build()
.attach(DB::init())
I would like to access the same database pool outside of the context of Rocket. I'm attempting to spawn a separate tokio task that runs alongside Rocket and does some additional background work e.g.
async fn main() -> _ {
rocket::tokio::spawn(async {
// access DB pool here
})
rocket::build()
// ...
}
Is there a way to initialize a sqlx::SqlitePool and easily provide it to both the background task and Rocket so I can still leverage the automatic #[derive(Database)] goodies that rocket_db_pools provides?
As a relative rust beginner I'm having a hard time reading through the traits and understanding if there is a way to do so that doesn't require doing it fully custom by creating a pool, writing my own impl FromRequest, etc.

I found an alternative based on the example from rocket_sync_db_pools which I adapted for the differences with rocket_db_pools.
It uses a fairing to get access to the Database after it has been initialized and then clones the wrapped SqlitePool to move into the task.
#[derive(Database)]
#[database("birthdays")]
struct DB(sqlx::SqlitePool);
// in main
rocket::build()
.attach(DB::init())
.attach(AdHoc::try_on_ignite("Background job", |rocket| async {
let pool = match DB::fetch(&rocket) {
Some(pool) => pool.0.clone(), // clone the wrapped pool
None => return Err(rocket),
};
rocket::tokio::task::spawn(async move {
loop {
if let Ok(mut conn) = pool.acquire().await {
let results = sqlx::query_as::<_, Birthday>(
"SELECT name, birthday, account_id FROM birthdays",
)
.fetch_all(&mut conn)
.await
.expect("query should succeed");
debug!("selected from birthdays: {:?}", results);
}
sleep(Duration::from_secs(10)).await;
}
});
Ok(rocket)
}))

Related

Trouble passing mpsc sync channel in warp handler

I'm new to rust and encountered an issue while building an API with warp. I'm trying to pass some requests to another thread with a channel(trying to avoid using arc/mutex). Still, I noticed that when I pass an mpsc::sync::Sender to a warp handler, I get this error.
"std::sync::mpsc::Sender cannot be shared between threads safely"
and
"the trait Sync is not implemented for `std::sync::mpsc::Sender"
Can someone lead me in the right direction?
use std::sync::mpsc::Sender;
pub async fn init_server(run_tx: Sender<Packet>) {
let store = Store::new();
let store_filter = warp::any().map(move || store.clone());
let run_tx_filter = warp::any().map(move || run_tx.clone());
let update_item = warp::get()
.and(warp::path("v1"))
.and(warp::path("auth"))
.and(warp::path::end())
.and(post_json())
.and(store_filter.clone())
.and(run_tx_filter.clone()) //where I'm trying to send "Sender"
.and_then(request_token);
let routes = update_item;
println!("HTTP server started on port 8080");
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
pub async fn request_token(
req: TokenRequest,
store: Store,
run_tx: Sender<Packet>,
) -> Result<impl warp::Reply, warp::Rejection> {
let (tmp_tx, tmp_rx) = std::sync::mpsc::channel();
run_tx
.send(Packet::IsPlayerLoggedIn(req.address, tmp_tx))
.unwrap();
let logged_in = tmp_rx.recv().unwrap();
if logged_in {
return Ok(warp::reply::with_status(
"Already logged in",
http::StatusCode::BAD_REQUEST,
));
}
Ok(warp::reply::with_status("some token", http::StatusCode::OK))
}
I've looked through some of the examples for warp, and was also wondering what are some good resources to get knowledgable of the crate. Thank you!
This is because you're using std::sync::mpsc::Sender. std::sync implements !Sync, so you won't be able to use that. You don't want to use that anyway since it's blocking.
When you use async functionality in rust, you need to provide a runtime for it. The good news is that warp runs on the tokio runtime, so you should have access to tokio::sync::mpsc If you take a look at that link, the Sender for that mpsc implementation implements Sync and Send so it's safe to share across threads.
TLDR:
Use tokio::sync::mpsc instead of std::sync::mpsc and this won't be an issue.

How do I spawn a long running Tokio task within another task without blocking the parent task?

I'm trying to construct an object that can manage a feed from a websocket but be able to switch between multiple feeds.
There is a Feed trait:
trait Feed {
async fn start(&mut self);
async fn stop(&mut self);
}
There are three structs that implement Feed: A, B, and C.
When start is called, it starts an infinite loop of listening for messages from a websocket and processing each one as it comes in.
I want to implement a FeedManager that maintains a single active feed but can receive commands to switch what feed source it is using.
enum FeedCommand {
Start(String),
Stop,
}
struct FeedManager {
active_feed_handle: tokio::task::JoinHandle,
controller: mpsc::Receiver<FeedCommand>,
}
impl FeedManager {
async fn start(&self) {
while let Some(command) = self.controller.recv().await {
match command {
FeedCommand::Start(feed_type) => {
// somehow tell the active feed to stop (need channel probably) or kill the task?
if feed_type == "A" {
// replace active feed task with a new tokio task for consuming feed A
} else if feed_type == "B" {
// replace active feed task with a new tokio task for consuming feed B
} else {
// replace active feed task with a new tokio task for consuming feed C
}
}
}
}
}
}
I'm struggling to understand how to manage all the of Tokio tasks properly. FeedManager's core loop is to listen forever for new commands that come in, but it needs to be able to spawn another long lived task without blocking on it (so it can listen for commands).
My first attempt was:
if feed_type == "A" {
self.active_feed_handle = tokio::spawn(async {
A::new().start().await;
});
self.active_feed_handle.await
}
the .await on the handle would cause the core loop to no longer accept commands, right?
can I omit that last .await and have the task still run?
do I need to clean up the currently active task some way?
You can I spawn a long running Tokio task without blocking the parent task by spawning a task — that's a primary reason tasks exist. If you don't .await the task, then you won't wait for the task:
use std::time::Duration;
use tokio::{task, time}; // 1.3.0
#[tokio::main]
async fn main() {
task::spawn(async {
time::sleep(Duration::from_secs(100)).await;
eprintln!(
"You'll likely never see this printed \
out because the parent task has exited \
and so has the entire program"
);
});
}
See also:
What happens to an async task when it is aborted?
One way to do this would be to use Tokio's join!() macro, which takes multiple futures and awaits on all of them. You could the create multiple futures and join!() them together to await on them collectively.

Polling many futures of different types

I'm trying to understand how to implement polling multiple futures with different types. For context, I'm calling an API that will return something like:
[{"type": "source_a", "id": 123}, {"type": "source_b", "id": 234}, ...]
Each type in the API response requires a call to another API, with each API returning different data types. The code I've written works something like this:
async fn get_data(sources: Vec<Source>) -> Data {
let mut data = Default::default();
for source in sources {
if source.kind == "source_a" {
let source_data = get_source_a(source).await;
process_source_a(source_data, &mut data);
} else if source.kind == "source_b" {
...
}
}
data
}
This won't run concurrently, it will simply fetch sources one at a time and process them. How can I rewrite this so that each source is fetched concurrently and then processed once data is available? Speaking Rustily, I think what I want is to execute a closure that mutably borrows data when the future is ready. Should I be looking at something like an Arc<RefCell<Data>>?
To process the futures in parallel, you need to await something like join_all, which will run them concurrently and return when they are all done. For this to work, you have to resolve two issues:
join_all requires futures of the same type, so you need to box them or otherwise unify them.
data needs to be accessed by multiple async blocks, so it needs to be protected by Arc and Mutex.
The first issue can be solved simply by spawning the async fns as tasks, which has the added advantage of potentially running them in parallel (in addition to them being run concurrently). The example below uses tokio::spawn, but it would be almost exactly the same for async_std. Since there is no reproducible example, I can't test the code, but it could look like this:
async fn get_data(sources: Vec<Source>) -> Data {
let data = Arc::new(Mutex::new(Data::default()));
let mut tasks = vec![];
for source in sources {
if source.kind == "source_a" {
let data = Arc::clone(&data);
tasks.push(tokio::task::spawn(async move {
let source_data = get_source_a(source).await;
process_source_a(source_data, &mut data.lock().unwrap());
}));
} else if source.kind == "source_b" {
// ...
}
}
// Wait for all sources to finish and propagate the panic if any.
// With async_std this wouldn't require the `for_each()`.
futures::future::join_all(tasks)
.await
.for_each(|x| x.unwrap());
// As all tasks are done, there should be no references to `data` at
// this point, so we can extract it out of the Arc<Mutex<_>> wrapping.
data.try_unwrap().unwrap().into_inner()
}

How to use thread local storage for tracing events?

This code uses tracing events:
# Cargo.toml
[dependencies]
tracing = "0.1.3"
tracing-subscriber = { version = "0.2.9", features = ["chrono", "env-filter", "fmt"] }
tracing-appender = "0.1.1"
use tracing::{Level, event, };
use tracing::dispatcher::{with_default, Dispatch};
use std::thread;
use tracing_appender::rolling::{RollingFileAppender};
use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
use tracing_subscriber::fmt::SubscriberBuilder;
pub static file_appender:RollingFileAppender = tracing_appender::rolling::never("/ws/sarvi-sjc/", "prefix.log");
pub static (non_blocking, _guard:WorkerGuard):(NonBlocking:WorkerGuard) = tracing_appender::non_blocking(file_appender);
pub static subscriber:SubscriberBuilder = tracing_subscriber::FmtSubscriber::builder()
.with_max_level(Level::TRACE)
.with_writer(non_blocking)
.finish();
pub static my_dispatch = Dispatch::new(subscriber);
with_default(&my_dispatch, || {
event!(Level::INFO, "chmod(...)");
});
I want the first global static lines to be initialized and stored in a thread_local!() so that it is initialized only once for each thread.
I should then be able to use that thread-specific Dispatch instance to scope event subscribers. The above code is taken from inside a function and were let statements. As static variables, one them doesn't work, and had the same problem within thread_local!() as well.
pub static (non_blocking, _guard:WorkerGuard):(NonBlocking:WorkerGuard) = tracing_appender::non_blocking(file_appender);
error: expected identifier, found `(`
--> src/lib.rs:13:12
|
13 | pub static (non_blocking, _guard:WorkerGuard):(NonBlocking:WorkerGuard) = tracing_appender::non_blocking(file_appender);
| ^ expected identifier
The second problem was understanding how they are initialized in a thread local fashion.
Wrap your static declarations with the thread_local! macro, then you can access each value using the with method, which will return a value unique to the thread.
use tracing::{
dispatcher::{with_default, Dispatch},
event, Level,
};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::FmtSubscriber;
fn make_dispatch() -> (Dispatch, WorkerGuard) {
let file_appender = tracing_appender::rolling::never("ws/sarvi-sjc/", "prefix.log");
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::TRACE)
.with_writer(non_blocking)
.finish();
(Dispatch::new(subscriber), guard)
}
thread_local!(static MY_DISPATCH: (Dispatch, WorkerGuard) = make_dispatch());
fn main() {
// Main thread:
let (my_dispatch, _guard) = make_dispatch();
with_default(&my_dispatch, || {
event!(Level::INFO, "main thread");
});
// Other thread:
std::thread::spawn(|| {
MY_DISPATCH.with(|(my_dispatch, _guard)| {
with_default(&my_dispatch, || {
event!(Level::INFO, "other thread");
});
});
})
.join()
.unwrap();
}
I made sure to store the WorkerGuard in thread local storage too, so that it does not go out of scope after MY_DISPATCH is initialized. This is because the documentation for tracing_appender::non_blocking states:
This function returns a tuple of NonBlocking and WorkerGuard. NonBlocking implements MakeWriter which integrates with tracing_subscriber. WorkerGuard is a drop guard that is responsible for flushing any remaining logs when the program terminates.
Note that the WorkerGuard returned by non_blocking must be assigned to a binding that is not _, as _ will result in the WorkerGuard being dropped immediately. Unintentional drops of WorkerGuard remove the guarantee that logs will be flushed during a program's termination, in a panic or otherwise.
This way, the guard will be dropped when the thread exits. However, keep in mind that Rust's built-in thread local storage has some weird quirks about initialization and destruction. See the documentation for std::thread::LocalKey. Notably:
On Unix systems when pthread-based TLS is being used, destructors will not be run for TLS values on the main thread when it exits. Note that the application will exit immediately after the main thread exits as well.
Therefore, on the main thread, you should call make_dispatch() directly rather than MY_DISPATCH, so that it is dropped when the program exits (but note that in general, things are not guaranteed to be dropped, especially during a panic or std::process::exit, etc.; therefore, there is still a chance that some logs could be lost, as is the nature of most non-blocking I/O).

How can I share or avoid sharing a websocket resource between two threads?

I am using tungstenite to build a chat server, and the way I want to do it relies on having many threads that communicate with each other through mpsc. I want to start up a new thread for each user that connects to the server and connect them to a websocket, and also have that thread be able to read from mpsc so that the server can send messages out through that connection.
The problem is that the mpsc read blocks the thread, but I can't block the thread if I want to be reading from it. The only thing I could think of to work around that is to make two threads, one for inbound and one for outbound messages, but that requires me to share my websocket connection with both workers, which of course I cannot do.
Here's a heavily truncated version of my code where I try to make two workers in the Action::Connect arm of the match statement, which gives error[E0382]: use of moved value: 'websocket' for trying to move it into the second worker's closure:
extern crate tungstenite;
extern crate workerpool;
use std::net::{TcpListener, TcpStream};
use std::sync::mpsc::{self, Sender, Receiver};
use workerpool::Pool;
use workerpool::thunk::{Thunk, ThunkWorker};
use tungstenite::server::accept;
pub enum Action {
Connect(TcpStream),
Send(String),
}
fn main() {
let (main_send, main_receive): (Sender<Action>, Receiver<Action>) = mpsc::channel();
let worker_pool = Pool::<ThunkWorker<()>>::new(8);
{
// spawn thread to listen for users connecting to the server
let main_send = main_send.clone();
worker_pool.execute(Thunk::of(move || {
let listener = TcpListener::bind(format!("127.0.0.1:{}", 8080)).unwrap();
for (_, stream) in listener.incoming().enumerate() {
main_send.send(Action::Connect(stream.unwrap())).unwrap();
}
}));
}
let mut users: Vec<Sender<String>> = Vec::new();
// process actions from children
while let Some(act) = main_receive.recv().ok() {
match act {
Action::Connect(stream) => {
let mut websocket = accept(stream).unwrap();
let (user_send, user_receive): (Sender<String>, Receiver<String>) = mpsc::channel();
let main_send = main_send.clone();
// thread to read user input and propagate it to the server
worker_pool.execute(Thunk::of(move || {
loop {
let message = websocket.read_message().unwrap().to_string();
main_send.send(Action::Send(message)).unwrap();
}
}));
// thread to take server output and propagate it to the server
worker_pool.execute(Thunk::of(move || {
while let Some(message) = user_receive.recv().ok() {
websocket.write_message(tungstenite::Message::Text(message.clone())).unwrap();
}
}));
users.push(user_send);
}
Action::Send(message) => {
// take user message and echo to all users
for user in &users {
user.send(message.clone()).unwrap();
}
}
}
}
}
If I create just one thread for both in and output in that arm, then user_receive.recv() blocks the thread so I can't read any messages with websocket.read_message() until I get an mpsc message from the main thread. How can I solve both problems? I considered cloning the websocket but it doesn't implement Clone and I don't know if just making a new connection with the same stream is a reasonable thing to try to do, it seems hacky.
The problem is that the mpsc read blocks the thread
You can use try_recv to avoid thread blocking. The another implementation of mpsc is crossbeam_channel. That project is a recommended replacement even by the author of mpsc
I want to start up a new thread for each user that connects to the server
I think the asyn/await approach will be much better from most of the prospectives then thread per client one. You can read more about it there

Resources