Hold C2ME byte permits through ME writer completion

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-07-10 16:35:39 +03:00
parent 2ac93c6d49
commit 893ce0cf36
5 changed files with 55 additions and 14 deletions
+1
View File
@@ -149,6 +149,7 @@ where
peer, peer,
translated_local_addr, translated_local_addr,
payload, payload,
_permit,
flags, flags,
effective_tag_array, effective_tag_array,
) )
+6 -1
View File
@@ -1,5 +1,6 @@
use bytes::Bytes; use bytes::Bytes;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::OwnedSemaphorePermit;
use crate::crypto::{AesCbc, crc32, crc32c}; use crate::crypto::{AesCbc, crc32, crc32c};
use crate::error::{ProxyError, Result}; use crate::error::{ProxyError, Result};
@@ -13,7 +14,10 @@ const RPC_WRITER_FRAME_BUF_RETAIN: usize = 64 * 1024;
/// Commands sent to dedicated writer tasks to avoid mutex contention on TCP writes. /// Commands sent to dedicated writer tasks to avoid mutex contention on TCP writes.
pub(crate) enum WriterCommand { pub(crate) enum WriterCommand {
Data(Bytes), Data {
payload: Bytes,
_permit: Option<OwnedSemaphorePermit>,
},
DataAndFlush(Bytes), DataAndFlush(Bytes),
ProxyReq(ProxyReqCommand), ProxyReq(ProxyReqCommand),
ControlAndFlush([u8; 12]), ControlAndFlush([u8; 12]),
@@ -28,6 +32,7 @@ pub(crate) struct ProxyReqCommand {
pub(crate) proto_flags: u32, pub(crate) proto_flags: u32,
pub(crate) proxy_tag: Option<[u8; 16]>, pub(crate) proxy_tag: Option<[u8; 16]>,
pub(crate) payload: PooledBuffer, pub(crate) payload: PooledBuffer,
pub(crate) _permit: OwnedSemaphorePermit,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+1 -1
View File
@@ -63,7 +63,7 @@ async fn writer_command_loop(
_ = cancel.cancelled() => return Ok(()), _ = cancel.cancelled() => return Ok(()),
cmd = rx.recv() => { cmd = rx.recv() => {
match cmd { match cmd {
Some(WriterCommand::Data(payload)) => { Some(WriterCommand::Data { payload, _permit }) => {
rpc_writer.send(&payload).await?; rpc_writer.send(&payload).await?;
} }
Some(WriterCommand::DataAndFlush(payload)) => { Some(WriterCommand::DataAndFlush(payload)) => {
+41 -10
View File
@@ -6,7 +6,7 @@ use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tokio::sync::mpsc; use tokio::sync::{OwnedSemaphorePermit, mpsc};
use tokio::sync::mpsc::error::TrySendError; use tokio::sync::mpsc::error::TrySendError;
use tracing::{debug, warn}; use tracing::{debug, warn};
@@ -43,9 +43,18 @@ fn proxy_tag_array(tag: Option<&[u8]>) -> Option<[u8; 16]> {
tag.and_then(|tag| <[u8; 16]>::try_from(tag).ok()) tag.and_then(|tag| <[u8; 16]>::try_from(tag).ok())
} }
fn proxy_req_payload_from_command(cmd: WriterCommand) -> Option<PooledBuffer> { fn proxy_req_payload_from_command(
cmd: WriterCommand,
) -> Option<(PooledBuffer, OwnedSemaphorePermit)> {
match cmd { match cmd {
WriterCommand::ProxyReq(command) => Some(command.payload), WriterCommand::ProxyReq(command) => Some((command.payload, command._permit)),
_ => None,
}
}
fn payload_permit_from_data_command(cmd: WriterCommand) -> Option<OwnedSemaphorePermit> {
match cmd {
WriterCommand::Data { _permit, .. } => _permit,
_ => None, _ => None,
} }
} }
@@ -67,6 +76,7 @@ async fn reserve_writer_command_slot(
impl MePool { impl MePool {
/// Send RPC_PROXY_REQ. `tag_override`: per-user ad_tag (from access.user_ad_tags); if None, uses pool default. /// Send RPC_PROXY_REQ. `tag_override`: per-user ad_tag (from access.user_ad_tags); if None, uses pool default.
/// `payload_permit` keeps optional client byte accounting alive until the writer consumes the command.
pub async fn send_proxy_req( pub async fn send_proxy_req(
self: &Arc<Self>, self: &Arc<Self>,
conn_id: u64, conn_id: u64,
@@ -76,6 +86,7 @@ impl MePool {
data: &[u8], data: &[u8],
proto_flags: u32, proto_flags: u32,
tag_override: Option<&[u8]>, tag_override: Option<&[u8]>,
mut payload_permit: Option<OwnedSemaphorePermit>,
) -> Result<()> { ) -> Result<()> {
let tag = tag_override.or(self.proxy_tag.as_deref()); let tag = tag_override.or(self.proxy_tag.as_deref());
let build_routed_payload = |effective_our_addr: SocketAddr| { let build_routed_payload = |effective_our_addr: SocketAddr| {
@@ -119,7 +130,11 @@ impl MePool {
if let Some((current, current_meta)) = self.registry.get_writer_with_meta(conn_id).await if let Some((current, current_meta)) = self.registry.get_writer_with_meta(conn_id).await
{ {
let (current_payload, _) = build_routed_payload(current_meta.our_addr); let (current_payload, _) = build_routed_payload(current_meta.our_addr);
match current.tx.try_send(WriterCommand::Data(current_payload)) { let command = WriterCommand::Data {
payload: current_payload,
_permit: payload_permit.take(),
};
match current.tx.try_send(command) {
Ok(()) => { Ok(()) => {
self.note_hybrid_route_success(); self.note_hybrid_route_success();
return Ok(()); return Ok(());
@@ -143,14 +158,17 @@ impl MePool {
"ME writer channel full within blocking send timeout".into(), "ME writer channel full within blocking send timeout".into(),
)); ));
} }
Err(WriterCommandReserveError::Closed) => {} Err(WriterCommandReserveError::Closed) => {
payload_permit = payload_permit_from_data_command(cmd);
}
} }
warn!(writer_id = current.writer_id, "ME writer channel closed"); warn!(writer_id = current.writer_id, "ME writer channel closed");
self.remove_writer_and_close_clients(current.writer_id) self.remove_writer_and_close_clients(current.writer_id)
.await; .await;
continue; continue;
} }
Err(TrySendError::Closed(_)) => { Err(TrySendError::Closed(cmd)) => {
payload_permit = payload_permit_from_data_command(cmd);
warn!(writer_id = current.writer_id, "ME writer channel closed"); warn!(writer_id = current.writer_id, "ME writer channel closed");
self.remove_writer_and_close_clients(current.writer_id) self.remove_writer_and_close_clients(current.writer_id)
.await; .await;
@@ -479,7 +497,10 @@ impl MePool {
self.remove_writer_and_close_clients(w.id).await; self.remove_writer_and_close_clients(w.id).await;
continue; continue;
} }
permit.send(WriterCommand::Data(payload)); permit.send(WriterCommand::Data {
payload,
_permit: payload_permit.take(),
});
self.stats self.stats
.increment_me_writer_pick_success_try_total(pick_mode); .increment_me_writer_pick_success_try_total(pick_mode);
if w.generation < self.current_generation() { if w.generation < self.current_generation() {
@@ -548,7 +569,10 @@ impl MePool {
self.remove_writer_and_close_clients(w.id).await; self.remove_writer_and_close_clients(w.id).await;
continue; continue;
} }
permit.send(WriterCommand::Data(payload)); permit.send(WriterCommand::Data {
payload,
_permit: payload_permit.take(),
});
self.stats self.stats
.increment_me_writer_pick_success_fallback_total(pick_mode); .increment_me_writer_pick_success_fallback_total(pick_mode);
if w.generation < self.current_generation() { if w.generation < self.current_generation() {
@@ -567,6 +591,7 @@ impl MePool {
} }
/// Send RPC_PROXY_REQ while keeping the first bound-writer path allocation-light. /// Send RPC_PROXY_REQ while keeping the first bound-writer path allocation-light.
/// The client byte permit follows the payload until writer completion or command drop.
pub async fn send_proxy_req_pooled( pub async fn send_proxy_req_pooled(
self: &Arc<Self>, self: &Arc<Self>,
conn_id: u64, conn_id: u64,
@@ -574,6 +599,7 @@ impl MePool {
client_addr: SocketAddr, client_addr: SocketAddr,
our_addr: SocketAddr, our_addr: SocketAddr,
payload: PooledBuffer, payload: PooledBuffer,
_permit: OwnedSemaphorePermit,
proto_flags: u32, proto_flags: u32,
tag_override: Option<[u8; 16]>, tag_override: Option<[u8; 16]>,
) -> Result<()> { ) -> Result<()> {
@@ -587,6 +613,7 @@ impl MePool {
proto_flags, proto_flags,
proxy_tag: tag, proxy_tag: tag,
payload, payload,
_permit,
}); });
match current.tx.try_send(command) { match current.tx.try_send(command) {
Ok(()) => { Ok(()) => {
@@ -613,7 +640,8 @@ impl MePool {
)); ));
} }
Err(WriterCommandReserveError::Closed) => { Err(WriterCommandReserveError::Closed) => {
let Some(payload) = proxy_req_payload_from_command(cmd) else { let Some((payload, _permit)) = proxy_req_payload_from_command(cmd)
else {
return Err(ProxyError::Proxy( return Err(ProxyError::Proxy(
"ME writer rejected unexpected command type".into(), "ME writer rejected unexpected command type".into(),
)); ));
@@ -630,13 +658,14 @@ impl MePool {
payload.as_ref(), payload.as_ref(),
proto_flags, proto_flags,
tag.as_ref().map(|tag| tag.as_slice()), tag.as_ref().map(|tag| tag.as_slice()),
Some(_permit),
) )
.await; .await;
} }
} }
} }
Err(TrySendError::Closed(cmd)) => { Err(TrySendError::Closed(cmd)) => {
let Some(payload) = proxy_req_payload_from_command(cmd) else { let Some((payload, _permit)) = proxy_req_payload_from_command(cmd) else {
return Err(ProxyError::Proxy( return Err(ProxyError::Proxy(
"ME writer rejected unexpected command type".into(), "ME writer rejected unexpected command type".into(),
)); ));
@@ -653,6 +682,7 @@ impl MePool {
payload.as_ref(), payload.as_ref(),
proto_flags, proto_flags,
tag.as_ref().map(|tag| tag.as_slice()), tag.as_ref().map(|tag| tag.as_slice()),
Some(_permit),
) )
.await; .await;
} }
@@ -667,6 +697,7 @@ impl MePool {
payload.as_ref(), payload.as_ref(),
proto_flags, proto_flags,
tag.as_ref().map(|tag| tag.as_slice()), tag.as_ref().map(|tag| tag.as_slice()),
Some(_permit),
) )
.await .await
} }
@@ -165,7 +165,7 @@ async fn recv_data_count(rx: &mut mpsc::Receiver<WriterCommand>, budget: Duratio
while Instant::now().duration_since(start) < budget { while Instant::now().duration_since(start) < budget {
let remaining = budget.saturating_sub(Instant::now().duration_since(start)); let remaining = budget.saturating_sub(Instant::now().duration_since(start));
match tokio::time::timeout(remaining.min(Duration::from_millis(10)), rx.recv()).await { match tokio::time::timeout(remaining.min(Duration::from_millis(10)), rx.recv()).await {
Ok(Some(WriterCommand::Data(_))) => data_count += 1, Ok(Some(WriterCommand::Data { .. })) => data_count += 1,
Ok(Some(WriterCommand::DataAndFlush(_))) => data_count += 1, Ok(Some(WriterCommand::DataAndFlush(_))) => data_count += 1,
Ok(Some(WriterCommand::ProxyReq(_))) => data_count += 1, Ok(Some(WriterCommand::ProxyReq(_))) => data_count += 1,
Ok(Some(WriterCommand::ControlAndFlush(_))) => data_count += 1, Ok(Some(WriterCommand::ControlAndFlush(_))) => data_count += 1,
@@ -185,7 +185,7 @@ async fn recv_first_data_payload(
while Instant::now().duration_since(start) < budget { while Instant::now().duration_since(start) < budget {
let remaining = budget.saturating_sub(Instant::now().duration_since(start)); let remaining = budget.saturating_sub(Instant::now().duration_since(start));
match tokio::time::timeout(remaining.min(Duration::from_millis(10)), rx.recv()).await { match tokio::time::timeout(remaining.min(Duration::from_millis(10)), rx.recv()).await {
Ok(Some(WriterCommand::Data(payload))) => return Some(payload.to_vec()), Ok(Some(WriterCommand::Data { payload, .. })) => return Some(payload.to_vec()),
Ok(Some(WriterCommand::DataAndFlush(payload))) => return Some(payload.to_vec()), Ok(Some(WriterCommand::DataAndFlush(payload))) => return Some(payload.to_vec()),
Ok(Some(_)) => {} Ok(Some(_)) => {}
Ok(None) => break, Ok(None) => break,
@@ -240,6 +240,7 @@ async fn send_proxy_req_does_not_replay_when_first_bind_commit_fails() {
b"hello", b"hello",
0, 0,
None, None,
None,
) )
.await; .await;
@@ -299,6 +300,7 @@ async fn send_proxy_req_prunes_iterative_stale_bind_failures_without_data_replay
b"storm", b"storm",
0, 0,
None, None,
None,
) )
.await; .await;
@@ -356,6 +358,7 @@ async fn send_proxy_req_uses_writer_source_ip_when_advertised_our_addr_differs()
b"route", b"route",
0, 0,
None, None,
None,
) )
.await; .await;
@@ -411,6 +414,7 @@ async fn send_proxy_req_blocking_fallback_uses_writer_source_ip() {
b"blocking", b"blocking",
0, 0,
None, None,
None,
) )
.await .await
}); });