Back home

A confiabilidade do tempo de execução do Rust/Wasm requer lidar com recuperação de pânico e abortamento

Assim que a instância compartilhada do Wasm começar a aceitar chamadas por um longo período, a falha passará de uma única falha para um problema de recuperação de estado e isolamento de falhas.

A princípio, o Wasm pode ser facilmente considerado uma camada de portabilidade: o código pode ser programado, a página pode ser executada, o desempenho é bom e as coisas parecem continuar iguais. Realmente começa a ficar difícil, geralmente depois que a demonstração é aprovada. Assim que módulos como editores, renderizadores e analisadores de documentos passarem de experimentos de página única para tempos de execução residentes de longo prazo, os modelos de falha mudarão imediatamente.

Neste momento, panic e abort não são mais ramos de exceção na camada de linguagem. O que eles decidem é: se esta instância pode continuar a receber trabalho subsequente, se o estado na memória está contaminado, se a camada host deve descartar a instância imediatamente e se o pool de instâncias precisa ser preenchido. Quando a equipe móvel move um kernel que está rodando em contêineres nativos há muito tempo para a Web, é essa camada de mudança que é mais facilmente subestimada.

Depois que a Demo for aprovada, o modelo de falha acabou de ser iniciado.

Falhas em uma única chamada não são difíceis de entender. Um clique de botão aciona uma chamada Wasm. Se falhar, um erro será relatado para a operação. Atualize a página e tente novamente. O custo ainda é controlável.

O problema ocorre depois que o tempo de execução começa a reutilizar instâncias. Quando a mesma instância Wasm abre continuamente vários documentos, recebe várias rodadas de eventos de entrada e passa por várias chamadas de ponte JS, o escopo de influência do pânico e da interrupção não para mais na ação atual. Uma falha incompleta pode atrasar as solicitações subsequentes.

Tais riscos muitas vezes não são expostos no primeiro dia. No primeiro estágio, você geralmente vê apenas relatórios de erros dispersos: falhas ocasionais de renderização, uma determinada exportação trava e um determinado documento fica em um estado incorreto após ser fechado e reaberto. Se você verificar mais detalhadamente, as pistas convergirão gradativamente para o mesmo fenômeno: embora a falha tenha ocorrido em uma cadeia de chamadas, o dano permaneceu na instância compartilhada.

Neste ponto, o foco da discussão não é mais “Se o código Rust entrará em pânico”, mas “Se este tempo de execução está qualificado para continuar atendendo a próxima chamada após o pânico”.

O pânico pode ser detectado, o aborto só pode alterar as instâncias.

A coisa mais importante a separar no Rust/Wasm são as duas semânticas de falha: pânico e aborto.

O pânico também tem a oportunidade de relaxar ao longo dos limites estabelecidos. Contanto que a camada de ligação e a camada de host concordem antecipadamente com o método de recuperação, a chamada atual poderá falhar e outros estados na instância também poderão ser mantidos. abortar não é o caminho a seguir. Isso significa que a execução atual atingiu um estado irrecuperável. Se você continuar usando a mesma instância para receber solicitações, estará essencialmente apostando que a memória e os recursos não serão danificados no meio do caminho.

Depois que os dois forem misturados durante o tempo de execução, certamente ocorrerão problemas no processamento subsequente:

  • Engula a interrupção como uma exceção normal e o pool de instâncias continuará a reutilizar objetos que perderam credibilidade.
  • Trate todos os pânicos como se a instância precisasse ser destruída e o rendimento seria reduzido desnecessariamente
  • O host JS sabe apenas que “a chamada falhou”, mas não sabe se deve tentar novamente, perder a instância ou interromper a sessão atual

Essa também é a coisa mais realista sobre a confiabilidade do tempo de execução do Wasm: a semântica de recuperação deve ser definida primeiro, antes que o isolamento e o agendamento subsequentes possam ser implementados.

Se a camada de ligação não fornecer semântica de recuperação, a camada host assumirá o estado ruim e continuará aceitando o trabalho.

O lugar mais perigoso para esse tipo de problema não está no código comercial, mas na camada de ligação que parece “já ter sido resolvida”. A camada host geralmente vê apenas um objeto de erro lançado e o registra como uma falha de chamada normal. O log está lá e a página não trava imediatamente, mas o sistema pode ter deixado o estado ruim dentro da instância.

