考虑以下代码:
use std::{cell::UnsafeCell, io, net::TcpStream, sync::Arc};
use native_tls::TlsStream;
#[derive(Debug)]
pub struct TcpStreamRecv(Arc<UnsafeCell<TlsStream<TcpStream>>>);
unsafe impl Send for TcpStreamRecv {}
unsafe impl Sync for TcpStreamRecv {}
impl io::Read for TcpStreamRecv {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
unsafe { &mut *self.0.get() }.read(buf)
}
}
#[derive(Debug)]
pub struct TcpStreamSend(Arc<UnsafeCell<TlsStream<TcpStream>>>);
unsafe impl Send for TcpStreamSend {}
unsafe impl Sync for TcpStreamSend {}
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()
}
}
pub fn tcp_split(stream: TlsStream<TcpStream>) -> (TcpStreamSend, TcpStreamRecv) {
let inner = Arc::new(UnsafeCell::new(stream));
let send = TcpStreamSend(inner.clone());
let recv = TcpStreamRecv(inner);
(send, recv)
}
我的推理如下:
安全代码可以获得具有相同底层 TlsStream 的 TcpStreamSend 和 TcpStreamRecv。
如果每个都发送到单独的线程,则可以同时调用
TcpStreamSend::write
和 TcpStreamRecv::read
。这些函数中的每一个都会获得对底层 TlsStream 的 &mut
引用。
因此,由于同时拥有 2 个可变引用是非法的,因此这段代码可能会导致 UB,应该被认为是不健全的。这是正确的吗?
我的同事向我保证“如果它有效,它就有效”,并且代码确实在大多数情况下都可以正常运行,除了一些随机的偶尔的恐慌之外。然而,根据我的理解,它可能会在我们的代码库中的任何地方导致不可预测的问题,应该立即重写。我错过了什么吗?
顺便说一句,我做了一些研究,代码似乎受到了这个SO答案的启发,它更复杂,但是,据我所知,同样糟糕。
绝对不健全。
在 Rust 中同时存在两个重叠的
&mut
引用是未定义的行为,如果两个线程同时调用 read
和 write
,就会发生这种情况。即使对于非线程代码来说这也是一个问题 - Rust 假设没有 &mut
引用可以别名。
在没有锁的情况下分割
TlsStream
是不可能的。即使 TcpStream
中的读取和写入实现是独立的,TlsStream
也可能需要根据来自服务器的消息更改其写入方式(例如重新生成密钥),这将要求读取器更改写入器使用的状态。