Closed NielDuysters closed 7 months ago
My actual code is a bit more complex using async and different threads:
use futures::{stream, StreamExt};
use futures::{FutureExt, TryStreamExt};
use std::env;
use tokio_postgres::{connect, NoTls};
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
// PostgreSQL connection.
let (client, mut connection) = tokio_postgres::connect("host=localhost user=postgres dbname=", NoTls).await.unwrap();
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let (tx, rx) = futures_channel::mpsc::unbounded();
let stream =
stream::poll_fn(move |cx| connection.poll_message(cx)).map_err(|e| panic!("{}", e));
let connection = stream.forward(tx).map(|r| r.unwrap());
tokio::spawn(connection);
client
.batch_execute(
"LISTEN test_notifications;
NOTIFY test_notifications, 'hello';
NOTIFY test_notifications, 'world';",
)
.await
.unwrap();
drop(client);
let notifications = rx
.filter_map(|m| match m {
tokio_postgres::AsyncMessage::Notification(n) => futures_util::future::ready(Some(n)),
_ => futures_util::future::ready(None),
})
.collect::<Vec<_>>()
.await;
}
Error:
error[E0382]: use of moved value: `connection`
--> src/algotester.rs:24:25
|
12 | let (client, mut connection) = tokio_postgres::con...
| -------------- move occurs because `connection` has type `Connection<Socket, NoTlsStream>`, which does not implement the `Copy` trait
...
15 | tokio::spawn(async move {
| __________________-
16 | | if let Err(e) = connection.await {
| | ---------- variable moved due to use in generator
17 | | eprintln!("connection error: {}", e);
18 | | }
19 | | });
| |_____- value moved here
...
24 | stream::poll_fn(move |cx| connection.poll_mess...
| ^^^^^^^^^ ---------- use occurs due to use in closure
| |
| value used here after move
I tried using Arc and Mutex but when I use tokio's Mutex I can't .await
the lock because stream::poll_fn
is sync.
Look at the example I linked. You need to remove
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
The problem is that if I don't await the connection my other code won't work (it compiles but the queries won't run) because I use that connection to query a lot of sql simultaneously and asynchronous: https://users.rust-lang.org/t/listen-for-psql-notification-using-tokio-postgres/105798/8
I think the best approach would be to create a new instance of the connection for the one function I need to listen for notifications. Is it possible to do that? Could I retrieve the connection parameters from the connection I am awaiting and create a new connection using those parameters?
connection.poll_message
also drives the connection. You do need to ensure that you are consuming the messages it writes to the channel though.
Could you elaborate a bit on what you mean exactly?
Can you show a self contained example of a some code where the queries don't run?
use futures::{stream, StreamExt};
use futures::{FutureExt, TryStreamExt};
use std::env;
use tokio_postgres::{connect, NoTls};
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
// PostgreSQL connection.
let (client, mut connection) = tokio_postgres::connect("host=localhost user=postgres dbname=", NoTls).await.unwrap();
let (tx, rx) = futures_channel::mpsc::unbounded();
let stream =
stream::poll_fn(move |cx| connection.poll_message(cx)).map_err(|e| panic!("{}", e));
let connection = stream.forward(tx).map(|r| r.unwrap());
tokio::spawn(connection);
client
.batch_execute(
"LISTEN test_notifications;
NOTIFY test_notifications, 'hello';
NOTIFY test_notifications, 'world';",
)
.await
.unwrap();
let notifications = rx
.filter_map(|m| match m {
tokio_postgres::AsyncMessage::Notification(n) => futures_util::future::ready(Some(n)),
_ => futures_util::future::ready(None),
})
.collect::<Vec<_>>()
.await;
// REST OF CODE
let query =
client
.query("
SELECT btc FROM history LIMIT 1
", &[]).await;
match query {
Ok(q) => {
let r = q[0].get::<_, &str>("btc");
println!("r {}", r);
},
Err(e) => {
return;
}
}
}
From my question here: https://users.rust-lang.org/t/listen-for-psql-notification-using-tokio-postgres/105798/6?u=oniel
Your .collect().await
call is only going to terminate when the connection dies. The function never progresses to the SELECT btc
portion.
Oh okay, so that is the part that stalls my code? Maybe I just need to run
let notifications = rx
.filter_map(|m| match m {
tokio_postgres::AsyncMessage::Notification(n) => futures_util::future::ready(Some(n)),
_ => futures_util::future::ready(None),
})
.collect::<Vec<_>>()
.await;
on a different thread using tokio::spawn?
What you do with the notifications is up to you.
I think I solved it thanks to your help:
use futures::{stream, StreamExt};
use futures::{FutureExt, TryStreamExt};
use std::env;
use tokio_postgres::{connect, NoTls};
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
// PostgreSQL connection.
let (client, mut connection) = tokio_postgres::connect("host=localhost user=postgres dbname=", NoTls).await.unwrap();
// Make transmitter and receiver.
let (tx, rx) = futures_channel::mpsc::unbounded();
let stream =
stream::poll_fn(move |cx| connection.poll_message(cx)).map_err(|e| panic!("{}", e));
let connection = stream.forward(tx).map(|r| r.unwrap());
tokio::spawn(connection);
// Wait for notifications in seperate thread.
tokio::spawn(async move {
let notifications = rx
.filter_map(|m| match m {
tokio_postgres::AsyncMessage::Notification(n) => {
println!("Notification {:?}", n);
futures_util::future::ready(Some(n))
},
_ => futures_util::future::ready(None),
})
.collect::<Vec<_>>().await;
// All notifications?
println!("All notifications {:?}", notifications);
});
// Execute listen/notify
match client
.batch_execute(
"LISTEN test_notifications;
NOTIFY test_notifications, 'hello';
NOTIFY test_notifications, 'world';",
)
.await
{
Ok(_) => (),
Err(e) => {
eprintln!("Error {}", e);
}
}
// Execute random query.
let query =
client
.query("
SELECT order_id FROM history LIMIT 1
", &[]).await;
match query {
Ok(q) => {
let r = q[0].get::<_, &str>("order_id");
println!("r {}", r);
},
Err(e) => {
return;
}
}
}
I am now able to receive notifications while also querying my database.
PostgreSQL has a feature NOTIFY/LISTEN which basically allows you to send a notification when a specific event happens in your database like for example a new row insert.
Now I want to be able in Rust using Tokio postgres to listen for a notification and output it. Tokio does seem to support it.
I tried multiple approaches.
FIRST APPROACH I tried the following example but it yields an error.
SECOND APPROACH Comes from this example.
How to listen for a Psql notification in Rust?
Crossposted: https://users.rust-lang.org/t/listen-for-psql-notification-using-tokio-postgres/105798