1

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).

Community
  • 1
  • 1
Jesse Hautala
  • 134
  • 10

1 Answers1

1

Using threads explicitly is generally no good in a EJB environment.

They populate/polute the server and might come out of control, causing problems for the server, because they are not controlled by the EJB container.

A better solution is to use the @Asynchronous annotation on a method of the singleton for example. With this you can start asynchronous tasks without problems for the server.

Edit: Reason, why the doGet() method is blocking:

When the Scheduler invokes the EJB's run() method, it will lock the Singleton EJB as a whole, as write protection is the default behavior. After entering run() the Task object's run() method will be called invoking Thread.sleep(...). Meanwhile the EJB's getState() method will be blocked until sleeping is finished, thus blocking the doGet() method of the WebServlet.

As the OP says in a later comment, this situation can be overcome by using an annotation @Lock(LockType.READ) above the Singleton's run() method (and above getState()).

codefan-BK
  • 310
  • 2
  • 9
  • I don't think I'm using an explicit Thread; where I call Task.run(), I expect it is executed inline. – Jesse Hautala Sep 11 '14 at 19:46
  • Ah, yes - I got confused by the interface Runnable. Could you give me a hint, why the Task class needs to be a Runnable, please? – codefan-BK Sep 11 '14 at 20:03
  • It's just a coincidence; it serves no purpose but it shows how I'm thinking :) – Jesse Hautala Sep 11 '14 at 20:10
  • When a Task object is created and run by the Singleton EJB, then it will run in the Thread which runs the EJB, right? So when the Task object invokes Thread.sleep, the EJB will be sleeping. Thus, accessing the getState() Method of the Singleton will force the doGet to wait, until sleeping is over. If you agree, I will add this comment to the answer, so that you can accept it. – codefan-BK Sep 11 '14 at 20:47
  • This is a dummy test-case so the EJB Thread is sleeping most of the time it's executing. I'll update the question with some detail from an actual test run. – Jesse Hautala Sep 11 '14 at 20:58
  • I don't think the "EJB itself" can be sleeping. The Thread that calls its run method is what's sleeping. You can't black all access to an Object's member variables by sleeping in one of its methods. You may be on to something though. I'll read up on it some more. I'll accept your answer if I determine it is accurate but (I know, against best practice) I tried using just kicking off a separate Thread in the Scheduler and it still seemed to block... – Jesse Hautala Sep 11 '14 at 21:15
  • So I found some more info about the Singleton annotation and it is, by default, configured for CONTAINER-level ConcurrencyManagement. I tried adding @Lock(LockType.READ) to the Scheduler's getState method and it didn't fix, which makes sense to me; I'm thinking the default configuration is equivalent to having all methods on a Singleton synchronized and @Lock(READ) is a way of letting the container know that method can be accessed concurrently. – Jesse Hautala Sep 11 '14 at 21:49
  • Yes, the methods of Singleton EJBs are synchronized by the container. So, when several http-listener-threads try to access the `TaskScheduler.getState()` method, they also have to wait on one another. But, when the `Task.getState()` method were not blocked itself (by `Thread.sleep(...)`) this were a condition occurring not so often as a simple getter method will not be time-consuming. – codefan-BK Sep 11 '14 at 22:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/61081/discussion-between-jesse-hautala-and-codefan). – Jesse Hautala Sep 11 '14 at 22:15
  • So the final revelation was that the whole EJB gets locked (it's not just synchronizing each method). I added @Lock(LockType.READ) to the Scheduler's run method and it worked! Many thanks codefan! – Jesse Hautala Sep 11 '14 at 22:35