Em 10 de outubro de 2018, nossa equipe lançou uma nova versão do aplicativo no React Native. Estamos satisfeitos e orgulhosos disso.
Mas o horror é algo: depois de algumas horas, o número de falhas do Android aumenta repentinamente.
 10.000 falhas no Android
10.000 falhas no AndroidNossa ferramenta de monitoramento de falhas 
Sentry está ficando louca.
Em todos os casos, vemos um erro como 
JSApplicationIllegalArgumentException Error while updating property 'left' in shadow node of type: RCTView" .
Em React Native, isso geralmente acontece se você definir uma propriedade com o tipo errado. Mas por que o erro não apareceu durante o teste? Em nós, cada desenvolvedor testa cuidadosamente novos lançamentos em vários dispositivos.
Os erros também parecem bastante aleatórios, parecem ocorrer em qualquer combinação de propriedades e digitar nós de sombra. Por exemplo, aqui estão os três primeiros:
- Error while updating property 'paddingTop' in shadow node of type: RCTView
- Error while updating property 'height' in shadow node of type: RCTImageView
- Error while updating property 'fill' of a view managed by: RNSVGPath
Parece que o erro ocorre em qualquer dispositivo e em qualquer versão do Android, a julgar pelo relatório Sentry.
A maioria das falhas do Android 8.0.0 falha, mas isso é consistente com nossa base de usuáriosVamos jogar de volta!
Então, o primeiro passo antes de corrigir o erro é reproduzi-lo, certo? Felizmente, graças aos logs do Sentry, podemos descobrir o que os usuários fazem antes que ocorra uma falha.
Ta-a-ak, vamos ver ...

Hmm, na grande maioria dos casos, os usuários simplesmente abrem o aplicativo e - boom, ocorre uma falha.
Ok, vamos tentar novamente. Instalamos o aplicativo em seis dispositivos Android, abrimos e saímos várias vezes. Nenhuma falha! Além disso, é impossível reproduzi-lo localmente no modo dev.
Ok, isso parece inútil. As falhas ainda são bastante aleatórias e ocorrem em 10% dos casos. Parece que você tem 1 em 10 chances de o aplicativo travar na inicialização.
Análise de rastreamento de pilha
Para reproduzir essa falha, vamos tentar entender de onde ela vem ...
Como mencionado anteriormente, temos vários erros diferentes. E todo mundo tem traços semelhantes, mas um pouco diferentes.
Ok, vamos dar o primeiro:
 java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1 at android.support.v4.util.Pools$SimplePool.release(Pools.java:116) at com.facebook.react.bridge.DynamicFromMap.recycle(DynamicFromMap.java:40) at com.facebook.react.uimanager.LayoutShadowNode.setHeight(LayoutShadowNode.java:168) at java.lang.reflect.Method.invoke(Method.java) ... java.lang.reflect.InvocationTargetException: null at java.lang.reflect.Method.invoke(Method.java) ... com.facebook.react.bridge.JSApplicationIllegalArgumentException: Error while updating property 'height' in shadow node of type: RNSVGSvgView at com.facebook.react.uimanager.ViewManagersPropertyCache$PropSetter.updateShadowNodeProp(ViewManagersPropertyCache.java:113) ... 
