The Setup:
I'm trying to show the progress of a scheduled task in my servlet response. I have a simple test setup that uses three classes to "increment state" of a task for 20 seconds (at 4 second intervals on the minute):
Scheduler:
import javax.annotation.PostConstruct;
import javax.ejb.Schedule;
import javax.ejb.Singleton;
@Singleton
public class TaskScheduler {
private Task task;
@PostConstruct
public void init() {
task = new Task();
}
@Schedule(hour="*", minute="*", second="0")
public void run() {
(task = new Task()).run(); // no new Thread, this runs in-line
}
public String getState() {
return task.getState();
}
}
Task:
import java.util.Date;
public class Task implements Runnable {
private volatile String state = String.format("%s: %s\n",
Thread.currentThread().getName(),
new Date());
public String getState() {
return state;
}
@Override
public void run() {
long end = System.currentTimeMillis() + 20000;
while (System.currentTimeMillis() < end) {
String s = Thread.currentThread().getName();
try {
Thread.sleep(4000);
} catch (InterruptedException ex) {
s = ex.getMessage();
}
state += String.format("%s: %s\n",
s,
new Date());
}
}
}
Servlet:
import java.io.IOException;
import java.util.Date;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/simple")
public class SimpleServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@EJB
private TaskScheduler scheduler;
private String prefix = String.format("%s constructed at %s\n",
Thread.currentThread().getName(),
new Date());
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
prefix += String.format("%s served at %s\n",
Thread.currentThread().getName(),
new Date());
String s = String.format("%s%s",
prefix,
scheduler.getState());
resp.getOutputStream().write(s.getBytes());
}
}
The Problem:
While the task is idle, doGet returns immediately with appropriate timestamps/etc but while the task is in progress it is delayed, as if blocking on access to the task's state.
Here's some actual sample output I copied from my browser during a delay:
http-listener-1(3) constructed at 2014-09-11 17:01:36.600
http-listener-1(3) inited at 2014-09-11 17:01:36.601
http-listener-1(3) served at 2014-09-11 17:01:36.601
http-listener-1(1) served at 2014-09-11 17:01:56.174
http-listener-1(2) served at 2014-09-11 17:01:57.541
http-listener-1(4) served at 2014-09-11 17:01:58.558
http-listener-1(3) served at 2014-09-11 17:01:59.444
http-listener-1(3): 2014-09-11 17:01:36.603
and here's the output that came (all at once) after the delay:
http-listener-1(3) constructed at 2014-09-11 17:01:36.600
http-listener-1(3) inited at 2014-09-11 17:01:36.601
http-listener-1(3) served at 2014-09-11 17:01:36.601
http-listener-1(1) served at 2014-09-11 17:01:56.174
http-listener-1(2) served at 2014-09-11 17:01:57.541
http-listener-1(4) served at 2014-09-11 17:01:58.558
http-listener-1(3) served at 2014-09-11 17:01:59.444
http-listener-1(5) served at 2014-09-11 17:02:00.502
__ejb-thread-pool2: 2014-09-11 17:02:00.004
__ejb-thread-pool2: 2014-09-11 17:02:04.005
__ejb-thread-pool2: 2014-09-11 17:02:08.006
__ejb-thread-pool2: 2014-09-11 17:02:12.006
__ejb-thread-pool2: 2014-09-11 17:02:16.006
Things I've tried:
- removing the "volatile" keyword on Task's "state"
- adding `@Lock(LockType.READ)` to the Scheduler's getState method
- adding `@Asynchronous` to the Scheduler's run method
I'm deploying to a local Glassfish server (version 4.0, to match my target environment). I got the gist of how to use the @Schedule
annotation from this SO question and the gist of Lock
annotations from this SO question.
The Resolution:
Singleton
classes default to @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
and all their methods default to @Lock(LockType.WRITE)
. When execution enters a LockType.WRITE
method it causes the execution of any other methods to wait. You can override this at the class level with @ConcurrencyManagement(ConcurrencyManagementType.BEAN)
or by annotating all methods that are suitable for concurrent access with @Lock(LockType.READ)
.