ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
event_pool.h
Go to the documentation of this file.
1#pragma once
2
3#if defined(USE_ESP32) || defined(USE_ZEPHYR) || defined(USE_LIBRETINY) || defined(USE_RP2) || defined(USE_HOST) || \
4 defined(USE_ESP8266)
5
6#include <atomic>
7#include <cstddef>
10
11namespace esphome {
12
13// Event Pool - On-demand pool of objects to avoid heap fragmentation
14// Events are allocated on first use and reused thereafter, growing to peak
15// usage; warm() pre-creates every entry up front for malloc-free producers
16// @tparam T The type of objects managed by the pool (must have a release() method)
17// @tparam SIZE The maximum number of objects in the pool (1-254, limited by uint8_t and the +1 free-list slot)
18//
19// SIZING: When paired with a LockFreeQueue<T, Q_SIZE>, the pool SIZE should be
20// Q_SIZE - 1 (the queue's actual capacity, since the ring buffer reserves one slot).
21// This ensures allocate() returns nullptr before push() can fail, which:
22// - Prevents the allocate-succeeds-but-push-fails mismatch that permanently
23// leaks a pool slot (the element is never returned to the pool)
24// - Avoids needing release() on the producer path after a failed push(),
25// preserving the SPSC contract on the internal free list
26template<class T, uint8_t SIZE> class EventPool {
27 // The free list ring must hold all SIZE objects at once (a fully drained
28 // pool), and LockFreeQueue reserves one slot — so it is sized SIZE + 1,
29 // which caps SIZE at 254.
30 static_assert(SIZE < 255, "EventPool SIZE must be at most 254");
31
32 public:
33 EventPool() : total_created_(0) {}
34
36 // Clean up any remaining events in the free list
37 // IMPORTANT: This destructor assumes no concurrent access. The EventPool must not
38 // be destroyed while any thread might still call allocate() or release().
39 // In practice, this is typically ensured by destroying the pool only during
40 // component shutdown when all producer/consumer threads have been stopped.
41 T *event;
43 while ((event = this->free_list_.pop()) != nullptr) {
44 // Call destructor
45 event->~T();
46 // Deallocate using RAMAllocator
47 allocator.deallocate(event, 1);
48 }
49 }
50
51 // Allocate an event from the pool
52 // Returns nullptr if pool is full
53 T *allocate() {
54 // Try to get from free list first
55 T *event = this->free_list_.pop();
56 if (event != nullptr)
57 return event;
58 // Need to create a new event
59 return this->create_();
60 }
61
62 // Return an event to the pool for reuse
63 void release(T *event) {
64 if (event != nullptr) {
65 // Clean up the event's allocated memory
66 event->release();
67 this->free_list_.push(event);
68 }
69 }
70
71 // Pre-create every pool entry so allocate() is always a free-list pop
72 // (for producers that must never malloc, e.g. IRQ-context handlers).
73 // Call from setup(); on false the heap could not supply every entry and
74 // the caller should mark_failed() — an incomplete warm puts malloc()
75 // back on the producer path. Tops the pool up from any quiescent state
76 // (entries that already exist are counted, not re-created); must not run
77 // concurrently with allocate()/release().
78 bool warm() {
79 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) -- ownership transfers to the free list
80 while (this->total_created_ < SIZE) {
81 T *event = this->create_();
82 if (event == nullptr)
83 return false;
84 this->free_list_.push(event);
85 }
86 return true;
87 }
88
89 private:
90 // Create and count one new object (shared by allocate() and warm()).
91 // Returns nullptr at capacity or when the heap is exhausted.
92 T *create_() {
93 if (this->total_created_ >= SIZE) {
94 // Pool is at capacity
95 return nullptr;
96 }
97 // Use internal RAM for better performance
98 RAMAllocator<T> allocator(RAMAllocator<T>::ALLOC_INTERNAL);
99 T *event = allocator.allocate(1);
100 if (event == nullptr) {
101 // Memory allocation failed
102 return nullptr;
103 }
104 // Placement new to construct the object
105 new (event) T();
106 this->total_created_++;
107 return event;
108 }
109
110 // SIZE + 1 slots so all SIZE objects fit when the pool is fully drained
111 // (the ring reserves one slot); otherwise the last release() of a
112 // completely returned pool would drop, permanently orphaning one object.
113 LockFreeQueue<T, static_cast<uint8_t>(SIZE + 1)> free_list_; // Free events ready for reuse
114 uint8_t total_created_; // Total events created (high water mark, max 254)
115};
116
117} // namespace esphome
118
119#endif // defined(USE_ESP32) || defined(USE_ZEPHYR) || defined(USE_LIBRETINY) || defined(USE_RP2) || defined(USE_HOST)
void release(T *event)
Definition event_pool.h:63
bool push(T *element)
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2298
void deallocate(T *p, size_t n)
Definition helpers.h:2355