Resposta ao Desafio da Semana #7 [Hang de Alta CPU em C#/VB.NET]



Por: Roberto Alexis Farah


 


Eis a resposta ao Desafio da Semana 7.


Veja o link com a definição do desafio aqui


 


 


PROBLEMA


 


O problema é que o código erroneamente assumia que a string recebida como parâmetro sempre tinha um número.


Portanto, o loop varre os caracteres da string até encontrar um número, quando não há número o loop nunca é terminado, ou seja, loop infinito causando alto consumo de CPU.


Note que além do problema de se assumir que a string continha números, o código também não preve a situação onde a string está vazia, ocasionando o mesmo loop infinito!


Assim, embora o código funcione na maioria das vezes ele vai falhar quando a string for vazia ou não houver números dentro dela, gerando o sintoma de alta CPU.


 


SOLUÇÃO


 


Mesmo sem ter uma especificação sobre o que o código deveria fazer e como deveria reagir é possível se concluir que, se deveria processar strings sem número, ele não faz isso. E se deveria rejeitar strings sem números, ele também não faz isso.


No caso, supondo-se que a idéia seja de se evitar strings sem números (e vazias), uma possível solução seria:


 


     ‘Exemplo de parametros:


     ‘ABC!DEF#45GH


     ‘ABC!DEFxxxxAAAGH1


     ‘4fgfgjfdhg3fdgjfgjh1


     Public Function Crypto(ByVal someString As String) As String


              ‘ Inicializa a semente de nuvero aleatorio para sempre ser diferente.


              VBMath.Randomize()


 


 


 


              ‘ FIX: Verifica se ha numeros na string.


              ‘ Tambem evita a situacao de string vazia!


              ‘ Abordagem pouco invasiva, nao foi necessario se alterar o algoritmo original.


             ‘ FIX 2: Antes testa se string e´objeto NULL.


              If someString = Nothing OrElse Not Regex.IsMatch(someString, “[0-9]”) Then


                   Debug.Assert(True, “someString nao tem caracteres numericos”)


 


‘ Pode-se logar mensagem no Event Viewer antes…


                   Return vbNullString


              End If


 


 


 


              Dim num1 As Long = Strings.Len(someString)


 


              ‘ Cria uma chave usando um numero aleatorio e o tamanho da string.


              ‘ O numero nunca e’ maior que o tamanho da string.


              Dim text1 As String = StringType.FromLong(CType(Math.Round(CType(((num1 * VBMath.Rnd) + 1.0!), Double)), Long))


 


              ‘ Varre os caracteres da string apenas.


              Do While Not Char.IsNumber(CharType.FromString(Strings.Mid(someString, IntegerType.FromString(text1), 1)))


                     ‘ Efetua a mesma conversao.


                     text1 = StringType.FromLong(CType(Math.Round(CType(((num1 * VBMath.Rnd) + 1.0!), Double)), Long))


              Loop


 


              Crypto = text1


     End Function


 


 


Como curiosidade explicarei como o problema foi isolado no cenário real. Primeiro foi necessário se identificar via Performance Monitor quem eram os processos e threads consumindo alta CPU.


Em seguida coletei um Hang Dump do processo. A análise automatizada revelou a pilha (stack) das threads executando o código suspeito e, em seguida, disparando o Garbage Collector.


A primeira vista alguns pensaram que era um problema no GC. Na verdade, o GC estava sendo disparado justamente pelo número de threads travadas no loop.


Após isso, depurei o dump manualmente e extrai o assembly a partir do dump e converti o código do método suspeito para VB.NET.


O código era bastante complexo com muitas operações e conversões. Entretanto, via o dump foi possível se extrair a string usada como parâmetro, analisar o código e entender como a string seria processada. A string não continha caracteres numéricos.


 


Como recomendação embasada por diversos livros de programação;depuração e fazendo minhas as palavras do meu amigo Waldemir Cambiucci, consultor senior da MCS em São Paulo, coloco: Nunca assuma!


 


Nunca assuma quando investigando um problema e nunca assuma quando programando. Para isso, quando você considera que uma situação nunca deveria ocorrer, use Assert, seja em VB.NET, C++, C ou C# para que você receba uma notificação se o que você assumiu como verdadeiro for falso (modo Debug) e eventualmente você pode prevenir problemas em versões Release testando a condição e registrando uma mensagem no Event Viewer, por exemplo.


 


Programando proativamente, usando Assert e trace (logs) ajuda a se evitar bugs e a pegá-los mais rapidamente.


No código original, o simples uso de Assert teria feito o problema ser identificado quando testado em modo debug e o uso de mensagens de trace teria tornado fácil identificar o problema em ambiente de produção.


 


Eis alguns links sobre o assunto:


 


http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/_core_Debugging_Assertions.asp


 


http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vxtskverifyingassertionsinmanagedcode.asp


 


http://msdn.microsoft.com/msdnmag/issues/01/10/bugslayer/


 


http://msdn.microsoft.com/msdnmag/issues/05/11/Bugslayer/default.aspx


 


 


 

Comments (1)

  1. Roberto Farah says:

    Pessoal, fiz uma importante correção no algoritmo que me ocorreu ao corrigir a mesma falha num código de um cliente e me dar conta que deixei passar na resposta! É necessário sempre se testar se o objeto é válido.

    Ufa, felizmente ninguém achou esse bug antes 🙂

    Obrigado.