Skip to content

Commit 3c58870

Browse files
committed
feat(ws): add slow client backpressure protection
1 parent 4d3e109 commit 3c58870

2 files changed

Lines changed: 51 additions & 5 deletions

File tree

include/vix/websocket/session.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,11 @@ namespace vix::websocket
293293
/** @brief True while a write flush is already in progress. */
294294
bool writeInProgress_{false};
295295

296+
std::size_t queuedWriteBytes_{0};
297+
298+
static constexpr std::size_t MAX_PENDING_WRITE_MESSAGES = 1024;
299+
static constexpr std::size_t MAX_PENDING_WRITE_BYTES = 4 * 1024 * 1024;
300+
296301
/** @brief Protects write queue and write state. */
297302
std::mutex writeMutex_{};
298303
};

src/session.cpp

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ namespace vix::websocket
408408
if (self->closing_)
409409
{
410410
self->writeQueue_.clear();
411+
self->queuedWriteBytes_ = 0;
411412
self->writeInProgress_ = false;
412413
co_return;
413414
}
@@ -420,6 +421,15 @@ namespace vix::websocket
420421

421422
msg = std::move(self->writeQueue_.front());
422423
self->writeQueue_.pop_front();
424+
425+
if (self->queuedWriteBytes_ >= msg.data.size())
426+
{
427+
self->queuedWriteBytes_ -= msg.data.size();
428+
}
429+
else
430+
{
431+
self->queuedWriteBytes_ = 0;
432+
}
423433
}
424434

425435
std::vector<std::byte> frame;
@@ -752,6 +762,9 @@ namespace vix::websocket
752762
return;
753763
}
754764

765+
const std::size_t payloadSize = payload.size();
766+
bool overflow = false;
767+
755768
{
756769
std::lock_guard<std::mutex> lock(writeMutex_);
757770

@@ -760,10 +773,32 @@ namespace vix::websocket
760773
return;
761774
}
762775

763-
writeQueue_.push_back(PendingMessage{
764-
isBinary,
765-
std::move(payload),
766-
});
776+
if (writeQueue_.size() >= MAX_PENDING_WRITE_MESSAGES ||
777+
queuedWriteBytes_ + payloadSize > MAX_PENDING_WRITE_BYTES)
778+
{
779+
overflow = true;
780+
}
781+
else
782+
{
783+
queuedWriteBytes_ += payloadSize;
784+
785+
writeQueue_.push_back(PendingMessage{
786+
isBinary,
787+
std::move(payload),
788+
});
789+
}
790+
}
791+
792+
if (overflow)
793+
{
794+
log().log(
795+
Logger::Level::Warn,
796+
"[ws] closing slow client reason=backpressure queue_messages={} queue_bytes={}",
797+
MAX_PENDING_WRITE_MESSAGES,
798+
MAX_PENDING_WRITE_BYTES);
799+
800+
close("backpressure");
801+
return;
767802
}
768803

769804
trigger_write_flush();
@@ -921,9 +956,15 @@ namespace vix::websocket
921956
open_ = false;
922957
closing_ = true;
923958

959+
{
960+
std::lock_guard<std::mutex> lock(writeMutex_);
961+
writeQueue_.clear();
962+
queuedWriteBytes_ = 0;
963+
writeInProgress_ = false;
964+
}
965+
924966
cancel_idle_timer();
925967
stop_heartbeat();
926-
927968
readCancel_.request_cancel();
928969
writeCancel_.request_cancel();
929970
closeCancel_.request_cancel();

0 commit comments

Comments
 (0)