java - Especificaciones de Servlet 3 y ThreadLocal
multithreading servlets (6)
¡Eres psíquico! (+1 para eso)
Mi objetivo es ... obtener una prueba de que esto ha dejado de funcionar en el contenedor Servlet 3.0
Here está la prueba que estabas pidiendo.
Por cierto, está utilizando exactamente el mismo filtro OEMIV que mencionaste en tu pregunta y, adivina qué, ¡rompe el procesamiento del servlet Async!
Editar: Aquí hay another prueba.
Hasta donde yo sé, la especificación de Servlet 3 introduce la función de procesamiento asíncrono. Entre otras cosas, esto significará que el mismo hilo puede y será reutilizado para procesar otra (s) solicitud (es) HTTP concurrentes. Esto no es revolucionario, al menos para las personas que trabajaron con NIO antes.
De todos modos, esto lleva a otra cosa importante: no ThreadLocal
variables ThreadLocal
como almacenamiento temporal para los datos de solicitud. Porque si el mismo hilo se convierte de repente en el hilo de la portadora a una solicitud HTTP diferente, los datos locales de la solicitud se verán expuestos a otra solicitud.
Todo eso es pura especulación basada en leer artículos, no tengo tiempo para jugar con ninguna implementación de Servlet 3 (Tomcat 7, GlassFish 3.0.X, etc.).
Entonces, las preguntas:
- ¿Estoy en lo cierto al suponer que
ThreadLocal
dejará de ser un truco conveniente para guardar los datos de solicitud? - ¿Alguien ha jugado con alguna de las implementaciones de Servlet 3 y ha intentado usar
ThreadLocal
s para probar lo anterior? - Además de almacenar datos dentro de la sesión HTTP, ¿hay algún otro truco similar de fácil acceso que pueda aconsejar?
EDITAR: no me malinterpreten Entiendo completamente los peligros y ThreadLocal
es un truco. De hecho, siempre desaconsejo usarlo en un contexto similar. Sin embargo, lo creas o no, el contexto del hilo se ha usado con mucha más frecuencia de lo que probablemente te imaginas. Un buen ejemplo sería Spring''s OpenSessionInViewFilter
que, según su Javadoc:
Este filtro hace que las sesiones de Hibernate estén disponibles a través del hilo actual, que será detectado automáticamente por los administradores de transacciones.
Esto no es estrictamente ThreadLocal
(no se ha verificado la fuente) pero ya suena alarmante. Puedo pensar en escenarios más similares, y la abundancia de marcos web hace que esto sea mucho más probable.
En pocas palabras, muchas personas han construido sus castillos de arena en la parte superior de este truco, con o sin conciencia. Por lo tanto, la respuesta de Stephen es comprensible, pero no es lo que busco. Me gustaría obtener una confirmación de si alguien realmente ha intentado y ha sido capaz de reproducir el comportamiento defectuoso, por lo que esta pregunta podría utilizarse como un punto de referencia para otros atrapados por el mismo problema.
(Advertencia: no he leído la especificación de Servlet 3 en detalle, así que no puedo decir con certeza si la especificación dice lo que piensas que es. Solo estoy suponiendo que sí ...)
¿Estoy en lo cierto al suponer que ThreadLocal dejará de ser un truco conveniente para guardar los datos de solicitud?
El uso de ThreadLocal
siempre fue un enfoque deficiente, porque siempre corría el riesgo de que la información se filtrara cuando un hilo de trabajo terminaba una solicitud y comenzaba otra. Almacenar cosas como atributos en el objeto ServletRequest
siempre fue una mejor idea.
Ahora simplemente tienes otra razón para hacerlo de la manera "correcta".
¿Alguien ha jugado con alguna de las implementaciones de Servlet 3 y ha intentado usar ThreadLocals para probar lo anterior?
Ese no es el enfoque correcto. Solo le informa sobre el comportamiento particular de una implementación particular bajo las circunstancias particulares de su prueba. No puedes generalizar
El enfoque correcto es suponer que a veces sucederá si la especificación dice que puede ... y diseñar su aplicación web para tenerlo en cuenta.
(¡Miedo!) Aparentemente, en este caso, esto no ocurre de forma predeterminada. Su aplicación web tiene que habilitar explícitamente la función de procesamiento asíncrono. Si su código está infestado con locales de subprocesos, se le aconsejará que no haga esto ...
Además de almacenar datos dentro de la sesión HTTP, ¿hay algún otro truco similar de fácil acceso que pueda aconsejar?
Nop. La única respuesta correcta es almacenar datos específicos de la solicitud en el objeto ServletRequest o ServletResponse. Incluso almacenarlo en la sesión HTTP puede ser incorrecto, ya que puede haber varias solicitudes activas al mismo tiempo para una sesión determinada.
El procesamiento asincrónico no debería molestarte a menos que lo solicites explícitamente.
Por ejemplo, la solicitud no se puede realizar de manera asincrónica si el servlet o cualquiera de los filtros en la cadena de filtros de la solicitud no está marcado con <async-supported>true</async-supported>
. Por lo tanto, aún puede usar prácticas regulares para solicitudes regulares.
Por supuesto, si realmente necesita un procesamiento asincrónico, debe utilizar las prácticas adecuadas. Básicamente, cuando la solicitud se procesa de forma asíncrona, su procesamiento se divide en partes. Estas partes no comparten el estado local del subproceso, sin embargo, aún puede usar el estado local del subproceso dentro de cada una de esas partes, aunque debe administrar el estado manualmente entre las partes.
NOTA: los piratas siguen. Usar con precaución, o simplemente no usar.
Mientras continúe entendiendo en qué hilo se está ejecutando su código, no hay razón para que no pueda usar un ThreadLocal de manera segura.
try {
tl.set(value);
doStuffUsingThreadLocal();
} finally {
tl.remove();
}
No es como si tu pila de llamadas se cambiara aleatoriamente. Diablos, si hay valores de ThreadLocal que desea establecer en profundidad en la pila de llamadas y luego usar más, también puede piratearlos:
public class Nasty {
static ThreadLocal<Set<ThreadLocal<?>>> cleanMe =
new ThreadLocal<Set<ThreadLocal<?>>>() {
protected Set<ThreadLocal<?>> initialValue() {
return new HashSet<ThreadLocal<?>>();
}
};
static void register(ThreadLocal<?> toClean) {
cleanMe.get().add(toClean);
}
static void cleanup() {
for(ThreadLocal<?> tl : toClean)
tl.remove();
toClean.clear();
}
}
Luego registras tus ThreadLocals como las configuras, y la limpieza en una cláusula finally en algún lugar. Esto es toda una vergüenza vergonzosa que probablemente no deberías hacer. Lamento haberlo escrito, pero ya es demasiado tarde:
Todavía me pregunto por qué las personas usan la API javax.servlet podrida para implementar realmente sus servlets. Lo que hago:
Tengo una clase base
HttpRequestHandler
que tiene campos privados para solicitud, respuesta y un métodohandle()
que puede arrojar excepciones más algunos métodos de utilidad para obtener / establecer parámetros, atributos, etc. Rara vez necesito más del 5-10% del servlet API, así que esto no es tanto trabajo como parece.En el manejador de servlets, creo una instancia de esta clase y luego me olvido de la API de servlet.
Puedo extender esta clase de controlador y agregar todos los campos y datos que necesito para el trabajo. Sin grandes listas de parámetros, sin hacking local de hilos, sin preocupaciones sobre concurrencia.
Tengo una clase de utilidad para pruebas unitarias que crea un
HttpRequestHandler
con implementaciones simuladas de solicitud y respuesta. De esta manera, no necesito un entorno de servlet para probar mi código.
Esto resuelve todos mis problemas porque puedo obtener la sesión DB y otras cosas en el método init()
o puedo insertar una fábrica entre el servlet y el manejador real para hacer cosas más complejas.
Una solución es no utilizar ThreadLocal, sino utilizar un singleton que contenga una matriz estática de los objetos que desee convertir en globales. Este objeto contendría un campo "threadName" que establezca. Primero establece el nombre del subproceso actual (en doGet, doPost) en algún valor único aleatorio (como un UUID), y luego lo almacena como parte del objeto que contiene los datos que desea almacenar en el singleton. Entonces, cada vez que una parte de su código necesita acceder a los datos, simplemente pasa por la matriz y busca el objeto con el threadName que se está ejecutando actualmente y recupera el objeto. Necesitará agregar algún código de limpieza para eliminar el objeto de la matriz cuando se complete la solicitud http.