mirror of https://github.com/fafhrd91/actix-web
ws: add buffer reclamation to prevent memory bloat on long-lived connections
BytesMut retains its allocation even after being fully drained, which means a single large WebSocket message permanently inflates memory for the lifetime of the connection. This adds a configurable `max_buffer_size` threshold (default 128 KiB) — when the read buffer is empty and its capacity exceeds this limit, it gets replaced with a fresh allocation. New API: `Codec::max_buffer_size(size)` to configure the threshold. Fixes #2075
This commit is contained in:
parent
96a4964c1b
commit
480ee0eef7
|
|
@ -71,6 +71,10 @@ pub enum Item {
|
|||
pub struct Codec {
|
||||
flags: Flags,
|
||||
max_size: usize,
|
||||
/// When the read buffer is fully drained and its capacity exceeds this
|
||||
/// threshold, it is replaced with a fresh allocation to reclaim memory.
|
||||
/// Defaults to 128 KiB.
|
||||
max_buffer_size: usize,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
|
@ -87,6 +91,7 @@ impl Codec {
|
|||
pub const fn new() -> Codec {
|
||||
Codec {
|
||||
max_size: 65_536,
|
||||
max_buffer_size: 128 * 1024,
|
||||
flags: Flags::SERVER,
|
||||
}
|
||||
}
|
||||
|
|
@ -100,6 +105,20 @@ impl Codec {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the max buffer capacity threshold for read buffer reclamation.
|
||||
///
|
||||
/// When the read buffer is fully drained (empty) and its allocated capacity
|
||||
/// exceeds this threshold, it is replaced with a fresh, smaller allocation.
|
||||
/// This prevents long-lived connections from permanently holding memory
|
||||
/// allocated for occasional large messages.
|
||||
///
|
||||
/// By default this is set to 128 KiB.
|
||||
#[must_use = "This returns the a new Codec, without modifying the original."]
|
||||
pub fn max_buffer_size(mut self, size: usize) -> Self {
|
||||
self.max_buffer_size = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set decoder to client mode.
|
||||
///
|
||||
/// By default decoder works in server mode.
|
||||
|
|
@ -220,6 +239,14 @@ impl Decoder for Codec {
|
|||
type Error = ProtocolError;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
// Reclaim memory: if the buffer has been fully drained and its capacity
|
||||
// exceeds the threshold, replace it with a fresh allocation. This prevents
|
||||
// peak memory usage from becoming permanent on long-lived connections
|
||||
// (e.g., WebSockets that occasionally receive large payloads).
|
||||
if src.is_empty() && src.capacity() > self.max_buffer_size {
|
||||
*src = BytesMut::new();
|
||||
}
|
||||
|
||||
match Parser::parse(src, self.flags.contains(Flags::SERVER), self.max_size) {
|
||||
Ok(Some((finished, opcode, payload))) => {
|
||||
// continuation is not supported
|
||||
|
|
|
|||
Loading…
Reference in New Issue