Fix regression: BK7231N devices not returning entities via API (#9283)

This commit is contained in:
J. Nick Koston 2025-07-01 18:30:03 -05:00 committed by GitHub
parent 3470305d9d
commit 0083abe3b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 72 additions and 94 deletions

View File

@ -1687,7 +1687,9 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
// O(n) but optimized for RAM and not performance. // O(n) but optimized for RAM and not performance.
for (auto &item : items) { for (auto &item : items) {
if (item.entity == entity && item.message_type == message_type) { if (item.entity == entity && item.message_type == message_type) {
// Update the existing item with the new creator // Clean up old creator before replacing
item.creator.cleanup(message_type);
// Move assign the new creator
item.creator = std::move(creator); item.creator = std::move(creator);
return; return;
} }
@ -1730,11 +1732,11 @@ void APIConnection::process_batch_() {
return; return;
} }
size_t num_items = this->deferred_batch_.items.size(); size_t num_items = this->deferred_batch_.size();
// Fast path for single message - allocate exact size needed // Fast path for single message - allocate exact size needed
if (num_items == 1) { if (num_items == 1) {
const auto &item = this->deferred_batch_.items[0]; const auto &item = this->deferred_batch_[0];
// Let the creator calculate size and encode if it fits // Let the creator calculate size and encode if it fits
uint16_t payload_size = uint16_t payload_size =
@ -1764,7 +1766,8 @@ void APIConnection::process_batch_() {
// Pre-calculate exact buffer size needed based on message types // Pre-calculate exact buffer size needed based on message types
uint32_t total_estimated_size = 0; uint32_t total_estimated_size = 0;
for (const auto &item : this->deferred_batch_.items) { for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
const auto &item = this->deferred_batch_[i];
total_estimated_size += get_estimated_message_size(item.message_type); total_estimated_size += get_estimated_message_size(item.message_type);
} }
@ -1785,7 +1788,8 @@ void APIConnection::process_batch_() {
uint32_t current_offset = 0; uint32_t current_offset = 0;
// Process items and encode directly to buffer // Process items and encode directly to buffer
for (const auto &item : this->deferred_batch_.items) { for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
const auto &item = this->deferred_batch_[i];
// Try to encode message // Try to encode message
// The creator will calculate overhead to determine if the message fits // The creator will calculate overhead to determine if the message fits
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type); uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
@ -1840,17 +1844,15 @@ void APIConnection::process_batch_() {
// Log messages after send attempt for VV debugging // Log messages after send attempt for VV debugging
// It's safe to use the buffer for logging at this point regardless of send result // It's safe to use the buffer for logging at this point regardless of send result
for (size_t i = 0; i < items_processed; i++) { for (size_t i = 0; i < items_processed; i++) {
const auto &item = this->deferred_batch_.items[i]; const auto &item = this->deferred_batch_[i];
this->log_batch_item_(item); this->log_batch_item_(item);
} }
#endif #endif
// Handle remaining items more efficiently // Handle remaining items more efficiently
if (items_processed < this->deferred_batch_.items.size()) { if (items_processed < this->deferred_batch_.size()) {
// Remove processed items from the beginning // Remove processed items from the beginning with proper cleanup
this->deferred_batch_.items.erase(this->deferred_batch_.items.begin(), this->deferred_batch_.remove_front(items_processed);
this->deferred_batch_.items.begin() + items_processed);
// Reschedule for remaining items // Reschedule for remaining items
this->schedule_batch_(); this->schedule_batch_();
} else { } else {
@ -1861,23 +1863,16 @@ void APIConnection::process_batch_() {
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single, uint16_t message_type) const { bool is_single, uint16_t message_type) const {
if (has_tagged_string_ptr_()) {
// Handle string-based messages
switch (message_type) {
#ifdef USE_EVENT #ifdef USE_EVENT
case EventResponse::MESSAGE_TYPE: { // Special case: EventResponse uses string pointer
auto *e = static_cast<event::Event *>(entity); if (message_type == EventResponse::MESSAGE_TYPE) {
return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single); auto *e = static_cast<event::Event *>(entity);
} return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
#endif
default:
// Should not happen, return 0 to indicate no message
return 0;
}
} else {
// Function pointer case
return data_.ptr(entity, conn, remaining_size, is_single);
} }
#endif
// All other message types use function pointers
return data_.function_ptr(entity, conn, remaining_size, is_single);
} }
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,

View File

@ -451,96 +451,53 @@ class APIConnection : public APIServerConnection {
// Function pointer type for message encoding // Function pointer type for message encoding
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
// Optimized MessageCreator class using tagged pointer
class MessageCreator { class MessageCreator {
// Ensure pointer alignment allows LSB tagging
static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging");
public: public:
// Constructor for function pointer // Constructor for function pointer
MessageCreator(MessageCreatorPtr ptr) { MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
// Function pointers are always aligned, so LSB is 0
data_.ptr = ptr;
}
// Constructor for string state capture // Constructor for string state capture
explicit MessageCreator(const std::string &str_value) { explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); }
// Allocate string and tag the pointer
auto *str = new std::string(str_value);
// Set LSB to 1 to indicate string pointer
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
}
// Destructor // No destructor - cleanup must be called explicitly with message_type
~MessageCreator() {
if (has_tagged_string_ptr_()) {
delete get_string_ptr_();
}
}
// Copy constructor // Delete copy operations - MessageCreator should only be moved
MessageCreator(const MessageCreator &other) { MessageCreator(const MessageCreator &other) = delete;
if (other.has_tagged_string_ptr_()) { MessageCreator &operator=(const MessageCreator &other) = delete;
auto *str = new std::string(*other.get_string_ptr_());
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
} else {
data_ = other.data_;
}
}
// Move constructor // Move constructor
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; } MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.function_ptr = nullptr; }
// Assignment operators (needed for batch deduplication)
MessageCreator &operator=(const MessageCreator &other) {
if (this != &other) {
// Clean up current string data if needed
if (has_tagged_string_ptr_()) {
delete get_string_ptr_();
}
// Copy new data
if (other.has_tagged_string_ptr_()) {
auto *str = new std::string(*other.get_string_ptr_());
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
} else {
data_ = other.data_;
}
}
return *this;
}
// Move assignment
MessageCreator &operator=(MessageCreator &&other) noexcept { MessageCreator &operator=(MessageCreator &&other) noexcept {
if (this != &other) { if (this != &other) {
// Clean up current string data if needed // IMPORTANT: Caller must ensure cleanup() was called if this contains a string!
if (has_tagged_string_ptr_()) { // In our usage, this happens in add_item() deduplication and vector::erase()
delete get_string_ptr_();
}
// Move data
data_ = other.data_; data_ = other.data_;
// Reset other to safe state other.data_.function_ptr = nullptr;
other.data_.ptr = nullptr;
} }
return *this; return *this;
} }
// Call operator - now accepts message_type as parameter // Call operator - uses message_type to determine union type
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
uint16_t message_type) const; uint16_t message_type) const;
private: // Manual cleanup method - must be called before destruction for string types
// Check if this contains a string pointer void cleanup(uint16_t message_type) {
bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; } #ifdef USE_EVENT
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
// Get the actual string pointer (clears the tag bit) delete data_.string_ptr;
std::string *get_string_ptr_() const { data_.string_ptr = nullptr;
// NOLINTNEXTLINE(performance-no-int-to-ptr) }
return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1)); #endif
} }
union { private:
MessageCreatorPtr ptr; union Data {
uintptr_t tagged; MessageCreatorPtr function_ptr;
} data_; // 4 bytes on 32-bit std::string *string_ptr;
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before
}; };
// Generic batching mechanism for both state updates and entity info // Generic batching mechanism for both state updates and entity info
@ -558,20 +515,46 @@ class APIConnection : public APIServerConnection {
std::vector<BatchItem> items; std::vector<BatchItem> items;
uint32_t batch_start_time{0}; uint32_t batch_start_time{0};
private:
// Helper to cleanup items from the beginning
void cleanup_items_(size_t count) {
for (size_t i = 0; i < count; i++) {
items[i].creator.cleanup(items[i].message_type);
}
}
public:
DeferredBatch() { DeferredBatch() {
// Pre-allocate capacity for typical batch sizes to avoid reallocation // Pre-allocate capacity for typical batch sizes to avoid reallocation
items.reserve(8); items.reserve(8);
} }
~DeferredBatch() {
// Ensure cleanup of any remaining items
clear();
}
// Add item to the batch // Add item to the batch
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type); void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
// Add item to the front of the batch (for high priority messages like ping) // Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
// Clear all items with proper cleanup
void clear() { void clear() {
cleanup_items_(items.size());
items.clear(); items.clear();
batch_start_time = 0; batch_start_time = 0;
} }
// Remove processed items from the front with proper cleanup
void remove_front(size_t count) {
cleanup_items_(count);
items.erase(items.begin(), items.begin() + count);
}
bool empty() const { return items.empty(); } bool empty() const { return items.empty(); }
size_t size() const { return items.size(); }
const BatchItem &operator[](size_t index) const { return items[index]; }
}; };
// DeferredBatch here (16 bytes, 4-byte aligned) // DeferredBatch here (16 bytes, 4-byte aligned)