Portanto, o problema está em 
android/support/v4/util/Pools.java .
Hmm, nós somos muito profundos na biblioteca de suporte do Android, dificilmente é possível obter qualquer benefício aqui.
Encontre outra maneira
Outra maneira de encontrar a causa raiz do erro é verificar se há novas alterações na versão mais recente. Especialmente aqueles que afetam o código nativo do Android. Duas hipóteses surgem:
- Atualizamos a Navegação nativa , onde fragmentos nativos para Android são usados para cada tela.
- Atualizamos react-native-svg . Havia algumas exceções relacionadas aos componentes SVG, mas esse não é o caso.
Não podemos reproduzir o erro no momento, então a melhor estratégia é:
- Reverta uma das duas bibliotecas e reveja para 10% dos usuários, o que é trivial na Play Store. Verifique com vários usuários se a falha persistir. Assim, confirmamos ou refutamos a hipótese.
  
 
 Mas como escolher uma biblioteca para reverter? Claro, você pode jogar uma moeda, mas essa é a melhor opção?
 
 
 Vá direto ao ponto
 Vamos dar uma olhada no rastreamento anterior. Talvez isso ajude a determinar a biblioteca.
 
   public static class SimplePool implements Pool { private final Object[] mPool; private int mPoolSize; ... @Override public boolean release(T instance) { if (isInPool(instance)) { throw new IllegalStateException("Already in the pool!"); } if (mPoolSize < mPool.length) { mPool[mPoolSize] = instance; mPoolSize++; return true; } return false; }
 
 Houve um fracasso. Errojava.lang.ArrayIndexOutOfBoundsException: length=10; index=-1java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1significa quemPoolé uma matriz de tamanho 10, masmPoolSize=-1.
 
 Ok, comomPoolSize=-1? Além do método derecycleacima, o único local para alterar omPoolSizeé o método deSimplePoolclasseSimplePool:
 
  public T acquire() { if (mPoolSize > 0) { final int lastPooledIndex = mPoolSize - 1; T instance = (T) mPool[lastPooledIndex]; mPool[lastPooledIndex] = null; mPoolSize--; return instance; } return null; }
 
 Portanto, a única maneira de obter um valormPoolSizenegativo é reduzi-lo commPoolSize=0. Mas como isso é possível com a condiçãomPoolSize > 0?
 
 Colocaremos pontos de interrupção no Android Studio e veremos o que acontece quando o aplicativo é iniciado. Quero dizer, aqui está a condiçãoif, este código deve funcionar bem!
 
 Finalmente, uma revelação!
 
 ConsulteDynamicFromMaplink estático para oSimplePool.
 
  private static final Pools.SimplePool<DynamicFromMap> sPool = new Pools.SimplePool<>(10);
 
 Após várias dezenas de cliques no botão Reproduzir com pontos de interrupção definidos com cuidado, vemos que os threads mqt_native_modules chamam as funçõesSimplePool.releaseeSimplePool.releaseusando React Native para controlar as propriedades de estilo do componente React (abaixo da propriedadewidthdo componente)
 
  
 
 Mas eles também são acessados pelo stream principal main !
 
  
 
 Acima, vemos que eles são usados para atualizar a propriedadefillno fluxo principal, geralmente para o componentereact-native-svg! De fato, a bibliotecareact-native-svgcomeçou a usar oDynamicFromMapapenas com a sétima versão para melhorar o desempenho das animações svg nativas.
 
 And-and-and ... uma função pode ser chamada de dois threads, masDynamicFromMapnão usa oSimplePoolde maneira segura para threads. "Segmento seguro", diz?
 
 Segurança da linha, um pouco de teoria
 No JavaScript de thread único, os desenvolvedores geralmente não precisam lidar com a segurança do thread.
 
 Java, por outro lado, suporta o conceito de programas paralelos ou multithread. Vários encadeamentos podem ser executados no mesmo programa e podem acessar potencialmente a estrutura geral de dados, o que às vezes leva a resultados inesperados.
 
 Veja um exemplo simples: a imagem abaixo mostra que os fluxos A e B são paralelos:
 
 - leia um número inteiro;
- aumentar seu valor;
- devolva-o.
 
 
 O fluxo B pode potencialmente acessar o valor dos dados antes que o fluxo A o atualize. Esperávamos duas etapas separadas para fornecer um valor final de19. Em vez disso, podemos obter18. Uma situação em que o estado final dos dados depende da ordem relativa das operações de fluxo é chamada de condição de corrida. O problema é que essa condição não ocorre necessariamente o tempo todo. Talvez, no caso acima, o encadeamento B tenha outro trabalho antes de continuar a aumentar o valor, o que fornece tempo suficiente para o encadeamento A atualizar o valor. Isso explica a aleatoriedade e a incapacidade de reproduzir a falha.
 
 Uma estrutura de dados é considerada segura para threads se as operações puderem ser executadas simultaneamente por vários threads sem o risco de uma condição de corrida.
 
 Quando um encadeamento lê um elemento de dados específico, outro encadeamento não deve ter o direito de modificar ou excluir esse elemento (isso é chamado atomicidade). No exemplo anterior, se os ciclos de atualização fossem atômicos, as condições da corrida poderiam ter sido evitadas. O segmento B aguardará até que o segmento A conclua a operação e inicie a si próprio.
 
 No nosso caso, isso pode acontecer:
 
 Como oDynamicFromMapcontém um link estático para oSimplePool, várias chamadas deDynamicFromMapvêm de diferentes segmentos, enquanto invocam o método deacquirenoSimplePool.
 
 Na ilustração acima, o segmento A chama o método, avaliando a condição como verdadeira , mas ainda não conseguiu reduzir o valor demPoolSize(que é usado em conjunto com o segmento B), enquanto o segmento B também chama esse método e também avalia a condição como verdadeira . Posteriormente, cada chamada reduzirá o valor demPoolSize, resultando no valor "impossível".
 
 Correção
 Estudando as opções de correção, encontramos uma solicitação de pool para nativo de reação , que ainda não ingressou na ramificação - e fornece segurança de encadeamento neste caso.
 
  
 
 Em seguida, lançamos uma versão fixa do React Native para usuários. O acidente está finalmente resolvido, um brinde!
 
 
 Portanto, graças à ajuda de Jenick Duplessis (colaborador do núcleo do React Native) e Michael Sand (mantenedor do reactreact-native-svg), o patch está incluído na próxima versão menor do React Native 0.57 .
 
 Demorou algum esforço para corrigir esse bug, mas foi uma grande oportunidade para aprofundar-se no react-native e react-native-svg. Um bom depurador e alguns pontos de interrupção bem colocados são importantes. Espero que você também tenha aprendido algo útil com essa história!