Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,95 @@
|
||||
//! 仅定位成功收发的同步成本,不作为完整资源合同的 Rust 参照。
|
||||
|
||||
use futures_util::{Sink, Stream};
|
||||
use std::{
|
||||
future::{Future, poll_fn},
|
||||
pin::Pin,
|
||||
sync::Mutex,
|
||||
task::{Context, Poll, ready},
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tungstenite::{
|
||||
WebSocketStream,
|
||||
tungstenite::{Error, Message},
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub trait Transport: AsyncRead + AsyncWrite + Unpin + Send {}
|
||||
impl<Value: AsyncRead + AsyncWrite + Unpin + Send> Transport for Value {}
|
||||
pub type Boxed = Box<dyn Transport>;
|
||||
type Wire = WebSocketStream<Boxed>;
|
||||
|
||||
pub struct Shared<const CANCEL: bool> {
|
||||
wire: Mutex<Wire>,
|
||||
read: tokio::sync::Mutex<()>,
|
||||
write: tokio::sync::Mutex<()>,
|
||||
closed: CancellationToken,
|
||||
closing: CancellationToken,
|
||||
}
|
||||
|
||||
impl<const CANCEL: bool> Shared<CANCEL> {
|
||||
pub fn new(wire: Wire) -> Self {
|
||||
Self {
|
||||
wire: Mutex::new(wire),
|
||||
read: tokio::sync::Mutex::new(()),
|
||||
write: tokio::sync::Mutex::new(()),
|
||||
closed: CancellationToken::new(),
|
||||
closing: CancellationToken::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll<Value>(
|
||||
&self,
|
||||
context: &mut Context<'_>,
|
||||
call: impl FnOnce(Pin<&mut Wire>, &mut Context<'_>) -> Poll<Value>,
|
||||
) -> Poll<Value> {
|
||||
let mut wire = self.wire.lock().unwrap();
|
||||
call(Pin::new(&mut *wire), context)
|
||||
}
|
||||
|
||||
async fn active<Value>(
|
||||
&self,
|
||||
work: impl Future<Output = Result<Value, Error>>,
|
||||
) -> Result<Value, Error> {
|
||||
if CANCEL {
|
||||
tokio::select! {
|
||||
biased;
|
||||
() = self.closed.cancelled() => Err(Error::ConnectionClosed),
|
||||
() = self.closing.cancelled() => Err(Error::ConnectionClosed),
|
||||
result = work => result,
|
||||
}
|
||||
} else {
|
||||
work.await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send(&self, frame: Message) -> Result<(), Error> {
|
||||
self.active(async {
|
||||
let _direction = self.write.lock().await;
|
||||
let mut frame = Some(frame);
|
||||
poll_fn(|context| {
|
||||
self.poll(context, |mut wire, context| {
|
||||
ready!(wire.as_mut().poll_ready(context))?;
|
||||
Poll::Ready(wire.start_send(frame.take().unwrap()))
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
poll_fn(|context| self.poll(context, |wire, context| wire.poll_flush(context))).await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn receive(&self) -> Result<Option<Message>, Error> {
|
||||
self.active(async {
|
||||
let _direction = self.read.lock().await;
|
||||
poll_fn(|context| self.poll(context, |wire, context| wire.poll_next(context)))
|
||||
.await
|
||||
.transpose()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn close(&mut self) {
|
||||
self.wire.get_mut().unwrap().close(None).await.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// 对照真实发送的生成适配层;双方共用运行库,分别记录发送与完整收发,逐字节校验在计时外。
|
||||
use dever_runtime::{
|
||||
http::websocket::{self as ws, Message, WebSocket},
|
||||
task::{Limits, Runtime},
|
||||
};
|
||||
use std::{hint::black_box, time::Instant};
|
||||
|
||||
async fn send(generated: bool, borrowed: bool, socket: &WebSocket, message: &MESSAGE_TYPE) {
|
||||
if generated && borrowed {
|
||||
WS_SEND_MESSAGE(socket, message).await.unwrap();
|
||||
return;
|
||||
}
|
||||
match message {
|
||||
MESSAGE_TYPE::Variant0(text) if generated => WS_SEND_TEXT(socket, text).await.unwrap(),
|
||||
MESSAGE_TYPE::Variant1(bytes) if generated => WS_SEND_BINARY(socket, bytes).await.unwrap(),
|
||||
MESSAGE_TYPE::Variant0(text) => socket.send(Message::Text(text.clone())).await.unwrap(),
|
||||
MESSAGE_TYPE::Variant1(bytes) => socket.send(Message::Binary(bytes.clone())).await.unwrap(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn payload_size(message: &MESSAGE_TYPE) -> usize {
|
||||
match message {
|
||||
MESSAGE_TYPE::Variant0(text) => text.len(),
|
||||
MESSAGE_TYPE::Variant1(bytes) => bytes.len(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn check(received: Option<Message>, expected: &MESSAGE_TYPE) {
|
||||
match (received, expected) {
|
||||
(Some(Message::Text(actual)), MESSAGE_TYPE::Variant0(expected)) => {
|
||||
assert_eq!(&actual, expected)
|
||||
}
|
||||
(Some(Message::Binary(actual)), MESSAGE_TYPE::Variant1(expected)) => {
|
||||
assert_eq!(&actual, expected)
|
||||
}
|
||||
_ => panic!("WebSocket message changed"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn ready(socket: &WebSocket, input: &MESSAGE_TYPE, count: usize) {
|
||||
socket
|
||||
.send(Message::Text(format!("roundtrip:{count}")))
|
||||
.await
|
||||
.unwrap();
|
||||
send(false, false, socket, input).await;
|
||||
assert_eq!(
|
||||
socket.receive().await.unwrap(),
|
||||
Some(Message::Text("ready".to_owned()))
|
||||
);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if std::env::args().any(|argument| argument == "--peer") {
|
||||
peer::run();
|
||||
return;
|
||||
}
|
||||
let mut peer = peer::Peer::start(1);
|
||||
let runtime = Runtime::new(Limits {
|
||||
threads: 1,
|
||||
workers: 1,
|
||||
tasks: 128,
|
||||
})
|
||||
.unwrap();
|
||||
println!("case,sample,implementation,elapsed_ns,checksum");
|
||||
runtime.run(async |scope| {
|
||||
// 双方轮流使用同一连接;固定协议对端在另一 CPU,隔离连接差异与线程争抢。
|
||||
let socket = ws::connect("127.0.0.1", i64::from(peer.port), "/bench", &ws::Limits::default(), scope).await.unwrap();
|
||||
for bytes in [64, 65536] {
|
||||
for binary in [false, true] {
|
||||
let input = if binary {
|
||||
MESSAGE_TYPE::Variant1((0..bytes).map(|index| (index % 251) as u8).collect::<Vec<_>>().into())
|
||||
} else {
|
||||
MESSAGE_TYPE::Variant0("雪a".repeat(bytes / 4))
|
||||
};
|
||||
for borrowed in [false, true] {
|
||||
let name = format!("ws_{}_{}_{}", if binary { "binary" } else { "text" }, if borrowed { "borrowed" } else { "temporary" }, bytes);
|
||||
for generated in [true, false] {
|
||||
ready(&socket, &input, 20).await;
|
||||
for _ in 0..20 {
|
||||
send(generated, borrowed, &socket, &input).await;
|
||||
check(socket.receive().await.unwrap(), &input);
|
||||
}
|
||||
}
|
||||
for sample in 0..15 {
|
||||
let mut order = [("dever", true), ("rust_runtime", false), ("control", true)];
|
||||
order.rotate_left(sample % 3);
|
||||
for (implementation, generated) in order {
|
||||
ready(&socket, &input, 200).await;
|
||||
let mut sent = 0;
|
||||
let mut roundtrip = 0;
|
||||
let mut checksum = 0;
|
||||
for _ in 0..200 {
|
||||
let start = Instant::now();
|
||||
send(generated, borrowed, &socket, black_box(&input)).await;
|
||||
sent += start.elapsed().as_nanos();
|
||||
let received = socket.receive().await.unwrap();
|
||||
roundtrip += start.elapsed().as_nanos();
|
||||
check(received, &input);
|
||||
checksum += payload_size(black_box(&input));
|
||||
}
|
||||
assert_eq!(checksum, bytes * 200);
|
||||
println!("{name}_send,{sample},{implementation},{sent},{checksum}");
|
||||
println!("{name}_roundtrip,{sample},{implementation},{roundtrip},{checksum}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
socket.close(1000, "").await.unwrap();
|
||||
}).unwrap();
|
||||
peer.finish();
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// 独立协议对端,不调用 Dever HTTP/WebSocket;绑到另一个 CPU,避免双方争抢工作线程。
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use std::{
|
||||
io::{BufRead, BufReader, Write},
|
||||
process::{Child, Command, Stdio},
|
||||
};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_tungstenite::{
|
||||
WebSocketStream,
|
||||
tungstenite::{Message, protocol::WebSocketConfig},
|
||||
};
|
||||
|
||||
pub fn config() -> WebSocketConfig {
|
||||
WebSocketConfig::default()
|
||||
.read_buffer_size(4096)
|
||||
.write_buffer_size(0)
|
||||
.max_write_buffer_size(1048576 + 4096)
|
||||
.max_message_size(Some(1048576))
|
||||
.max_frame_size(Some(1048576))
|
||||
}
|
||||
|
||||
pub struct Peer {
|
||||
child: Child,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl Peer {
|
||||
pub fn start(connections: usize) -> Self {
|
||||
let cpu = std::env::var("DEVER_WS_PEER_CPU").expect("set a separate peer CPU");
|
||||
let executable = std::env::var_os("DEVER_WS_PEER_BINARY")
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| std::env::current_exe().unwrap());
|
||||
let mut child = Command::new("taskset")
|
||||
.args(["-c", &cpu])
|
||||
.arg(executable)
|
||||
.arg("--peer")
|
||||
.env("DEVER_WS_PEER_CONNECTIONS", connections.to_string())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let mut ready = String::new();
|
||||
BufReader::new(child.stdout.take().unwrap())
|
||||
.read_line(&mut ready)
|
||||
.unwrap();
|
||||
let port = ready.trim().parse().expect("peer did not report its port");
|
||||
Self { child, port }
|
||||
}
|
||||
|
||||
pub fn finish(&mut self) {
|
||||
assert!(self.child.wait().unwrap().success());
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Peer {
|
||||
fn drop(&mut self) {
|
||||
// 断言失败也回收本次测试创建的子进程。
|
||||
let _ = self.child.kill();
|
||||
let _ = self.child.wait();
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve(mut socket: WebSocketStream<TcpStream>) {
|
||||
while let Some(command) = socket.next().await {
|
||||
let command = command.unwrap();
|
||||
if command.is_close() {
|
||||
socket.flush().await.unwrap();
|
||||
return;
|
||||
}
|
||||
let (mode, count) = command.to_text().unwrap().split_once(':').unwrap();
|
||||
let count: usize = count.parse().unwrap();
|
||||
assert!(mode == "batch" || mode == "roundtrip");
|
||||
let expected = socket.next().await.unwrap().unwrap();
|
||||
socket.send(Message::Text("ready".into())).await.unwrap();
|
||||
for _ in 0..count {
|
||||
let actual = socket.next().await.unwrap().unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
if mode == "roundtrip" {
|
||||
socket.send(actual).await.unwrap();
|
||||
}
|
||||
}
|
||||
if mode == "batch" {
|
||||
socket.send(expected).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
let connections: usize = std::env::var("DEVER_WS_PEER_CONNECTIONS")
|
||||
.expect("set expected peer connections")
|
||||
.parse()
|
||||
.unwrap();
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
tokio::time::timeout(std::time::Duration::from_secs(120), async {
|
||||
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
|
||||
println!("{}", listener.local_addr().unwrap().port());
|
||||
std::io::stdout().flush().unwrap();
|
||||
let mut sessions = Vec::new();
|
||||
for _ in 0..connections {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
stream.set_nodelay(true).unwrap();
|
||||
sessions.push(tokio::spawn(async move {
|
||||
let socket =
|
||||
tokio_tungstenite::accept_async_with_config(stream, Some(config()))
|
||||
.await
|
||||
.unwrap();
|
||||
serve(socket).await;
|
||||
}));
|
||||
}
|
||||
for session in sessions {
|
||||
session.await.unwrap();
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("protocol peer timed out");
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
// 成功会话的独立协议参照。原共享运行库基准保留;这里不声称覆盖别名、取消和资源归属合同。
|
||||
use dever_runtime::{
|
||||
http::websocket::{self as ws, Message, WebSocket},
|
||||
task::{Limits, Runtime},
|
||||
};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use std::{
|
||||
hint::black_box,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{WebSocketStream, tungstenite::Message as Frame};
|
||||
|
||||
enum Client {
|
||||
Dever(WebSocket),
|
||||
Rust(WebSocketStream<TcpStream>),
|
||||
Boxed(WebSocketStream<layers::Boxed>),
|
||||
Locked(layers::Shared<false>),
|
||||
Cancelled(layers::Shared<true>),
|
||||
Shared {
|
||||
socket: shared::Socket,
|
||||
_owner: shared::Owner,
|
||||
},
|
||||
}
|
||||
|
||||
impl Client {
|
||||
async fn send(&mut self, value: &MESSAGE_TYPE) {
|
||||
if let Self::Dever(socket) = self {
|
||||
match value {
|
||||
MESSAGE_TYPE::Variant0(text) => WS_SEND_TEXT(socket, text).await.unwrap(),
|
||||
MESSAGE_TYPE::Variant1(bytes) => WS_SEND_BINARY(socket, bytes).await.unwrap(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 所有参照采用同样的借用输入、拥有消息及写期限。
|
||||
let frame = match value {
|
||||
MESSAGE_TYPE::Variant0(text) => Frame::Text(text.clone().into()),
|
||||
MESSAGE_TYPE::Variant1(bytes) => Frame::Binary(bytes.clone()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if let Self::Shared { socket, .. } = self {
|
||||
socket.send(frame).await.unwrap();
|
||||
return;
|
||||
}
|
||||
assert!(frame.len() <= 1048576);
|
||||
tokio::time::timeout(Duration::from_millis(5000), async {
|
||||
match self {
|
||||
Self::Rust(socket) => socket.send(frame).await,
|
||||
Self::Boxed(socket) => socket.send(frame).await,
|
||||
Self::Locked(socket) => socket.send(frame).await,
|
||||
Self::Cancelled(socket) => socket.send(frame).await,
|
||||
Self::Dever(_) => unreachable!(),
|
||||
Self::Shared { .. } => unreachable!(),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn receive(&mut self) -> Message {
|
||||
if let Self::Dever(socket) = self {
|
||||
return socket.receive().await.unwrap().unwrap();
|
||||
}
|
||||
let frame = if let Self::Shared { socket, .. } = self {
|
||||
socket.receive().await.unwrap().unwrap()
|
||||
} else {
|
||||
tokio::time::timeout(Duration::from_millis(60000), async {
|
||||
match self {
|
||||
Self::Rust(socket) => socket.next().await.transpose(),
|
||||
Self::Boxed(socket) => socket.next().await.transpose(),
|
||||
Self::Locked(socket) => socket.receive().await,
|
||||
Self::Cancelled(socket) => socket.receive().await,
|
||||
Self::Dever(_) => unreachable!(),
|
||||
Self::Shared { .. } => unreachable!(),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
};
|
||||
match frame {
|
||||
Frame::Text(value) => Message::Text(value.to_string()),
|
||||
Frame::Binary(value) => Message::Binary(value),
|
||||
_ => panic!("unexpected protocol frame"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn close(&mut self) {
|
||||
match self {
|
||||
Self::Dever(socket) => socket.close(1000, "").await.unwrap(),
|
||||
Self::Rust(socket) => socket.close(None).await.unwrap(),
|
||||
Self::Boxed(socket) => socket.close(None).await.unwrap(),
|
||||
Self::Locked(socket) => socket.close().await,
|
||||
Self::Cancelled(socket) => socket.close().await,
|
||||
Self::Shared { socket, .. } => socket.close(1000, "").await.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check(actual: Message, expected: &MESSAGE_TYPE) {
|
||||
match (actual, expected) {
|
||||
(Message::Text(actual), MESSAGE_TYPE::Variant0(expected)) => assert_eq!(&actual, expected),
|
||||
(Message::Binary(actual), MESSAGE_TYPE::Variant1(expected)) => {
|
||||
assert_eq!(&actual, expected)
|
||||
}
|
||||
_ => panic!("payload changed"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn measure(client: &mut Client, input: &MESSAGE_TYPE, mode: &str, count: usize) -> u128 {
|
||||
client
|
||||
.send(&MESSAGE_TYPE::Variant0(format!("{mode}:{count}")))
|
||||
.await;
|
||||
client.send(input).await;
|
||||
check(
|
||||
client.receive().await,
|
||||
&MESSAGE_TYPE::Variant0("ready".to_owned()),
|
||||
);
|
||||
if mode == "batch" {
|
||||
// 批量发送后单独收确认,避免把每条消息的回声等待计入发送成本。
|
||||
let start = Instant::now();
|
||||
for _ in 0..count {
|
||||
client.send(black_box(input)).await;
|
||||
}
|
||||
let elapsed = start.elapsed().as_nanos();
|
||||
check(client.receive().await, input);
|
||||
elapsed
|
||||
} else {
|
||||
let mut elapsed = 0;
|
||||
for _ in 0..count {
|
||||
let start = Instant::now();
|
||||
client.send(black_box(input)).await;
|
||||
let actual = client.receive().await;
|
||||
elapsed += start.elapsed().as_nanos();
|
||||
check(actual, input);
|
||||
}
|
||||
elapsed
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if std::env::args().any(|argument| argument == "--peer") {
|
||||
peer::run();
|
||||
return;
|
||||
}
|
||||
let mut peer = peer::Peer::start(2);
|
||||
let reference = std::env::var("DEVER_WS_REFERENCE").unwrap_or_else(|_| "raw".to_owned());
|
||||
let label = match reference.as_str() {
|
||||
"raw" => "rust_protocol",
|
||||
"boxed" => "rust_boxed_diagnostic",
|
||||
"locked" => "rust_locked_diagnostic",
|
||||
"cancelled" => "rust_cancelled_diagnostic",
|
||||
"shared" => "rust_shared",
|
||||
_ => panic!("unknown WebSocket reference"),
|
||||
};
|
||||
let runtime = Runtime::new(Limits {
|
||||
threads: 1,
|
||||
workers: 1,
|
||||
tasks: 128,
|
||||
})
|
||||
.unwrap();
|
||||
println!("case,sample,implementation,elapsed_ns,checksum");
|
||||
runtime
|
||||
.run(async |scope| {
|
||||
tokio::time::timeout(Duration::from_secs(120), async {
|
||||
let socket = ws::connect(
|
||||
"127.0.0.1",
|
||||
i64::from(peer.port),
|
||||
"/bench",
|
||||
&ws::Limits::default(),
|
||||
scope,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let stream = TcpStream::connect(("127.0.0.1", peer.port)).await.unwrap();
|
||||
stream.set_nodelay(true).unwrap();
|
||||
let url = format!("ws://127.0.0.1:{}/bench", peer.port);
|
||||
let control = if reference == "raw" || reference == "shared" {
|
||||
let (wire, _) = tokio_tungstenite::client_async_with_config(
|
||||
url,
|
||||
stream,
|
||||
Some(peer::config()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
if reference == "shared" {
|
||||
let (socket, owner) = shared::Socket::new(
|
||||
wire,
|
||||
shared::Limits {
|
||||
message: 1048576,
|
||||
idle: Duration::from_millis(60000),
|
||||
write: Duration::from_millis(5000),
|
||||
},
|
||||
);
|
||||
Client::Shared {
|
||||
socket,
|
||||
_owner: owner,
|
||||
}
|
||||
} else {
|
||||
Client::Rust(wire)
|
||||
}
|
||||
} else {
|
||||
let stream: layers::Boxed = Box::new(stream);
|
||||
let (wire, _) = tokio_tungstenite::client_async_with_config(
|
||||
url,
|
||||
stream,
|
||||
Some(peer::config()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
match reference.as_str() {
|
||||
"boxed" => Client::Boxed(wire),
|
||||
"locked" => Client::Locked(layers::Shared::new(wire)),
|
||||
"cancelled" => Client::Cancelled(layers::Shared::new(wire)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
let mut clients = [Client::Dever(socket), control];
|
||||
for bytes in [64, 65536] {
|
||||
for binary in [false, true] {
|
||||
let input = if binary {
|
||||
MESSAGE_TYPE::Variant1(
|
||||
(0..bytes)
|
||||
.map(|index| (index % 251) as u8)
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
MESSAGE_TYPE::Variant0("雪a".repeat(bytes / 4))
|
||||
};
|
||||
for mode in ["batch", "roundtrip"] {
|
||||
let name = format!(
|
||||
"ws_protocol_{}_{bytes}_{mode}",
|
||||
if binary { "binary" } else { "text" }
|
||||
);
|
||||
for client in &mut clients {
|
||||
black_box(measure(client, &input, mode, 20).await);
|
||||
}
|
||||
for sample in 0..15 {
|
||||
let mut order = [("dever", 0), (label, 1), ("control", 0)];
|
||||
order.rotate_left(sample % 3);
|
||||
if sample / 3 % 2 == 1 {
|
||||
order.swap(0, 1);
|
||||
}
|
||||
for (implementation, index) in order {
|
||||
let elapsed =
|
||||
measure(&mut clients[index], &input, mode, 200).await;
|
||||
println!(
|
||||
"{name},{sample},{implementation},{elapsed},{}",
|
||||
bytes * 200
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for client in &mut clients {
|
||||
client.close().await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("protocol comparison timed out");
|
||||
})
|
||||
.unwrap();
|
||||
peer.finish();
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// 独立 Rust 共享会话参照,不调用 Dever 运行库;覆盖收发、期限、关闭和所有权退出。
|
||||
// TCP/WebSocket 握手在计时外完成;此参照不代表 TLS、HTTP 升级或整个 Scope 系统。
|
||||
mod wire;
|
||||
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
future::Future,
|
||||
sync::{
|
||||
Arc, Mutex,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{net::TcpStream, sync::Mutex as Direction};
|
||||
use tokio_tungstenite::{
|
||||
WebSocketStream,
|
||||
tungstenite::{
|
||||
Message,
|
||||
protocol::{CloseFrame, frame::coding::CloseCode},
|
||||
},
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use wire::Wire;
|
||||
|
||||
pub type Error = Cow<'static, str>;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Limits {
|
||||
pub message: usize,
|
||||
pub idle: Duration,
|
||||
pub write: Duration,
|
||||
}
|
||||
|
||||
struct State {
|
||||
socket: Mutex<Option<WebSocketStream<TcpStream>>>,
|
||||
reader: Direction<()>,
|
||||
writer: Direction<()>,
|
||||
closing: CancellationToken,
|
||||
ended: AtomicBool,
|
||||
limits: Limits,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn abort(&self) {
|
||||
self.closing.cancel();
|
||||
self.socket.lock().unwrap().take();
|
||||
}
|
||||
|
||||
async fn active<Value>(
|
||||
&self,
|
||||
work: impl Future<Output = Result<Value, Error>>,
|
||||
) -> Result<Value, Error> {
|
||||
tokio::select! {
|
||||
biased;
|
||||
() = self.closing.cancelled() => Err("WebSocket is closing".into()),
|
||||
result = work => result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 所有者退出同步释放协议连接;保留别名不能延长连接生命。
|
||||
pub struct Owner(Arc<State>);
|
||||
impl Drop for Owner {
|
||||
fn drop(&mut self) {
|
||||
self.0.abort();
|
||||
}
|
||||
}
|
||||
|
||||
struct Writing<'socket>(Option<&'socket State>);
|
||||
impl Drop for Writing<'_> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(state) = self.0 {
|
||||
state.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Socket(Arc<State>);
|
||||
|
||||
impl Socket {
|
||||
pub fn new(socket: WebSocketStream<TcpStream>, limits: Limits) -> (Self, Owner) {
|
||||
let state = Arc::new(State {
|
||||
socket: Mutex::new(Some(socket)),
|
||||
reader: Direction::new(()),
|
||||
writer: Direction::new(()),
|
||||
closing: CancellationToken::new(),
|
||||
ended: AtomicBool::new(false),
|
||||
limits,
|
||||
});
|
||||
(Self(state.clone()), Owner(state))
|
||||
}
|
||||
|
||||
pub async fn send(&self, message: Message) -> Result<(), Error> {
|
||||
let limit = match message {
|
||||
Message::Text(_) | Message::Binary(_) => self.0.limits.message,
|
||||
Message::Ping(_) | Message::Pong(_) => 125,
|
||||
_ => return Err("use close for the closing handshake".into()),
|
||||
};
|
||||
if message.len() > limit {
|
||||
return Err("WebSocket message exceeds byte limit".into());
|
||||
}
|
||||
self.0
|
||||
.active(async {
|
||||
tokio::time::timeout(self.0.limits.write, async {
|
||||
let _writer = self.0.writer.lock().await;
|
||||
let mut writing = Writing(Some(&self.0));
|
||||
Wire(&self.0.socket).send(message).await.map_err(failure)?;
|
||||
writing.0 = None;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.map_err(|_| Error::from("WebSocket write timed out"))?
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn receive(&self) -> Result<Option<Message>, Error> {
|
||||
if self.0.ended.load(Ordering::Acquire) {
|
||||
return Ok(None);
|
||||
}
|
||||
let result = self
|
||||
.0
|
||||
.active(async {
|
||||
tokio::time::timeout(self.0.limits.idle, async {
|
||||
let _reader = self.0.reader.lock().await;
|
||||
if self.0.ended.load(Ordering::Acquire) {
|
||||
return Ok(None);
|
||||
}
|
||||
let message = Wire(&self.0.socket)
|
||||
.next()
|
||||
.await
|
||||
.transpose()
|
||||
.map_err(failure)?;
|
||||
match message {
|
||||
Some(Message::Ping(_)) => {
|
||||
self.reply().await?;
|
||||
Ok(message)
|
||||
}
|
||||
Some(Message::Close(_)) | None => {
|
||||
let _closing = Writing(Some(&self.0));
|
||||
self.reply().await?;
|
||||
self.0.ended.store(true, Ordering::Release);
|
||||
Ok(None)
|
||||
}
|
||||
_ => Ok(message),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| Error::from("WebSocket receive timed out"))?
|
||||
})
|
||||
.await;
|
||||
if result.is_err() && !self.0.closing.is_cancelled() {
|
||||
self.0.abort();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
async fn reply(&self) -> Result<(), Error> {
|
||||
let _writer = self.0.writer.lock().await;
|
||||
let mut writing = Writing(Some(&self.0));
|
||||
tokio::time::timeout(self.0.limits.write, Wire(&self.0.socket).flush())
|
||||
.await
|
||||
.map_err(|_| Error::from("WebSocket reply timed out"))?
|
||||
.map_err(failure)?;
|
||||
writing.0 = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn close(&self, code: u16, reason: &str) -> Result<(), Error> {
|
||||
let code = CloseCode::from(code);
|
||||
if !code.is_allowed() || reason.len() > 123 {
|
||||
return Err("invalid WebSocket close frame".into());
|
||||
}
|
||||
if self.0.closing.is_cancelled() {
|
||||
return Err("WebSocket is closed".into());
|
||||
}
|
||||
self.0.closing.cancel();
|
||||
let _closing = Writing(Some(&self.0));
|
||||
tokio::time::timeout(self.0.limits.write, async {
|
||||
let writer = self.0.writer.lock().await;
|
||||
Wire(&self.0.socket)
|
||||
.send(Message::Close(Some(CloseFrame {
|
||||
code,
|
||||
reason: reason.to_owned().into(),
|
||||
})))
|
||||
.await
|
||||
.map_err(failure)?;
|
||||
drop(writer);
|
||||
let _reader = self.0.reader.lock().await;
|
||||
loop {
|
||||
let message = Wire(&self.0.socket)
|
||||
.next()
|
||||
.await
|
||||
.transpose()
|
||||
.map_err(failure)?;
|
||||
Wire(&self.0.socket).flush().await.map_err(failure)?;
|
||||
if matches!(message, Some(Message::Close(_)) | None) {
|
||||
self.0.ended.store(true, Ordering::Release);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| Error::from("WebSocket close timed out"))?
|
||||
}
|
||||
}
|
||||
|
||||
fn failure(error: tokio_tungstenite::tungstenite::Error) -> Error {
|
||||
error.to_string().into()
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use futures_util::{Sink, Stream};
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::Mutex,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{
|
||||
WebSocketStream,
|
||||
tungstenite::{Error, Message},
|
||||
};
|
||||
|
||||
// 共享引用仅在一次协议调用内借用连接;send/next 的状态机由 futures-util 提供。
|
||||
pub(super) struct Wire<'socket>(pub &'socket Mutex<Option<WebSocketStream<TcpStream>>>);
|
||||
|
||||
impl Wire<'_> {
|
||||
fn with<Value>(
|
||||
&self,
|
||||
call: impl FnOnce(Pin<&mut WebSocketStream<TcpStream>>) -> Value,
|
||||
) -> Result<Value, Error> {
|
||||
let mut socket = self.0.lock().unwrap();
|
||||
socket
|
||||
.as_mut()
|
||||
.map(|socket| call(Pin::new(socket)))
|
||||
.ok_or(Error::ConnectionClosed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink<Message> for Wire<'_> {
|
||||
type Error = Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.with(|socket| socket.poll_ready(context))?
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, message: Message) -> Result<(), Error> {
|
||||
self.with(|socket| socket.start_send(message))?
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.with(|socket| socket.poll_flush(context))?
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.with(|socket| socket.poll_close(context))?
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Wire<'_> {
|
||||
type Item = Result<Message, Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match self.with(|socket| socket.poll_next(context)) {
|
||||
Ok(result) => result,
|
||||
Err(error) => Poll::Ready(Some(Err(error))),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user