playbook/antigravity-awesome-skills/skills/matematico-tao/references/concurrency-models.md

8.1 KiB

Modelos Formais de Concorrência para Kotlin/Android

1. Modelo CSP (Communicating Sequential Processes)

Definição Formal

Um processo CSP é definido por:

P ::= STOP                  -- processo morto (deadlock)
    | SKIP                  -- processo terminado normalmente
    | a → P                 -- prefixo: executa evento a, depois P
    | P □ Q                 -- choice externo: o ambiente escolhe
    | P ⊓ Q                 -- choice interno: P escolhe
    | P ‖ Q                 -- composição paralela
    | P \ A                  -- ocultação do conjunto A de eventos

Aplicação a Coroutines Kotlin

// Cada coroutine é um processo CSP
// launch { } ≡ processo concorrente
// channel.send(x) ≡ evento de saída
// channel.receive() ≡ evento de entrada

// Deadlock clássico em CSP:
// P = a → b → STOP
// Q = b → a → STOP
// P ‖ Q → cada um espera o outro primeiro → DEADLOCK

// Equivalente em Kotlin:
val channelA = Channel<Int>()
val channelB = Channel<Int>()
launch { channelA.send(1); channelB.receive() }  // P
launch { channelB.send(2); channelA.receive() }  // Q
// DEADLOCK: ambos bloqueados esperando

2. Modelo de Atores (Actor Model)

Definição

