-
Notifications
You must be signed in to change notification settings - Fork 282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add Remote
middleware
#575
base: master
Are you sure you want to change the base?
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
Buffer
that 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