-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
how to implement stream r/w in parallel? #1108
Comments
I've ran into this as well. So far, I've written this (very ugly) workaround for a non-locking split: #[derive(Debug)]
pub struct TcpStreamRecv(Arc<UnsafeCell<TcpStream>>);
unsafe impl Send for TcpStreamRecv {}
unsafe impl Sync for TcpStreamRecv {}
impl TcpStreamRecv {
pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
unsafe { &*self.0.get() }.shutdown(how)
}
}
impl io::Read for TcpStreamRecv {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
unsafe { &mut *self.0.get() }.read(buf)
}
}
impl AsyncRead for TcpStreamRecv {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
(&*self.0.get()).prepare_uninitialized_buffer(buf)
}
fn read_buf<B: bytes::BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
unsafe { &mut *self.0.get() }.read_buf(buf)
}
}
#[derive(Debug)]
pub struct TcpStreamSend(Arc<UnsafeCell<TcpStream>>);
unsafe impl Send for TcpStreamSend {}
unsafe impl Sync for TcpStreamSend {}
impl TcpStreamSend {
pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
unsafe { &*self.0.get() }.shutdown(how)
}
}
impl io::Write for TcpStreamSend {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
unsafe { &mut *self.0.get() }.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
unsafe { &mut *self.0.get() }.flush()
}
}
impl AsyncWrite for TcpStreamSend {
fn shutdown(&mut self) -> Poll<(), io::Error> {
unsafe { &mut *self.0.get() }.shutdown()
}
fn write_buf<B: bytes::Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
unsafe { &mut *self.0.get() }.write_buf(buf)
}
}
pub fn tcp_split(stream: TcpStream) -> (TcpStreamSend, TcpStreamRecv) {
let inner = Arc::new(UnsafeCell::new(stream));
let send = TcpStreamSend(inner.clone());
let recv = TcpStreamRecv(inner);
(send, recv)
} Like I said, it's ugly, but I don't think it can be much prettier without some sort of explicit support... |
This cannot be implement in We might have a full-duplex rustls implementation? @ctz |
@quininer afaik this is fixed in tokio in master, so you shouldn't use custom hacks and just wait for the next relesae I think? :) |
Non-locking splits should be possible even w/ tokio-rustls using the |
Yes, but we still have to use a lock for the |
@quininer why do you need a lock? |
I want Ideally, the |
The generic |
Yes, I want to implement this optimization for |
It is possible to implement a zero cost |
How would that work for TLS streams here? The two handles can have two |
There might be a bit of misunderstanding here. The generic |
I don't think this is true, otherwise you can write and read a I didn't find generic split implementation in |
I think right now in tokio there is only split for concrete IO types (like TcpStream), no generic split lock. |
A "blocked"
|
I think this is half duplex. You can call it to full duplex emulation. Anyway, I don't want to struggle with definitions. I want to be able to write in another thread while reading in one thread, and they won't block each other. |
Current IMO, the real concurrency is that we could run Is there any idea here for the tokio developers? |
在2019年10月7日十月 下午5:54,jinhua luo写道:
Current `split` is no locking (https://github.com/tokio-rs/tokio/blob/master/tokio-net/src/tcp/split.rs), but it depends on the r/w methods are orthogonal to each other and do not cause any data race on inner members, and all methods use immutable reference to `TcpStream`.
However, `ReadHalf` and `WriteHalf` only works in the same spawn task with `TcpStream`, because they contain references to `Stream`.
That's how the current `proxy` example works (https://github.com/tokio-rs/tokio/blob/master/tokio/examples/proxy.rs).
IMO, the real concurrency is that we could run `tokio::spawn` twice, and put the same `TcpStream` into them, that means it must be `Sync`. In that way, the r/w could run in parallel using two threads.
Is there any idea here for the tokio developers?
My original design would allow exactly this: each half takes an Arc and have static lifetime.
|
@sopium Then why not |
It replaced by |
But that still uses mutual locking. |
To me it's questionable that we even need the |
@sopium Ok, interesting. I think #1307 is the (most) relevant one, esp Carl's comment:
So, if I read that right, that means that, for example in epoll, the userdata field in On idea that comes to mind is to not map to |
Now I think no need to clone anything, since the r/w methods are orthogonal and the low level Check my patch on the split: And here is my example app (socks5 server) to use parallel r/w: Note that I spawn two tasks (may run on two threads) to do r/w. In fact, |
In |
@kingluo |
Yes, but it still needs
However, we could not focus only on the zero-cost on the |
@kingluo Sure, I'm not disagreeing and in fact the encode/decode thing is exactly what we do in the software we're using Tokio for :) The Arc-based split would still be a benefit for us too. Sill, I thought it would be interesting to consider a truly zero-cost solution... |
@sopium implementing AsyncRead / AsyncWrite on |
I understand it does not work with the current
(Or are you saying this will dead lock?) For streams it's trickier because they need to implement I'm not saying that tokio should implement it this way. Just want to discuss maybe some alternative API designs. |
@carllerche would you accept a PR introducing a It seems like an easy way to have both the zero-cost wrapper and the It’s unfortunate that we cannot move the two halves in separate tasks right now. |
Just checking, is this the same situation for windows named pipes? E.g. splitting |
Note that #2270 has been merged. |
Depends on if there are other streams that could be split, I guess? After digging into the named pipe issue I was hitting (write blocks waiting for read), it wouldn't help to avoid the mutex on splitting |
I guess it does. We still need to make the split methods more consistent, but that has to wait for v0.3 due to backwards compatibility. |
As known, due to the ownership, we could not r/w the stream at the same time.
The
split
in tokio seems like a faked split, because it uses mutex for r/w.Although we do not block on syscall, which is just the goal of async programming, but the syscall itself is locked by that mutex, i.e. when the
read()
syscall is processing, we could not dowrite()
syscall. That seems very silly, with obvious performance impact. At syscall level, the read and write could be in parallel, which is meaningful for full-duplex app protocols (e.g. http 1.1 with pipe-lining and http2),Could we do a real split? Like we could clone
mio::net::TcpStream
(https://docs.rs/mio/0.6.18/mio/net/struct.TcpStream.html#method.try_clone), but sharing the registration and other stuff? That way, we do have real zero cost abstraction.The text was updated successfully, but these errors were encountered: