Versão do documento 2026-05-29 · Contexto: 1300 ble_token_renew de um só convidado durante 4 horas com o proprietário offline, o que impulsionou a procura de outras fontes de tráfego HTTP e WebSocket a mais.
ble_token_renew in pending_host_msgs (um UNIQUE INDEX sobre host_id, dedup_key). A migração está aplicada.GuestConn.requestBleTokenRenew — não mais de uma vez a cada 60 s por bleId.onOpen; espera por uma sessão estável de pelo menos 60 s, o que protege da tempestade ligar–cair–ligar.Files: HostService.kt:1424-1432, Vault.kt:213
private val pendingKeysRunnable = object : Runnable {
override fun run() {
if (Prefs.isHostRegistered && Prefs.getPendingHostKeys().isNotEmpty()) {
Thread { syncPendingHostKeys() }.start()
}
pendingKeysHandler.postDelayed(this, 60_000L)
}
}
syncPendingHostKeys pega em TODAS as chaves de anfitrião pendentes e chama Api.keyCreatepara cada uma. Se a chamada HTTP falhar, a chave fica pendente e 60 s depois o ciclo repete-se.
В Prefs guardar por localId:
attempt_at_<localId> — a marca temporal da última tentativaattempt_count_<localId> — o contador de tentativasEm cada passagem de syncPendingHostKeys:
val waitMs = (30_000L * (1L shl attemptCount)).coerceAtMost(30 * 60_000L)
if (now - attemptAt < waitMs) return@forEach // skip
// otherwise try
val ok = try { Api.keyCreate(...); true } catch (e) { false }
if (ok) { clear attempt_at/count } else { attempt_count++; attempt_at = now }
Progression: 30 s → 1 min → 2 min → 4 min → 8 min → 16 min → 30 min (limite). Com uma falha permanente isso é um pedido a cada 30 minutos em vez de um por minuto. Uma redução de trinta vezes.
O mesmo vale para Vault.retryPendingBundles() — o mesmo padrão.
snap_url é o endereço externo de uma câmara, gravador ou fonte HTTP. O cliente vai a esse URL diretamente pelo Coil ou ExoPlayer; o nosso servidor apenas passa o data_cipher cifrado que contém o endereço dentro do pacote. Não passa por nós nenhum fotograma.
Se um proprietário puser snap_int=1 para 10 convidados, o gravador dele recebe 10 pedidos por segundo. Essa é carga do gravador dele, não do nosso servidor.
Opcional: limitar por baixo o cursor da interface a três segundos. É questão de interface, não de infraestrutura.
Já está travado pelas correções da 6.61:
Com dez objetos BLE, um utilizador envia dez mensagens WebSocket seguidas e o servidor executa dez ramos case 'ble_token_renew' seguidos. Uma ninharia.
File: GuestConn.kt — 7+ locais.
AppState.overridesVersion = MutableState<Int>, um sinal do Compose com o sentido os dados mudaram, relê remember. É usado em ActionCard:
val ovrVersion = AppState.overridesVersion.value
val (overrideLabel, avatarPath) = remember(action.key, ovrVersion) {
Prefs.getOverride(action.key) // SharedPreferences IO
}
val snapCfg = remember(action.key, ovrVersion) {
if (action.source == ActionSource.HOST_OWN) Prefs.getSnapshot(action.numberId)
else Prefs.getIncomingSnapshot(...)
}
val tpl = remember(action.key, ovrVersion) { loadTpl() } // more IO
Cada incremento faz cada cartão reler a substituição, o avatar, a captura e o modelo de SharedPreferences. Um utilizador com 20 cartões significa 80 leituras de SharedPreferences por incremento.
| Line | Context | É preciso? |
|---|---|---|
| L193 | após reingerir um pacote (sem K_obj) | yes |
| L355 | após parseNumbers em numbers_update | redundant quando os números não mudaram |
| L411 | depois de carregar um avatar | yes |
| L558, L595 | após parseNumbers nos restantes tratadores | redundant quando os dados são idênticos |
Com um WebSocket instável, cada guest_ok → o tratador volta a analisar os números → incrementa overridesVersion → 80 leituras de SharedPreferences × 20 cartões × a frequência de guest_ok.
Isso é CPU local e E/S, não carga direta do servidor, mas:
Antes de incrementar, comparar a nova lista JSON de numbers com a anterior, por hash ou comparando a lista de GuestNumberInfo). Idênticos → não incrementar.
private var lastNumbersHash: Int = 0
private fun maybeUpdateNumbers(arr: JSONArray?) {
val parsed = parseNumbers(arr)
val newHash = parsed.hashCode() // or a hash of the JSON serialisation
if (newHash == lastNumbersHash) return // nothing changed
lastNumbersHash = newHash
numbers = parsed
publish()
AppState.overridesVersion.value = AppState.overridesVersion.value + 1
}
numbers_update chega com regularidade mas muda raramente — um proprietário não edita objetos a cada minuto. A otimização remove cerca de 90 % das ondas de redesenho supérfluas.