¿Es seguro iniciar un nuevo hilo en un frijol administrado por JSF?


No pude encontrar una respuesta definitiva a si es seguro generar hilos dentro de los frijoles administrados de JSF con alcance de sesión. El subproceso necesita llamar a métodos en la instancia EJB sin estado (que fue inyectada por dependencia al bean administrado).

El fondo es que tenemos un informe que tarda mucho tiempo en generarse. Esto causó que la solicitud HTTP se agotara debido a la configuración del servidor que no podemos cambiar. Así que la idea es iniciar un nuevo hilo y dejar que genere el informe y temporalmente guárdalo. Mientras tanto, la página de JSF muestra una barra de progreso, sondea el frijol administrado hasta que la generación se complete y luego hace una segunda solicitud para descargar el informe almacenado. Esto parece funcionar, pero me gustaría estar seguro de que lo que estoy haciendo no es un truco.

Author: Dmitry Chornyi, 2011-05-27

3 answers

Introducción

Generar hilos desde dentro de un bean administrado con alcance de sesión no es necesariamente un hack siempre y cuando haga el trabajo que desea. Pero los hilos de desove en su propio necesita ser hecho con extremo cuidado. El código no debe escribirse de tal manera que un solo usuario pueda, por ejemplo, generar una cantidad ilimitada de subprocesos por sesión y/o que los subprocesos continúen ejecutándose incluso después de que la sesión se destruya. Tarde o temprano volaría tu solicitud.

El el código debe escribirse de manera que pueda asegurarse de que un usuario, por ejemplo, nunca pueda generar más de un subproceso en segundo plano por sesión y que el subproceso se interrumpa siempre que se destruya la sesión. Para varias tareas dentro de una sesión, debe poner en cola las tareas.

Además, todos esos subprocesos deben ser servidos preferiblemente por un grupo de subprocesos comunes para que pueda poner un límite a la cantidad total de subprocesos generados a nivel de aplicación. El promedio de Java EE el servidor de aplicaciones ofrece un grupo de subprocesos administrado por contenedor que puede utilizar a través de EJB, entre otros @Asynchronous y @Schedule. Para ser independiente del contenedor, también puede usar el concurrente Util de Java 1.5ExecutorService y ScheduledExecutorService por esto.

Los ejemplos siguientes asumen Java EE 6+ con EJB.

Disparar y olvidar una tarea en el formulario enviar

@Named
@RequestScoped // Or @ViewScoped
public class Bean {

    @EJB
    private SomeService someService;

    public void submit() {
        someService.asyncTask();
        // ... (this code will immediately continue without waiting)
    }

}
@Stateless
public class SomeService {

    @Asynchronous
    public void asyncTask() {
        // ...
    }

}

Obtenga el modelo de forma asíncrona al cargar la página

@Named
@RequestScoped // Or @ViewScoped
public class Bean {

    private Future<List<Entity>> asyncEntities;

    @EJB
    private EntityService entityService;

    @PostConstruct
    public void init() {
        asyncEntities = entityService.asyncList();
        // ... (this code will immediately continue without waiting)
    }

    public List<Entity> getEntities() {
        try {
            return asyncEntities.get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new FacesException(e);
        } catch (ExecutionException e) {
            throw new FacesException(e);
        }
    }
}
@Stateless
public class EntityService {

    @PersistenceContext
    private EntityManager entityManager;

    @Asynchronous
    public Future<List<Entity>> asyncList() {
        List<Entity> entities = entityManager
            .createQuery("SELECT e FROM Entity e", Entity.class)
            .getResultList();
        return new AsyncResult<>(entities);
    }

}

En caso de que usando JSF utility library OmniFaces , esto podría hacerse aún más rápido si anota el bean administrado con @Eager.

Programar trabajos en segundo plano al iniciar la aplicación

@Singleton
public class BackgroundJobManager {

    @Schedule(hour="0", minute="0", second="0", persistent=false)
    public void someDailyJob() {
        // ... (runs every start of day)
    }

    @Schedule(hour="*/1", minute="0", second="0", persistent=false)
    public void someHourlyJob() {
        // ... (runs every hour of day)
    }

    @Schedule(hour="*", minute="*/15", second="0", persistent=false)
    public void someQuarterlyJob() {
        // ... (runs every 15th minute of hour)
    }

    @Schedule(hour="*", minute="*", second="*/30", persistent=false)
    public void someHalfminutelyJob() {
        // ... (runs every 30th second of minute)
    }

}

Actualización continua del modelo de aplicación en segundo plano

@Named
@RequestScoped // Or @ViewScoped
public class Bean {

    @EJB
    private SomeTop100Manager someTop100Manager;

    public List<Some> getSomeTop100() {
        return someTop100Manager.list();
    }

}
@Singleton
@ConcurrencyManagement(BEAN)
public class SomeTop100Manager {

    @PersistenceContext
    private EntityManager entityManager;

    private List<Some> top100;

