Bueno, esta es una de esas cosas que pueden volver loco a uno antes de encontrar el motivo que lo está provocando.

Supón que tienes una aplicación Web que funciona de maravilla y que utiliza algunas variables de sesión para guardar datos del usuario (por ejemplo un identificador o cualquier otra cosa). Colocas la aplicación en producción en un servidor Windows 2003 Server y todo parece ir perfecto. El servidor es IIS 6.0 y la aplicación está escrita en ASP.NET 2.0. A medida que la cantidad de peticiones y usuarios crece la aplicación funciona de maravilla y se comporta muy bien, gestionando muchas peticiones por segundo sin problema hasta que de repente un día tienes una llamada...

"Disculpe, soy un usuario de su aplicación y me hoy me ha pasado una cosa muy rara. He entrado con mi clave y nada más hacerlo me han aparecido los datos de otra empresa que no es la mía. Es como si se nos hubiesen cruzado las líneas. ¿no le estará pasando esto a alguien más y verán mis datos, verdad?"

Tragas saliva y tratas de justificar lo que ha pasado echñandole la culpa a los proxies-caché de Telefónica y cosas similares, y crees que te has librado. A los pocos días recibes otra llamda parecida... Y otra más algo después. No son muchas pero el problema parece recurrente y ya te empiezas a "acongojar". ¿Qué diablos está pasando aquí? Por más que buscas no hay manera de encontrar el origen del problema.

Ya puedes seguir buscando que no lo vas a encontrar...

El problema está en la caché en modo Kernel (núcleo) de IIS 6.0. Apuesto a que es esto lo primero que se te ha pasado por la mente ;-)

La caché en modo kernel de IIS 6.0

La caché en modo Kernel de IIS 6.0 es una novedad que apareció con IIS 6.0 en Windows 2003 Server y que está pensada para mejorar enormemente el desempeño del servidor al almacenar copias en memoria de recursos que no necesitan una transición entre el modo kernel y el modo usuario del sistema operativo. El "escuchador" de peticiones en modo Kernel de IIS 6.0 puede recibir una petición, comprobar si el recurso está en caché y devolverlo inmediatamente sin pasar por otros elementos más lentos y sin cambiar de contexto de funcionamiento. para que nos hagamos una idea, sólo con esto IIS 6.0 puede duplicar el rendimiento de páginas ASPX almacenadas en caché respecto a la versión anterior. O sea, que es estupendo.

Ahora vamos a estudiar brevemente cómo se comporta la caché de modo Kernel cuando trabajamos con páginas de ASP.NET 2.0  y éstas, claro está, tienen habilitada algún tipo de caché:

  1. Si la página tiene habilitada la caché total porque hemos especificado únicamente el parámetro VaryByParam=“None“, entonces ASP.NEt envía instrucciones a la caché de modo núcleo para que almacene su contenido. Ello hace que las siguientes peticiones de la misma página mientras dure su vida en la caché se envíen directamente desde ésta. Ello hace que sea un proceso rapidísimo y aumentamos mucho el rendimiento. Luego veremos los problemas que tenemos.
  2. Si se usa algún parámetro "Vary" con otros valores (como el propio VaryByparam o VaryByControl y VaryBycustom) no se usará la caché en modo kernel, sólo una caché "normal" de modo usuario, como en ASP.NET 1.x.

Según lo visto siempre que podamos es mejor usar una caché total para sacar partido a esta característica y así mejorar el rendimiento. Lo que ocurre es que no está exento de problemas.

El primero de ellos es que si actualizamos la página (o ya puestos el gráfico, script o cualqueir otro recurso "cacheado" de este modo) no notaremos el cambio en las peticiones hasta que pase un rato. Incluso aunque borremos un archivo puede que durante bastante tiempo sigamos recibiéndolo en el navegador como si aún existiera. Esto puede ser problemático, es evidente.

Otro problema adicional y menos evidente es que, al devolverse el contenido directamente desde la caché, no interviene para nada ASP.NET, por lo que olvídate de que se lancen eventos de la página. Así que si contabas con anotar algo en la base de datos, realizar cualquier acción al inicializar la página, etc... vete despidiendo pues no funcionará. Interesante ¿no?

Retomemos el problema inicial

Bueno, volvamos ahora veamos qué está pasando en el problema de pérdida de sesión (o más bien "cruce" de sesión) aparentemente aleatorio que se está produciendo en nuestra aplicación.

Todo viene por un "bug" existente en el módulo OutPutCacheModule de ASP.NET. Cuando éste en determinadas ocasiones proporciona a la caché de modo kernel el contenido de una página cacheada totalmente "se olvida" de eliminar del contenido la cabecera HTTP que establece la cookie de sesión del usuario y por lo tanto los siguientes que entran a ver la misma página reciben una copia que incluye dicha cabecera. Si esa página es la primera que éstos reciben del servidor la cabecera se toma como el identificador de sesión nuevo del usuario, reenviándose al servidor en las sigueintes peticiones. Dado que esa cabecera es la forma de identificar (por defecto) las sesiones, se les muestra el contenido del otro usuario. Suena un poco complicado pero así es...

Soluciones

La solución mas evidente es la de no usar nunca páginas cacheadas de manera íntegra. Es decir, trata de usar siempre VarByParam o alguno de los otros parámetros y no sólo el VaryByParam="None".

Si no te queda más remedio que usarlo puedes desconectar la caché en modo kernel para tu aplicación. Es muy fácil, sólo tienes que incluir esto en tu web.config:

<httpRuntime enableKernelOutputCache="false">

Puedes ver su documentación aquí.

También puedes cortar por lo sano, ser más radical, y desactivar la caché en modo kernel de golpe para todo el servidor. Sólo tienes que tocar el vbalor del registro UriEnableCache, y ya está.

En general no te recomiendo que desactives esta útil característica. Seguro que puedes encontrar otra solución. De todos modos la próxima vez que te encuentres con un problema grave de cruce de sesiones pregúntate a ti mismo "¿estaré usando caché completa en alguna página?". Si la respuesta es sí casi seguro que has encontrado la solución en este post y me vas a dejar una nota de agradecimiento :-)

Escrito por un humano, no por una IA