use axum::http;
use tonic::body::Body;
use tonic::transport::channel::ResponseFuture;
use tower::Service;
use tower::load::Load as _;
use tower::util::rng::HasherRng;
use tower::util::rng::Rng;
use tracing::info;
use trz_gateway_common::is_global::IsGlobal;
use super::connection_id::ConnectionId;
use super::pending_requests::PendingRequests;
pub struct IncomingClients<S: Service<http::Request<Body>>> {
channels: Vec<ChannelWithId<S>>,
rng: HasherRng,
}
struct ChannelWithId<S> {
connection_id: ConnectionId,
channel: PendingRequests<S>,
}
impl<S> IncomingClients<S>
where
S: Service<
http::Request<Body>,
Response = http::Response<Body>,
Future = ResponseFuture,
Error = tonic::transport::Error,
> + Clone
+ IsGlobal,
{
pub fn new() -> Self {
Self {
channels: vec![],
rng: Default::default(),
}
}
pub fn add_channel(&mut self, connection_id: ConnectionId, channel: S) {
info!("Adding channel");
let channel = PendingRequests::new(channel);
self.channels.push(ChannelWithId {
connection_id,
channel,
});
}
pub fn remove_channel(&mut self, connection_id: ConnectionId) {
info!("Removing channel");
self.channels = std::mem::take(&mut self.channels)
.into_iter()
.filter(|c| c.connection_id != connection_id)
.collect();
}
pub fn is_empty(&self) -> bool {
self.channels.is_empty()
}
pub fn get_channel(&mut self) -> Option<PendingRequests<S>> {
let count = self.channels.len();
if count < 2 {
return if count == 0 {
None
} else {
Some(self.channels[0].channel.clone())
};
}
let [a, b] =
sample_floyd2(&mut self.rng, count as u64).map(|i| &self.channels[i as usize].channel);
Some(if a.load() < b.load() { a } else { b }.clone())
}
}
fn sample_floyd2<R: Rng>(rng: &mut R, length: u64) -> [u64; 2] {
debug_assert!(2 <= length);
let aidx = rng.next_range(0..length - 1);
let bidx = rng.next_range(0..length);
let aidx = if aidx == bidx { length - 1 } else { aidx };
[aidx, bidx]
}