    @PostConstruct
    @Schedule(hour="*", minute="*/1", second="0", persistent=false)
    public void load() {
        top100 = entityManager
            .createNamedQuery("Some.top100", Some.class)
            .getResultList();
    }

    public List<Some> list() {
        return top100;
    }

}

Véase también:

 42
Author: BalusC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:25:51

Echa un vistazo a EJB 3.1 @Asynchronous methods. Esto es exactamente para lo que están.

Pequeño ejemplo que usa OpenEJB 4.0.0-SNAPSHOTs. Aquí tenemos un frijol @Singleton con un método marcado @Asynchronous. Cada vez que ese método es invocado por alguien, en este caso su JSF managed bean, volverá inmediatamente sin importar cuánto tiempo tome realmente el método.

@Singleton
public class JobProcessor {

    @Asynchronous
    @Lock(READ)
    @AccessTimeout(-1)
    public Future<String> addJob(String jobName) {

        // Pretend this job takes a while
        doSomeHeavyLifting();

        // Return our result
        return new AsyncResult<String>(jobName);
    }

    private void doSomeHeavyLifting() {
        try {
            Thread.sleep(SECONDS.toMillis(10));
        } catch (InterruptedException e) {
            Thread.interrupted();
            throw new IllegalStateException(e);
        }
    }
}

Aquí hay un pequeño testcase que invoca ese método @Asynchronous varias veces seguidas.

Cada invocación devuelve un Future objeto que esencialmente comienza empty y más tarde tendrá su valor rellenado por el contenedor cuando la llamada al método relacionado realmente se complete.

import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class JobProcessorTest extends TestCase {

    public void test() throws Exception {

        final Context context = EJBContainer.createEJBContainer().getContext();

        final JobProcessor processor = (JobProcessor) context.lookup("java:global/async-methods/JobProcessor");

        final long start = System.nanoTime();

        // Queue up a bunch of work
        final Future<String> red = processor.addJob("red");
        final Future<String> orange = processor.addJob("orange");
        final Future<String> yellow = processor.addJob("yellow");
        final Future<String> green = processor.addJob("green");
        final Future<String> blue = processor.addJob("blue");
        final Future<String> violet = processor.addJob("violet");

        // Wait for the result -- 1 minute worth of work
        assertEquals("blue", blue.get());
        assertEquals("orange", orange.get());
        assertEquals("green", green.get());
        assertEquals("red", red.get());
        assertEquals("yellow", yellow.get());
        assertEquals("violet", violet.get());

        // How long did it take?
        final long total = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);

        // Execution should be around 9 - 21 seconds
        assertTrue("" + total, total > 9);
        assertTrue("" + total, total < 21);
    }
}

Ejemplo de código fuente

Bajo las cubiertas lo que hace que este trabajo es:

  • El JobProcessor que ve el llamante no es realmente una instancia de JobProcessor. Más bien es una subclase o proxy que tiene todos los métodos anulados. Los métodos que se supone que son asíncronos se manejan diferente.
  • Las llamadas a un método asincrónico simplemente resultan en la creación de un Runnable que envuelve el método y los parámetros que dio. Este ejecutable se le da a un Ejecutor que es simplemente una cola de trabajo adjunta a un grupo de subprocesos.
  • Después de agregar el trabajo a la cola, la versión proxy del método devuelve una implementación de Future que está vinculada a Runnable que ahora está esperando en la cola.
  • Cuando Runnable finalmente ejecuta el método en el real JobProcessor instancia, tomará el valor devuelto y lo establecerá en el Future poniéndolo a disposición del llamante.

Es importante tener en cuenta que el objeto AsyncResult que devuelve JobProcessor no es el mismo objeto Future que el llamador está sosteniendo. Habría sido genial si el verdadero JobProcessor pudiera devolver String y la versión de la persona que llama de JobProcessor pudiera devolver Future<String>, pero no vimos ninguna manera de hacerlo sin agregar más complejidad. Así que el AsyncResult es un objeto de envoltura simple. El el contenedor sacará el String, tirará el AsyncResult lejos, luego pondrá el String en el real Future que la persona que llama está sosteniendo.

Para obtener progreso en el camino, simplemente pase un objeto seguro para subprocesos como AtomicInteger al método @Asynchronous y haga que el código bean lo actualice periódicamente con el porcentaje completo.

 52
Author: David Blevins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-04 23:27:07

Probé esto y funciona muy bien desde mi JSF gestionado frijol

ExecutorService executor = Executors.newFixedThreadPool(1);

@EJB
private IMaterialSvc materialSvc;

private void updateMaterial(Material material, String status,  Location position) {

    executor.execute(new Runnable() {
        public void run() {
            synchronized (position) {
                // TODO update material in audit? do we need materials in audit?
                int index = position.getMaterials().indexOf(material);
                Material m = materialSvc.getById(material.getId());
                m.setStatus(status);
                m = materialSvc.update(m);
                if (index != -1) {
                    position.getMaterials().set(index, m);
                }

            }
        }
    });

}

@PreDestroy
public void destory() {
    executor.shutdown();
}
 -4
Author: Sacky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-18 02:46:08