Una de las recomendaciones importantes en el desarrollo de código es la “no utilización de excepciones para evitar realizar validaciones.”
¿A qué me refiero?
A usar try/catch para no tener que escribir código que valide algo. Total, si se cae, en el catch retorno que es falso, si no se cayó, entonces retorno verdadero. Así no es necesario codificar rutinas especiales.
Vamos al caso
Como ya es costumbre en mi trabajo, y de las buenas costumbres, las pruebas de carga muestran la peor parte de las aplicaciones, y esta no fue la excepción, aunque el culpable era código de un componente de terceros.
Pruebas de carga
Treinta minutos de ejecución de un script grabado con [ACT], 50 usuarios concurrentes, 30 minutos. No vamos a dar todos los detalles, pero el uso de la CPU estaba “fluctuante”, y tenía unas subidas a 100% por varios segundos.
El siguiente es el grafico de performance monitor. Como hay más de 30 minutos, los segundos que la aplicación pasaba al 100% solo se ven como picos, pero en algunos casos llegaba a casi 40 segundos.
Te podrá haber llamado la atención que el uso de la CPU está muy extraño, como que no es fluido, considerando que una prueba de carga debiera mantenerla ocupada constantemente. No tengo la explicación ahora, pero al final veremos algo diferente.
Mientras revisaba contadores varios, me llamó la atención la cantidad de excepciones que se producían a veces, por lo tanto, agregué el contador de excepciones y voila!!. Problema encontrado.
Asombrosa similitud, ¿no?
A tomar dumps en las excepciones para ver que está ocurriendo. Mmm, mejor que no. Con 184 excepciones por segundo, reventamos el servidor. Mejor aún, utilizo un archivo de configuración para AdPlus, que viene en [windbg], el cual configuro para obtener información de threads, threadpool, stacks manejados y no manejados y excepciones cuando yo le solicite, que será cuando la CPU esté en 100%.
¿Resultado?
Esta es parte del stack no manejado, de un thread manejado. Como este, habían 10 más, es decir, 11 threads lanzando excepciones, a una asombrosa tasa de 184 por segundo.
22 Id: 3924.46b8 Suspend: 1 Teb: 7ffa5000 Unfrozen ChildEBP RetAddr Args to Child 06ece448 7c827ad6 7c8063cb ffffffff 7921ace1 ntdll!KiFastSystemCallRet 06ece44c 7c8063cb ffffffff 7921ace1 00000000 ntdll!NtQueryVirtualMemory+0xc 06ece4ac 7c8123b4 7921ace1 00000000 793e8730 ntdll!RtlIsValidHandler+0x82 06ece520 7c834d44 06ecc000 06ece530 00010007 ntdll!RtlDispatchException+0x78 06ece800 77e52db4 06ece810 050d5008 e0434f4d ntdll!RtlRaiseException+0x3d 06ece860 7921af79 e0434f4d 00000001 00000000 kernel32!RaiseException+0x53 06ece8b8 7921aefc 1194c7f0 00000000 06eceb14 mscorwks!RaiseTheException+0xa0 06ece8e0 7921aeb0 1194c7f0 00000000 06eceb24 mscorwks!RealCOMPlusThrow+0x48 06ece8f0 79218cc2 1194c7f0 00000000 0219d0b4 mscorwks!RealCOMPlusThrow+0xd 06eceb24 792b1893 00000018 00000001 00000000 mscorwks!CreateMethodExceptionObject+0x67b 06eceb58 79274773 00000018 79274778 06ecec08 mscorwks!RealCOMPlusThrow+0x35 06eceb68 79338b01 0216137a 000000e7 06eceb90 mscorwks!StringToNumber+0x7e 06ecec08 04266804 06ecec14 0f1604f0 000000e7 mscorwks!COMNumber::ParseDouble+0x32 |
Esta es parte del stack manejado, del mismo thread.
0x06ece908 0x7c834cf4 [FRAME: GCFrame] 0x06ecec38 0x7c834cf4 [FRAME: ECallMethodFrame] [DEFAULT] R8 System.Number.ParseDouble(String,ValueClass System.Globalization.NumberStyles,Class System.Globalization.NumberFormatInfo) 0x06ecec48 0x79a14e0f [DEFAULT] R8 System.Double.Parse(String,ValueClass System.Globalization.NumberStyles,Class System.IFormatProvider) 0x06ecec84 0x79a0e3cc [DEFAULT] R8 System.Convert.ToDouble(String) 0x06ecec88 0x06a30741 [DEFAULT] [hasThis] ValueClass System.Drawing.StringAlignment C1.Util.Styles.StyleContext.1M(String) 0x06ececc4 0x06a306cd [DEFAULT] [hasThis] ValueClass System.Drawing.StringAlignment C1.Util.Styles.StyleContext.GetGeneralAlignment(Class C1.Util.Styles.Style,String) 0x06ececd8 0x06a30246 [DEFAULT] [hasThis] Void C1.Util.Styles.StyleContext.UpdateStringFormat(Class C1.Util.Styles.Style,String) 0x06ececf8 0x0580f464 [DEFAULT] [hasThis] ValueClass System.Drawing.SizeF C1.Util.Styles.StyleContext.GetContentSize(Class C1.Util.Styles.Style,Class System.Drawing.Graphics,ValueClass System.Drawing.SizeF,String,Class System.Drawing.Image) 0x06eced40 0x06a31182 [DEFAULT] [hasThis] I4 C1.Util.Styles.StyleContext.GetContentWidth(Class C1.Util.Styles.Style,Class System.Drawing.Graphics,I4,String,Class System.Drawing.Image) |
La información de la excepción se muestra en la siguiente caja, y es equivalente a la presentada en el stack manejado.
Exception 0f69dff0 in MT 79bacb74: System.FormatException _message: Input string was not in a correct format. _stackTrace: 00000000 00000000 79bbb998 79a14eb0 [DEFAULT] R8 System.Double.Parse(String,ValueClass System.Globalization.NumberStyles,Class System.IFormatProvider) 06e8eca0 79bac7b0 79a0e3cb [DEFAULT] R8 System.Convert.ToDouble(String) 06e8ed20 79bbf6b0 06a30740 [DEFAULT] [hasThis] ValueClass System.Drawing.StringAlignment C1.Util.Styles.StyleContext.1M(String) 06e8ed24 0573bf28 |
¿Qué nos dice [reflector] del código en el método 1M?
![]() | No voy a entrar a analizar que hace o no el código. Lo único que puedo vez rápidamente es que el desarrollador que codificó estas líneas tenia ganas de irse rápido a la casa, como muchas veces me pasó a mí [:)]. ¿Por qué poner un try/catch en vez de hacer la validación que correspondería hacer? ¿Es tan difícil usar un expresión regular para validar decimales o doubles, algo así como “[-+]?[0-9]*\.?[0-9]+“? Muchas de los try/catch que son codificados podrían ser reemplazados por validaciones con ifs y elses, pero consume mucho más tiempo aprender cosas nuevas, y seguramente las pruebas en el computador del desarrollador no detectaron este problema [:)]. |
Como no fue posible corregir el código defectuoso, ya que estaba en un componente de terceros, procedimos a hacer unos ajustes y remover parte de la funcionalidad que invocaba ese método.
Nueva prueba de carga
El resultado obtenido habla por si solo. Consecuentemente, el grafico del uso de CPU ahora muestra un comportamiento más esperado para una prueba de carga. Y la gran cantidad de excepciones se fueron.
Conclusión
Tarde o temprano, los problemas aparecen. Es mejor invertir un par de horas en aprender algo que te podrá servir muchas veces a futuro, y dejar la chapucería para otro tipo de actividades.
Estas conclusiones están medias pobres, pero se debe a que no hay mucho más que decir. Los gráficos hablan por sí solos.
Adicionales
Si te interesa aprender de expresiones regulares, existe un muy buen libro y muchos sitios en internet. Personalmente uso el libro Mastering Regular Expressions.
Otros sitios web interesantes:
Saludos,
Patrick.