use std::sync::atomic::{
AtomicBool,
AtomicU32,
Ordering,
};
use crossbeam_queue::ArrayQueue;
use tokio_util::sync::CancellationToken;
use crate::error::Error;
use crate::stream::Stream;
use crate::subprotocol::Subprotocol;
const SPARE_STREAM_CAP: usize = 16;
const SPARE_STREAM_LOW_WATERMARK: usize = 8;
type SpdyHeader = (String, String);
type PortforwardHeaderPair = (Vec<SpdyHeader>, Vec<SpdyHeader>);
pub struct Session {
inner: spdy_mux::Session,
protocol: Subprotocol,
port: u16,
next_request_id: AtomicU32,
spare_streams: ArrayQueue<Stream>,
replenishing: AtomicBool,
}
impl Session {
pub(crate) fn from_spdy(session: spdy_mux::Session, protocol: Subprotocol, port: u16) -> Self {
Self {
spare_streams: ArrayQueue::new(SPARE_STREAM_CAP),
replenishing: AtomicBool::new(false),
inner: session,
protocol,
port,
next_request_id: AtomicU32::new(0),
}
}
fn portforward_headers(&self) -> PortforwardHeaderPair {
let request_id = self
.next_request_id
.fetch_add(1, Ordering::Relaxed)
.to_string();
let port = self.port.to_string();
let error_headers = vec![
("streamtype".to_string(), "error".to_string()),
("port".to_string(), port.clone()),
("requestid".to_string(), request_id.clone()),
];
let data_headers = vec![
("streamtype".to_string(), "data".to_string()),
("port".to_string(), port),
("requestid".to_string(), request_id),
];
(error_headers, data_headers)
}
pub async fn connect(&self) -> Result<Stream, Error> {
while let Some(stream) = self.spare_streams.pop() {
if !stream.is_read_closed() {
return Ok(stream);
}
tracing::debug!("spare stream stale (remote closed while idle), discarding");
}
self.open_new_stream().await
}
async fn open_new_stream(&self) -> Result<Stream, Error> {
let (error_headers, data_headers) = self.portforward_headers();
self.inner
.open_stream_pair(error_headers, data_headers)
.await
.map(Stream::from_spdy)
.map_err(Error::from)
}
pub async fn replenish_spare_streams(&self) {
if self
.replenishing
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_err()
{
return;
}
let _guard = ReplenishGuard(&self.replenishing);
while self.spare_streams.len() < SPARE_STREAM_CAP {
if self.is_full() || self.cancellation_token().is_cancelled() {
break;
}
match self.open_new_stream().await {
Ok(stream) => {
if self.spare_streams.push(stream).is_err() {
break;
}
}
Err(_) => break,
}
}
}
pub fn spare_count(&self) -> usize {
self.spare_streams.len()
}
pub fn needs_replenish(&self) -> bool {
self.spare_count() <= SPARE_STREAM_LOW_WATERMARK
}
pub const fn protocol(&self) -> Subprotocol {
self.protocol
}
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
pub fn operating_capacity(&self) -> usize {
self.inner.operating_capacity()
}
pub fn in_use(&self) -> usize {
self.inner.in_use()
}
pub fn available(&self) -> usize {
self.inner.available()
}
pub fn is_full(&self) -> bool {
self.inner.is_full()
}
pub fn is_drained(&self) -> bool {
self.inner.is_drained()
}
pub fn cancellation_token(&self) -> CancellationToken {
self.inner.cancellation_token()
}
pub async fn close(self) -> Result<(), Error> {
self.inner.close().await.map_err(Error::from)
}
}
struct ReplenishGuard<'a>(&'a AtomicBool);
impl Drop for ReplenishGuard<'_> {
fn drop(&mut self) {
self.0.store(false, Ordering::Release);
}
}