O que realmente precisa ser corrigido não é apenas try/catch, mas as ações de tratamento após a falha. Lógica semelhante à seguinte apenas começou a entrar no projeto de confiabilidade:

async function runWithRecovery(instance, input) {
  try {
    return await instance.exports.handle(input)
  } catch (error) {
    if (isAbort(error)) {
      pool.replace(instance.id)
    }
    throw error
  }
}

O foco deste código não está na sintaxe, mas em um simples julgamento: se a falha atual marcou esta instância como um objeto não confiável. Se a resposta for sim, a ação de recuperação não deve parar no lançamento de erros, mas deve continuar até a eliminação de instâncias, reconstrução de recursos e corte de fluxo de solicitações.

Enquanto essa camada não estiver claramente definida, o sistema parecerá estar lidando com erros, mas o que na verdade está fazendo é colocar um tempo de execução potencialmente corrompido de volta no caminho de produção.

Instâncias compartilhadas amplificarão o problema de recuperação em um problema de estratégia de pooling

Depois que o Wasm é colocado em produtos reais, raramente há apenas “uma instância até que a página seja fechada”. Mais comuns são pools de instâncias, pools de trabalhadores ou documentos em primeiro plano e tarefas em segundo plano que compartilham um conjunto de recursos de tempo de execução. Nesta fase, os custos de recuperação do pânico e do aborto irão reescrever directamente a estratégia de pooling.

Se a inicialização da instância for cara, o sistema tenderá naturalmente a reutilizá-la tanto quanto possível. Mas uma vez estabelecida a reutilização, o isolamento de falhas deve ser atualizado simultaneamente:

  • Quais estados só podem ser suspensos em uma única chamada e serão descartados com a chamada após falha
  • Quais caches podem ser retidos entre chamadas e quais caches devem ser completamente invalidados quando a interrupção for encontrada
  • Após a substituição da instância, como serão migradas as tarefas enfileiradas? Tentar novamente causará efeitos colaterais duas vezes?

Estas não são respostas que a camada de idioma enviará automaticamente. Eles são designs de tempo de execução.

Por causa disso, se a discussão sobre a confiabilidade do Rust/Wasm parar apenas em “o pânico pode ser detectado?”, é fácil subestimar o problema. O que realmente aumenta a lacuna no custo de manutenção é se o pool de instâncias consegue manter um limite de confiança claro após uma falha.

O limite aplicável está fortemente relacionado ao ciclo de vida

Este conjunto de projetos de restauração não é necessário para todos os projetos Wasm.

Se o módulo for apenas uma ferramenta offline única ou se toda a instância for reciclada quando a página for destruída, a diferença entre pânico e aborto ainda existirá, mas o benefício de recuperação será muito menor. Muitas vezes é suficiente atualizar a página diretamente e executar novamente a tarefa diretamente.

Uma vez que o sistema tenha as seguintes características, a semântica de recuperação mudará rapidamente de um “item de otimização” para um “item de infraestrutura”:

  • A instância reside por muito tempo e não é destruída junto com o ciclo de vida de uma única página
  • A mesma instância realiza continuamente múltiplas rodadas de chamadas
  • A camada de hospedagem precisa usar pooling em troca do tempo de inicialização e da taxa de transferência
  • Proteja o estado da sessão, o estado do cache e as tarefas na fila após falha

Quando as equipes móveis movem recursos nativos para a Web, esse limite é o mais provável de ser encontrado. O relacionamento de isolamento que foi originalmente estabelecido por padrão no processo do aplicativo muitas vezes precisava ser preenchido novamente após atingir o limite do host JS/Wasm.

Wasm facilita a entrada de código nativo no navegador, mas não traz consigo a semântica de recuperação de tempo de execução. Assim que o sistema começa a compartilhar instâncias, reutilizar o estado e aceitar chamadas de longo prazo, o pânico e a interrupção devem ser tratados como dois eventos de tempo de execução diferentes. O primeiro se preocupa em como encerrar a chamada atual e o último se preocupa se esta instância pode continuar a viver no pool. Se esse julgamento não for feito primeiro, quanto mais bem-sucedido for o transplante de código, mais difícil será lidar com as falhas subsequentes.

FAQ

What to read next

Related

Continue reading