Enforce max websocket listeners
This commit is contained in:
parent
c3eb2dbf67
commit
62f8e5fdcd
2 changed files with 30 additions and 2 deletions
|
|
@ -12,23 +12,42 @@ pub enum Notification {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Broadcasts {
|
pub struct Broadcasts {
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
|
admission: Arc<std::sync::Mutex<HashMap<VaultId, std::sync::Weak<tokio::sync::Semaphore>>>>,
|
||||||
senders: Arc<Mutex<HashMap<VaultId, broadcast::Sender<Notification>>>>,
|
senders: Arc<Mutex<HashMap<VaultId, broadcast::Sender<Notification>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Broadcasts {
|
impl Broadcasts {
|
||||||
pub fn new(config: &ServerConfig) -> Self {
|
pub fn new(config: &ServerConfig) -> Self {
|
||||||
Self {
|
Self {
|
||||||
capacity: config.max_clients_per_vault.max(1),
|
capacity: config.max_clients_per_vault,
|
||||||
|
admission: Arc::default(),
|
||||||
senders: Arc::default(),
|
senders: Arc::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn try_admit(&self, vault: &VaultId) -> Option<tokio::sync::OwnedSemaphorePermit> {
|
||||||
|
let mut limits = self.admission.lock().unwrap();
|
||||||
|
|
||||||
|
limits.retain(|_, limit| limit.strong_count() > 0);
|
||||||
|
|
||||||
|
let limit = limits
|
||||||
|
.get(vault)
|
||||||
|
.and_then(std::sync::Weak::upgrade)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let limit = Arc::new(tokio::sync::Semaphore::new(self.capacity));
|
||||||
|
limits.insert(vault.clone(), Arc::downgrade(&limit));
|
||||||
|
limit
|
||||||
|
});
|
||||||
|
|
||||||
|
limit.try_acquire_owned().ok()
|
||||||
|
}
|
||||||
|
|
||||||
async fn sender(&self, vault: VaultId) -> broadcast::Sender<Notification> {
|
async fn sender(&self, vault: VaultId) -> broadcast::Sender<Notification> {
|
||||||
self.senders
|
self.senders
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.entry(vault)
|
.entry(vault)
|
||||||
.or_insert_with(|| broadcast::channel(self.capacity).0)
|
.or_insert_with(|| broadcast::channel(self.capacity.max(1)).0)
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,16 @@ pub async fn websocket_handler(
|
||||||
) -> Result<Response, SyncServerError> {
|
) -> Result<Response, SyncServerError> {
|
||||||
debug!("Upgrading WebSocket connection for vault `{vault_id}`");
|
debug!("Upgrading WebSocket connection for vault `{vault_id}`");
|
||||||
|
|
||||||
|
let Some(permit) = state.broadcasts.try_admit(&vault_id) else {
|
||||||
|
return Ok((
|
||||||
|
StatusCode::TOO_MANY_REQUESTS,
|
||||||
|
"Vault connection limit reached",
|
||||||
|
)
|
||||||
|
.into_response());
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ws.on_upgrade(move |socket| async move {
|
Ok(ws.on_upgrade(move |socket| async move {
|
||||||
|
let _permit = permit;
|
||||||
if let Err(error) = websocket(state, socket, vault_id).await {
|
if let Err(error) = websocket(state, socket, vault_id).await {
|
||||||
debug!("WebSocket disconnected: {error}");
|
debug!("WebSocket disconnected: {error}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue