// Glaze Library // For the license information refer to glaze.hpp #pragma once #include #include #include #include namespace glz { /// Thread-safe buffer pool for coroutine-based servers. /// /// thread_local buffers are unsafe with coroutines because when a coroutine /// suspends (co_await), the thread may process other connections, overwriting /// thread_local data. This pool provides per-connection buffers that survive /// coroutine suspension. /// /// Usage: /// buffer_pool pool{}; /// auto buf = pool.borrow(); // RAII - auto-returned on destruction /// buf.value().resize(100); /// // ... use buffer ... /// // buffer automatically returned when buf goes out of scope class buffer_pool final { private: std::vector> buffers_; mutable std::mutex mutex_; size_t max_buffers_; size_t max_buffer_size_; public: /// RAII handle for borrowed buffer - automatically returns to pool on destruction class scoped_buffer final { friend class buffer_pool; buffer_pool* pool_{}; std::unique_ptr buffer_{}; scoped_buffer(buffer_pool* pool, std::unique_ptr buf) noexcept : pool_(pool), buffer_(std::move(buf)) {} public: scoped_buffer() = default; ~scoped_buffer() { if (pool_ && buffer_) { pool_->release(std::move(buffer_)); } } // Move-only scoped_buffer(scoped_buffer&& other) noexcept : pool_(other.pool_), buffer_(std::move(other.buffer_)) { other.pool_ = nullptr; } scoped_buffer& operator=(scoped_buffer&& other) noexcept { if (this != &other) { if (pool_ && buffer_) { pool_->release(std::move(buffer_)); } pool_ = other.pool_; buffer_ = std::move(other.buffer_); other.pool_ = nullptr; } return *this; } scoped_buffer(const scoped_buffer&) = delete; scoped_buffer& operator=(const scoped_buffer&) = delete; /// Get reference to the underlying string buffer std::string& value() noexcept { return *buffer_; } const std::string& value() const noexcept { return *buffer_; } /// Pointer access to the underlying string buffer std::string* operator->() noexcept { return buffer_.get(); } const std::string* operator->() const noexcept { return buffer_.get(); } /// Dereference to get reference std::string& operator*() noexcept { return *buffer_; } const std::string& operator*() const noexcept { return *buffer_; } /// Check if buffer is valid explicit operator bool() const noexcept { return buffer_ != nullptr; } }; /// Construct a buffer pool /// @param max_buffers Maximum number of buffers to keep in pool (default 1024) /// @param max_buffer_size Buffers larger than this are shrunk when returned (default 1MB) explicit buffer_pool(size_t max_buffers = 1024, size_t max_buffer_size = 1024 * 1024) noexcept : max_buffers_(max_buffers), max_buffer_size_(max_buffer_size) { buffers_.reserve(std::min(max_buffers, size_t{64})); // Pre-allocate some capacity } // Non-copyable, non-movable (due to mutex and pointers in scoped_buffers) buffer_pool(const buffer_pool&) = delete; buffer_pool& operator=(const buffer_pool&) = delete; buffer_pool(buffer_pool&&) = delete; buffer_pool& operator=(buffer_pool&&) = delete; ~buffer_pool() = default; /// Borrow a buffer from the pool (thread-safe) /// The returned scoped_buffer automatically returns the buffer to the pool on destruction [[nodiscard]] scoped_buffer borrow() { std::unique_ptr buf; { std::lock_guard lock{mutex_}; if (!buffers_.empty()) { buf = std::move(buffers_.back()); buffers_.pop_back(); } } if (!buf) { buf = std::make_unique(); } buf->clear(); return scoped_buffer{this, std::move(buf)}; } /// Get current number of buffers in the pool [[nodiscard]] size_t size() const noexcept { std::lock_guard lock{mutex_}; return buffers_.size(); } /// Get maximum number of buffers the pool will hold [[nodiscard]] size_t max_size() const noexcept { return max_buffers_; } /// Get maximum buffer size before shrinking [[nodiscard]] size_t max_buffer_size() const noexcept { return max_buffer_size_; } private: void release(std::unique_ptr buf) { if (!buf) return; // Shrink oversized buffers to prevent memory bloat if (buf->capacity() > max_buffer_size_) { buf->shrink_to_fit(); } std::lock_guard lock{mutex_}; if (buffers_.size() < max_buffers_) { buffers_.push_back(std::move(buf)); } // Otherwise buffer is deallocated } }; }