Conversation
|
I think this is kinda interesting. Its not a need I've had myself previously. However, I don't believe its always valid to pass things around to different runtimes 🤔 I know that some tokio IO resources (such as For example consider this example: use std::thread;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
use tokio::runtime::Builder;
use tokio::sync::mpsc;
fn main() {
// channel used to send things between runtimes
let (tx, mut rx) = mpsc::channel(10);
// create a runtime in a thread
let t1 = thread::spawn(move || {
Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
// create some IO resource and send it to another runtime
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
let (socket, _) = listener.accept().await.unwrap();
tx.send(socket).await.unwrap();
});
});
// create another runtime in another thread
let t2 = thread::spawn(move || {
Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async move {
// receive the socket created on the other runtime
let mut socket = rx.recv().await.unwrap();
// read the request
let mut buf = [0; 1014];
let n = socket.read(&mut buf).await.unwrap(); // <- this panics because the IO driver is gone
let request = String::from_utf8_lossy(&buf[..n]);
println!("{}", request);
// write a response
socket.write_all(b"HTTP/1.1 200 OK\r\n").await.unwrap();
socket.write_all(b"content-length: 0\r\n").await.unwrap();
socket.write_all(b"\r\n").await.unwrap();
});
});
t1.join().unwrap();
t2.join().unwrap();
}If you So one could imagine a Is that something you have considered or run into? |
|
Thank you for the feedback @davidpdrsn!
This is correct, yes. Using resources across runtimes requires special caution, but can work fine as long as the origin runtime is longer-lived than the "consuming" runtime.
In our case the origin runtime (the one we want to use Generally the plan for the |
|
I'm not convinced this needs to live in tower rather than a separate crate given the gotchas but I could probably be convinced otherwise 😊 Would like to hear what others think. cc @hawkw @LucioFranco @jonhoo |
|
I think a good guide here is to see whether others also come asking for this. If so, I think it would make a lot of sense. But I think it's too early to adopt it pro-actively. But, at least now we have a starting point for if that day comes! |
|
Am I going crazy but can't you just do this with |
That is essentially what this code does; it uses tower/tower/src/remote/service.rs Lines 65 to 67 in 049490b However, the other thing that this code does is wrapping the |
Hey! First of all, thank you for making tower! We've had great success using it.
In one of our applications we are running some parts of the code in single-threaded runtimes on dedicated threads. Those parts of the code periodically call a service that expects to be run on a multi-threaded runtime, however (e.g. by using
task::block_in_place), and so we've been seeing panics.This PR introduces a middleware that allows executing a service remotely on another runtime. It's a thin layer over
Bufferthat spawns the futures returned from the buffered service onto the service's executor, and then returns theJoinHandle(wrapped in a newtype) back to the user. The user then gets the result of running the service through theJoinHandle, potentially crossing runtime boundaries in the process.TODO: Tests, more docs