Cada ator tem:

  • Caixa postal (mailbox) — fila de mensagens
  • Comportamento — função: Mensagem → (Estado', [Atores novos], [Mensagens])
  • Estado local encapsulado — não compartilhado

Em Kotlin com Coroutines

// Actor via Channel + coroutine
fun CoroutineScope.counterActor() = actor<CounterMsg> {
    var counter = 0
    for (msg in channel) {
        when (msg) {
            is IncCounter -> counter++
            is GetCounter -> msg.response.complete(counter)
        }
    }
}

// Propriedades formais:
// - Sem race conditions: estado encapsulado
// - Sem deadlocks: se mailbox unbounded e sem cycles
// - Linearizabilidade: operações parecem atômicas para clientes

3. Modelo de Memória Android (JMM - Java Memory Model)

Happens-Before Relations

Regras do JMM que garantem visibilidade:
1. Program order: a₁ →ₚ a₂ se a₁ vem antes de a₂ no mesmo thread
2. Monitor lock: unlock(m) → lock(m)
3. Volatile: write(v) → read(v) para variável volatile
4. Thread start: start(t) → qualquer ação de t
5. Thread join: qualquer ação de t → join(t)
6. Finalizer: fim do construtor → início do finalize()

StateFlow e Atomicidade

// MutableStateFlow usa CAS (Compare-And-Swap) internamente
// Garantia: atualização via compareAndSet é lock-free e wait-free
// Leitura de .value é sempre a versão mais recente (volatile semantics)

// CORRETO: update atômico
_state.update { currentState ->
    currentState.copy(isRecording = true)
}

// INCORRETO: read-modify-write não atômico
val current = _state.value          // read
_state.value = current.copy(...)    // write separado → race condition!

4. Análise de Deadlocks em Android

Padrões de Deadlock Comuns

Pattern 1: runBlocking em Main Thread

// DEADLOCK: runBlocking bloqueia Main, coroutines precisam do Main
fun onClickButton() {
    runBlocking {  // bloqueia Main thread
        viewModel.doSomething()  // precisa de Main para updates
        // DEADLOCK!
    }
}

// CORRETO:
fun onClickButton() {
    lifecycleScope.launch {
        viewModel.doSomething()
    }
}

Pattern 2: Mutex lock reentrante (não existe em Kotlin)

// Kotlin Mutex é NÃO reentrante — diferente de synchronized(this)
val mutex = Mutex()

suspend fun outer() {
    mutex.withLock {
        inner()  // tenta adquirir mesmo mutex → DEADLOCK!
    }
}

suspend fun inner() {
    mutex.withLock {  // bloqueia esperando outer() liberar
        // nunca chega aqui
    }
}

Pattern 3: Channel rendezvous sem consumidor

val channel = Channel<Result>()  // sem buffer

launch {
    channel.send(result)  // bloqueia até alguém receber
}
// Se não há nenhum receiver ativo → coroutine fica suspensa para sempre
// Pode causar memory leak se scope sobrevive

// CORRETO: usar Channel(BUFFERED) ou garantir receiver existe

5. Análise de Liveness (Ausência de Starvation)

Definição Formal

Starvation: processo P está em starvation se:
∃ sequência infinita de execuções onde P nunca progride,
mesmo sendo elegível para execução.

Em termos de LTL:
¬Starvation(P) ≡ GF(ready(P)) → GF(running(P))
("sempre que P está pronto, eventualmente P executa")

No Contexto Android/Kotlin

// Fairness do scheduler de coroutines:
// - Dispatchers.Default: trabalho processor-bound, round-robin entre coroutines
// - Dispatchers.IO: thread pool expansível (default 64 threads), fair scheduling
// - Dispatchers.Main: fila FIFO no Main thread

// Risco de starvation:
// 1. Dispatchers.Default com muitas coroutines CPU-bound → novas ficam esperando
// 2. Dispatchers.IO.limitedParallelism(n) → n pequeno → fila grande

// Exemplo Auri:
// VoicePipeline roda em Main (para updates de UI)
// LLM requests rodam em IO
// Se LLM request bloquear IO thread pool → STT pode ficar esperando

6. Verificação de Propriedades com TLA+

Exemplo para VoicePipeline

VARIABLES state, sttResult, llmResult

Init == state = "IDLE" /\ sttResult = "" /\ llmResult = ""

StartRecording ==
    /\ state = "IDLE"
    /\ state' = "RECORDING"
    /\ UNCHANGED <<sttResult, llmResult>>

StopAndTranscribe ==
    /\ state = "RECORDING"
    /\ state' = "TRANSCRIBING"
    /\ UNCHANGED <<sttResult, llmResult>>

STTComplete ==
    /\ state = "TRANSCRIBING"
    /\ sttResult' \in STRING \ {""}
    /\ state' = "QUERYING_LLM"
    /\ UNCHANGED <<llmResult>>

-- Propriedade de Safety:
NoDeadlock == state \in {"IDLE","RECORDING","TRANSCRIBING",
                          "QUERYING_LLM","SPEAKING","ERROR"}

-- Propriedade de Liveness:
EventuallyIdle == <>(state = "IDLE")

7. Race Conditions — Checklist para Kotlin/Android

Variáveis que precisam de proteção

// ❌ INSEGURO: var compartilhado entre coroutines sem sincronização
var isConnected: Boolean = false
launch(Dispatchers.IO) { isConnected = true }
launch(Dispatchers.Default) { if (isConnected) ... }  // race!

// ✅ SEGURO: @Volatile para leituras/escritas simples
@Volatile var isConnected: Boolean = false

// ✅ SEGURO: AtomicBoolean para CAS operations
val isConnected = AtomicBoolean(false)
isConnected.compareAndSet(false, true)

// ✅ SEGURO: StateFlow para estado observável
private val _isConnected = MutableStateFlow(false)
val isConnected = _isConnected.asStateFlow()

Padrões seguros em Kotlin coroutines

// Mutex para seções críticas
val mutex = Mutex()
mutex.withLock {
    // seção crítica
}

// Actor para estado mutável encapsulado
val stateActor = actor<StateMessage> { ... }

// StateFlow para estado reativo
val state = MutableStateFlow(initialState)
state.update { it.copy(...) }  // atômico via CAS

8. Análise de Memory Leaks em Android

Context Leaks (mais comum)

// ❌ LEAK: Activity context capturada em objeto de longa vida
class LlmClient(val context: Context) {  // se context = Activity → leak
    // cliente pode sobreviver à Activity
}

// ✅ CORRETO: Application context para objetos de longa vida
class LlmClient(val context: Context) {
    init {
        // usar context.applicationContext para operações longas
    }
}

Coroutine Leaks

// ❌ LEAK: coroutine lançada sem scope adequado
fun startRecording() {
    GlobalScope.launch {  // nunca cancelado!
        // ...
    }
}

// ✅ CORRETO: scope vinculado ao ciclo de vida
class EarLlmService : Service() {
    private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    override fun onDestroy() {
        serviceScope.cancel()  // cancela todas as coroutines
    }
}

Listener Leaks (Bluetooth)

// ❌ LEAK: listener registrado mas nunca removido
audioManager.registerAudioDeviceCallback(callback, null)
// onDestroy esquece de chamar unregisterAudioDeviceCallback

// ✅ CORRETO: registro/desregistro simétrico
override fun onStart() { register(callback) }
override fun onStop() { unregister(callback) }