1

I have some troubles.

For example, I have two actions: First and Second.

I have written simple utility, that uses executor service to send 100000 asynchronous requests to action First and to actions Second.

In First action I do:

HitCounter.increment();
ActionContext.getContext().getSession().put("counter", HitCounter.getAtomicCounter());
return Action.SUCCESS;

In second action I do:

System.out.println("From session: "+ActionContext.getContext().getSession().get("counter"));
System.out.println("Actual:"+ HitCounter.getAtomicCounter());
return Action.SUCCESS;

And the output I see(and it really makes me mad):

From session: 2 
Actual: 69352

After some time when I use this Fitst action/Second action only from my browsers and no concurrent requests come(generated by my load utility), results "are stabilized" to actual values. Thus, I have concurrency issues.

Is there a standard way that I should use to avoid problems with concurrency in Struts2 ?

P.S. HitCounter is thread safe, because it contains only one field and it's AtomicInteger.

P.P.S. HitCounter realisation:

public class HitCounter {
    private static AtomicInteger counter = new AtomicInteger(0);

    public static void increment() {
        counter.incrementAndGet();
    }
    public static int getAtomicCounter() {
        return counter.get();
    }
}

P.P.P.S. I removed Thread.yield(); but it didn't help. :(

hades
  • 1,077
  • 3
  • 11
  • 19

2 Answers2

1

Struts2 actions are thread safe in that they use thread local storage... that is there is a per-thread map from a variable to a value. Hence there is no shared mutable state and no synchronization is needed.

However a resource such as the Session can not be be treated this way and so care must be taken with concurrent access. This question discusses these issues: Using request.getSession() as a locking object?

According to this question: Is HttpSession thread safe, are set/get Attribute thread safe operations? That the servlet spec states that the following is thread safe: request.getSession().setAttribute("foo", 1);

However note that the above request is an HttpServletRequest which in turn derives an HttpSession object, this object is not a merely a map as returned by Struts2... Which means the struts2 version must be wrapped and as such may not adhere to the spec. As such we could implement ServletRequestAware which gives us a HttpServletRequest object from which we can derive an HttpSession. But that one line is not going to help us much as the previous questions point out, so implementing HttpServletRequest is going to be a waste of time. What looking all that up did is make me wonder how Struts2 reconciles the Map it returns for the Session with an HttpSession which does not implement Map, nor does it even have a method that returns a map...

So let us consider what may be happening currently...

line1: hitCounter.increment();
line2: ActionContext.getContext().getSession().put("counter", hitCounter.getAtomicCounter());

Line 1 : We increment some global hitCounter object.

Line 2a: We get a copy of the hitCounter object (int as return type).

Line 2b: We set the hitCounter value in the Session.

Because of the thread safe nature of HitCounter we know that line 1 is always good... but what about line2? We can see there are two parts, what if a thread is suspended between getting the hitCournter copy and setting the hitCounter, there is going to be a race condition... the last thread to execute from this point will win.

One way is putting the AtomicInteger into the session it self, this avoids the issue of a copy slipping in.

Community
  • 1
  • 1
Quaternion
  • 10,380
  • 6
  • 51
  • 102
  • Well, probably it could be like a hack to use thread safe singleton and never change it, thus it will be available to all threads. But this is just simplified case. I have much more complicated case in real-life system and I can't just use singleton object. So, have I understood right, that regardless that ActionContext uses thread local stuff, I still get global session object, that is not bound to thread and thus is not thread safe? Or I'm just trying to use it wrong and Struts2 uses have thought for me about that? – hades Apr 25 '12 at 10:04
  • You're right I came to my senses, a singleton would not work because you want it per-session. Simply add the counter to the session once, then increment it when necessary. I think I would just put an AtomicInteger called 'counter' in the session. – Quaternion Apr 25 '12 at 10:08
0

You are not retrieving the HitCounter in the second action.

System.out.println("From session: "+ActionContext.getContext().getSession().get("counter"));

The above line simply prints the object. Is there something you are missing in the code?

Dandy
  • 692
  • 4
  • 7
  • Probably I'm missing something, or you didn't understood. If I put something in whatever action, then I should be able to get it in same or in any other action. – hades Apr 25 '12 at 06:53
  • 1
    Does your Load Utility retain the sessionId over requests to First and Session Actions? i.e. the first time it hits the Action, a session would be generated and the HitCounter would be stored in that session. If that sessionId is not passed in the second request, then a new session would be created and it would have another instance of HitCounter. You won't have this problem when you hit from Browsers because browsers pass the sessionId through cookie. – Dandy Apr 25 '12 at 10:03
  • Yep, probably this is the problem. I'll need to rewrite my utility to use it right way and re-perform my tests. – hades Apr 25 '12 at 10:41
  • Yeap, it was problem with my stuff, that was not using session id :) – hades Apr 25 '12 at 21:38