Warn once per transaction when the TCP buffer is congested

This commit is contained in:
J. Nick Koston
2026-08-21 15:57:54 -05:00
parent 46f90d0c54
commit 18c563a82c
2 changed files with 24 additions and 6 deletions
@@ -275,10 +275,15 @@ void BluetoothConnection::send_ack_(PendingAck kind, uint16_t handle, conn_err_t
if (this->try_send_ack_(kind, handle, error))
return;
// Report a newly owed reply and a displaced one; displacing is the case
// that loses a reply. Re-refusing the same one stays quiet.
// that loses a reply. Re-refusing the same one stays quiet, and so does a
// fresh deferral for the handle already warned about: a congested bulk
// transfer re-asks the same handle every cycle and each ack would warn.
if (!this->has_pending_ack_()) {
ESP_LOGW(TAG, "[%d] [%s] GATT reply for handle 0x%04X deferred, TCP buffer full", this->connection_index_,
this->address_str_, handle);
if (!this->ack_deferred_warned_ || this->pending_ack_handle_ != handle) {
ESP_LOGW(TAG, "[%d] [%s] GATT reply for handle 0x%04X deferred, TCP buffer full", this->connection_index_,
this->address_str_, handle);
this->ack_deferred_warned_ = true;
}
} else if (this->pending_ack_handle_ != handle || this->pending_ack_ != kind) {
ESP_LOGW(TAG, "[%d] [%s] GATT reply for handle 0x%04X dropped for handle 0x%04X", this->connection_index_,
this->address_str_, this->pending_ack_handle_, handle);
@@ -365,8 +370,14 @@ void BluetoothConnection::on_notify_data(uint16_t handle, const uint8_t *data, u
resp.set_data(data, len);
if (!api_connection->send_message(resp)) {
// Not latched, same reason as the read reply. Notify data is lossy: the
// peripheral will not resend it.
ESP_LOGW(TAG, "[%d] [%s] Failed to send notify data response", this->connection_index_, this->address_str_);
// peripheral will not resend it. Warn on the first drop only; a congested
// link drops a whole stream and one line per notify floods the log.
if (!this->notify_drop_warned_) {
ESP_LOGW(TAG, "[%d] [%s] Failed to send notify data response", this->connection_index_, this->address_str_);
this->notify_drop_warned_ = true;
} else {
ESP_LOGV(TAG, "[%d] [%s] Failed to send notify data response", this->connection_index_, this->address_str_);
}
}
}
@@ -164,6 +164,8 @@ class BluetoothConnection final : public ble_device_base::GattClientListener {
this->pending_ack_ = PendingAck::PENDING_ACK_NONE;
this->batch_stalled_ = false;
this->connected_reply_owed_ = false;
this->ack_deferred_warned_ = false;
this->notify_drop_warned_ = false;
}
/// Sole construction site for these replies, shared by send and retry.
bool try_send_ack_(PendingAck kind, uint16_t handle, conn_err_t error);
@@ -238,7 +240,7 @@ class BluetoothConnection final : public ble_device_base::GattClientListener {
// Group 5: bit-packed tail. The first two bytes were already full, so the
// first added bit forced a third and took the 8-aligned object 48 -> 56;
// the handle, error and retry counter ride in that padding. Four bitfield
// the handle, error and retry counter ride in that padding. Two bitfield
// bits left; another byte-sized member costs 8 per slot.
static_assert(static_cast<uint8_t>(ClientState::ESTABLISHED) < (1 << 3), "state_ bitfield too narrow");
static_assert(static_cast<uint8_t>(ConnectionType::V3_WITHOUT_CACHE) < (1 << 2),
@@ -258,6 +260,11 @@ class BluetoothConnection final : public ble_device_base::GattClientListener {
bool batch_stalled_ : 1 {false};
/// An owed connected=true reply; the proxy's paced drain re-offers it.
bool connected_reply_owed_ : 1 {false};
/// Set once the deferred warn fired; with an unchanged pending_ack_handle_
/// it keeps re-deferrals of the same handle quiet (see send_ack_).
bool ack_deferred_warned_ : 1 {false};
/// Set on the first dropped notify; later drops log at verbose only.
bool notify_drop_warned_ : 1 {false};
// Plain byte after the bitfields: takes the padding byte instead of
// straddling pending_ack_'s storage unit and growing the object.
static_assert(PENDING_ACK_RETRY_LIMIT <= 0xFF, "retry counter too narrow");