/* ************************************************************************** */ /* */ /* ::: :::::::: */ /* SlotPool.hpp :+: :+: :+: */ /* +:+ +:+ +:+ */ /* By: acossari +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2026/08/28 21:51:32 by acossari #+# #+# */ /* Updated: 2026/08/29 20:33:13 by acossari ### ########.fr */ /* */ /* ************************************************************************** */ #pragma once #include #include #include #include #include #include "event/EventToken.hpp" #include "net/Connection.hpp" #include "net/FileDescriptor.hpp" namespace webserv { // SlotPool uses fixed storage instead of growing a container for every new // connection. This representation provides three guarantees: // // 1. Every slot keeps a permanent index and stable storage. EventToken pairs // that index with an endpoint generation so stale events can be rejected. // // 2. The slot array is allocated once. Connections are constructed and // destroyed in place, so their slots are never relocated. // // 3. The free list limits the pool to CAPACITY connections. acquire() returns // no index when it is exhausted, and drain() makes retired slots reusable. class SlotPool final { public: // Slot indices use uint32_t to match their field in the 64-bit EventToken. // 256 is a calculated tradeoff, not an optimal value. It doubles the // target of 128 concurrent clients exercised by the provided tester. It // assumes a conservative limit of 1024 fds per process, common on Linux. // // fd budget = 4 base fds (standard streams and epoll) // + L listener fds // + 2 * 256 connection fds (socket + possible temporary file) // + 2 * 24 CGI pipe fds (the tester reaches 20, leaving 4 spare) // + 8 spare fds (temporary setup and general working margin) // = 572 + L // // This leaves space for up to 452 listeners, giving substantial headroom. // A capacity of 512 would require 1084 + L fds, already exceeding 1024. static constexpr std::uint32_t CAPACITY = 256; // Each slot tracks the client socket and the CGI stdin and stdout pipes. static constexpr std::size_t ENDPOINTS_PER_SLOT = 3; struct Endpoint { std::uint32_t generation = 0; std::uint32_t registeredEvents = 0; bool registered = false; }; SlotPool(); bool full() const noexcept { return freeHead_ == CAPACITY; } // Constructs a Connection for the socket in a free slot and returns its // index. Returns std::nullopt when the pool is full. std::optional acquire(FileDescriptor socket); // Returns true only if the token still identifies a registered endpoint // in an active slot with the same generation. bool isCurrent(const EventToken& token) const noexcept; // Accesses the connection or endpoint metadata already stored in a slot. Connection& connection(std::uint32_t index); Endpoint& endpoint(std::uint32_t index, EndpointKind kind); // Quarantines an active slot until the current event batch is complete. void retire(std::uint32_t index) noexcept; // Recycles all quarantined slots and returns how many became available. std::size_t drain(); private: enum class SlotState { Free, InUse, Retired }; struct Slot { std::array endpoints; std::optional connection; SlotState state = SlotState::Free; std::uint32_t nextFree = 0; }; std::array slots_; // Holds retired slot indices until they can return to the free list. // With storage reserved for CAPACITY entries, it acts as if it were a fixed // array while tracking how many entries are active without another counter. std::vector pendingFree_; std::uint32_t freeHead_ = 0; }; } // namespace webserv