Sample BLE notification backpressure logs

The enqueue/drain/still-full logs fire per fragment during media
transfers (~150 lines for one 35KB image in field captures). They now
sample first + every 25th with a running event count, the sent/pending
lines merge into one, and the redundant peripheral-ready line is gone -
same treatment the relay event logs received. The drop-after-exhaustion
error stays unsampled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-06-11 19:40:51 +02:00
co-authored by Claude Fable 5
parent cf3b85f65c
commit e74f36927f
2 changed files with 18 additions and 9 deletions
+15 -9
View File
@@ -91,6 +91,10 @@ final class BLEService: NSObject {
private var pendingNoiseSessionQueues = BLENoiseSessionQueues()
// Queue for notifications that failed due to full queue
private var pendingNotifications = BLEOutboundNotificationBuffer<CBCentral>()
// Backpressure logging fires per fragment during media transfers
// (hundreds of lines per image); sampled via this counter, which is
// only touched inside collectionsQueue barriers (no sync needed).
var notificationBackpressureLogCount = 0
// Accumulate long write chunks per central until a full frame decodes
private var pendingWriteBuffers = BLEInboundWriteBuffer()
@@ -924,7 +928,7 @@ final class BLEService: NSObject {
)
if case let .enqueued(count) = result {
SecureLogger.debug("📋 Queued \(context) packet for retry (pending=\(count))", category: .session)
self.logBackpressureSampled("📋 Queued \(context) packet for retry (pending=\(count))")
return
}
@@ -2045,11 +2049,17 @@ extension BLEService: CBPeripheralManagerDelegate {
}
func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) {
SecureLogger.debug("📤 Peripheral manager ready to send more notifications", category: .session)
drainPendingNotifications(logPrefix: "✅ Sent")
}
private func logBackpressureSampled(_ message: @autoclosure () -> String) {
notificationBackpressureLogCount += 1
if notificationBackpressureLogCount == 1 ||
notificationBackpressureLogCount.isMultiple(of: TransportConfig.bleBackpressureLogInterval) {
SecureLogger.debug("\(message()) [backpressure event #\(notificationBackpressureLogCount)]", category: .session)
}
}
private func drainPendingNotifications(logPrefix: String) {
collectionsQueue.async(flags: .barrier) { [weak self] in
guard let self = self,
@@ -2060,11 +2070,7 @@ extension BLEService: CBPeripheralManagerDelegate {
let sentCount = self.sendPendingNotifications(pending, characteristic: characteristic)
if sentCount > 0 {
SecureLogger.debug("\(logPrefix) \(sentCount) pending notifications from retry queue", category: .session)
}
if !self.pendingNotifications.isEmpty {
SecureLogger.debug("📋 Still have \(self.pendingNotifications.count) pending notifications", category: .session)
self.logBackpressureSampled("\(logPrefix) \(sentCount) pending notifications from retry queue (\(self.pendingNotifications.count) still pending)")
}
}
}
@@ -2082,7 +2088,7 @@ extension BLEService: CBPeripheralManagerDelegate {
guard success else {
let remaining = Array(pending.dropFirst(index))
pendingNotifications.prepend(remaining)
SecureLogger.debug("⚠️ Notification queue still full after \(sentCount) sent, re-queuing \(remaining.count) items", category: .session)
logBackpressureSampled("⚠️ Notification queue still full after \(sentCount) sent, re-queuing \(remaining.count) items")
break
}
+3
View File
@@ -41,6 +41,9 @@ enum TransportConfig {
static let blePendingNotificationsCapCount: Int = 128
static let bleNotificationRetryDelayMs: Int = 25
static let bleNotificationRetryMaxAttempts: Int = 80
// Sample interval for notification backpressure logs (fire per fragment
// during media transfers).
static let bleBackpressureLogInterval: Int = 25
// Nostr
static let nostrReadAckInterval: TimeInterval = 0.35 // ~3 per second