28

As far as I know, Servlet 3 spec introduces asynchronous processing feature. Among other things, this will mean that the same thread can and will be reused for processing another, concurrent, HTTP request(s). This isn't revolutionary, at least for people who worked with NIO before.

Anyway, this leads to another important thing: no ThreadLocal variables as a temporary storage for the request data. Because if the same thread suddenly becomes the carrier thread to a different HTTP request, request-local data will be exposed to another request.

All of that is my pure speculation based on reading articles, I haven't got time to play with any Servlet 3 implementations (Tomcat 7, GlassFish 3.0.X, etc.).

So, the questions:

  • Am I correct to assume that ThreadLocal will cease to be a convenient hack to keep the request data?
  • Has anybody played with any of Servlet 3 implementations and tried using ThreadLocals to prove the above?
  • Apart from storing data inside HTTP Session, are there any other similar easy-to-reach hacks you could possibly advise?

EDIT: don't get me wrong. I completely understand the dangers and ThreadLocal being a hack. In fact, I always advise against using it in similar context. However, believe it or not, thread context has been used far more frequently than you probably imagine. A good example would be Spring's OpenSessionInViewFilter which, according to its Javadoc:

This filter makes Hibernate Sessions available via the current thread, which will be autodetected by transaction managers.

This isn't strictly ThreadLocal (haven't checked the source) but already sounds alarming. I can think of more similar scenarios, and the abundance of web frameworks makes this much more likely.

Briefly speaking, many people have built their sand castles on top of this hack, with or without awareness. Therefore Stephen's answer is understandable but not quite what I'm after. I would like to get a confirmation whether anyone has actually tried and was able to reproduce failing behaviour so this question could be used as a reference point to others trapped by the same problem.

Nick Bastin
  • 30,415
  • 7
  • 59
  • 78
mindas
  • 26,463
  • 15
  • 97
  • 154
  • a lot of the code "behind the scenes" uses thread local to associate the thread w/ the transaction. as long as you keep the code in `register()/try{}finally{unregister();}` you should be fine. After all you do manage your own state for the duration of the call, no spec can interference with that. The stackframe is all yours. Please, note that it's the `call` not the request. – bestsss Mar 02 '11 at 11:19
  • I disagree. This is lesser evil than binding a thread-specific data to entire request context, but still pretty much grey area as far as the servlet API contract is concerned. Contract provides no guarantees that your code is going to be executed by any particular thread (same or not) at any moment. For example, container can detect that your servlet code is blocking on some I/O and decide to switch current thread to fulfil another HTTP request. – mindas Mar 02 '11 at 12:29
  • *For example, container can detect that your servlet code is blocking on some I/O and decide to switch current thread to fulfil another HTTP request.* that's practically not possible in java. There are way too many reasons why the same thread cannot start another request. I will give you one only: any locked object will remain locked. Blocking on IO happens b/c there is data to be send or to be received, (baring that's virtually impossible) any interference will break the contract of the IO> – bestsss Mar 02 '11 at 16:16
  • the contract is not provided by the servlet spec. but the java execution stack frame. As long as it is java, I see no way (aside attaching debugger) to intervene w/ the execution between try/finally. – bestsss Mar 02 '11 at 16:22
  • My aim is actually not to find yet another way to hack this, but to get a proof this has stopped working in Servlet 3.0 container (e.g. Tomcat 7). Possible failing case is nice, but but it's just extra. – mindas Mar 02 '11 at 17:21

6 Answers6

10

Async processing shouldn't bother you unless you explcitly ask for it.

For example, request can't be made async if servlet or any of filters in request's filter chain is not marked with <async-supported>true</async-supported>. Therefore, you can still use regular practices for regular requests.

Of couse, if you actually need async processing, you need to use appropriate practices. Basically, when request is processed asynchronously, its processing is broken into parts. These parts don't share thread-local state, however, you can still use thread-local state inside each of that parts, though you have to manage the state manually between the parts.

axtavt
  • 239,438
  • 41
  • 511
  • 482
6

(Caveat: I've not read the Servlet 3 spec in detail, so I cannot say for sure that the spec says what you think it does. I'm just assuming that it does ...)

Am I correct to assume that ThreadLocal will cease to be a convenient hack to keep the request data?

Using ThreadLocal was always a poor approach, because you always ran the risk that information would leak when a worker thread finished one request and started on another one. Storing stuff as attributes in the ServletRequest object was always a better idea.

Now you've simply got another reason to do it the "right" way.

Has anybody played with any of Servlet 3 implementations and tried using ThreadLocals to prove the above?

That's not the right approach. It only tells you about the particular behaviour of a particular implementation under the particular circumstances of your test. You cannot generalize.

The correct approach is to assume that it will sometimes happen if the spec says it can ... and design your webapp to take account of it.

(Fear not! Apparently, in this case, this does not happen by default. Your webapp has to explicitly enable the async processing feature. If your code is infested with thread locals, you would be advised not to do this ...)

Apart from storing data inside HTTP Session, are there any other similar easy-to-reach hacks you could possibly advise.

Nope. The only right answer is storing request-specific data in the ServletRequest or ServletResponse object. Even storing it in the HTTP Session can be wrong, since there can be multiple requests active at the same time for a given session.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
3

NOTE: Hacks follow. Use with caution, or really just don't use.

So long as you continue to understand which thread your code is executing in, there's no reason you can't use a ThreadLocal safely.

try {
    tl.set(value);
    doStuffUsingThreadLocal();
} finally {
    tl.remove();
}

It's not as if your call stack is switched out randomly. Heck, if there are ThreadLocal values you want to set deep in the call stack and then use further out, you can hack that too:

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();
    }
}

Then you register your ThreadLocals as you set them, and cleanup in a finally clause somewhere. This is all shameful wankery that you shouldn't probably do. I'm sorry I wrote it but it's too late :/

wowest
  • 1,974
  • 14
  • 21
  • 1
    `try { tl.set(value);...`, should have `tl.set(value)` before `try` – bestsss Mar 02 '11 at 16:27
  • @bestsss - lol, are you complaining about style in this monstrosity? upboat for you! – wowest Mar 03 '11 at 16:06
  • not about the style but generally you commit the operation and then put try/finally to roll it back. Either way, I do not mind code bloat (unless it's blatant copy-paste), and most of the code is obvious enough, so no problems either. – bestsss Mar 03 '11 at 21:14
1

You are psychic ! (+1 for that)

My aim is ... to get a proof this has stopped working in Servlet 3.0 container

Here is the proof that you were asking for.

Incidentally, it is using the exact same OEMIV filter that you mentioned in your question and, guess what, it breaks Async servlet processing !

Edit: Here is another proof.

Community
  • 1
  • 1
2020
  • 2,821
  • 2
  • 23
  • 40
1

I'm still wondering why people use the rotten javax.servlet API to actually implement their servlets. What I do:

  • I have a base class HttpRequestHandler which has private fields for request, response and a handle() method that can throw Exception plus some utility methods to get/set parameters, attributes, etc. I rarely need more than 5-10% of the servlet API, so this isn't as much work as it sounds.

  • In the servlet handler, I create an instance of this class and then forget about the servlet API.

  • I can extend this handler class and add all the fields and data that I need for the job. No huge parameter lists, no thread local hacking, no worries about concurrency.

  • I have a utility class for unit tests that creates a HttpRequestHandler with mock implementations of request and response. This way, I don't need a servlet environment to test my code.

This solves all my problems because I can get the DB session and other things in the init() method or I can insert a factory between the servlet and the real handler to do more complex things.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • "I'm still wondering why people use the rotten javax.servlet API" - probably because standards are good :) Reasonable approach nevertheless! – mindas Mar 02 '11 at 10:27
  • Some standards cost more to keep than others. The Servlet API makes testing harder+slower and I think we all agree that this is one of the most important points in software development today. – Aaron Digulla Mar 02 '11 at 12:35
  • I wonder what you'd think, if I hack tomcat and write valves to do the job. The API is fine, you can isolate the code if you need to. – bestsss Mar 02 '11 at 16:29
  • @bestsss: Valve has the same issues as the Servlet API: All important data is passed in as parameters -> you either have to create a handler/context object to keep the references or all your methods need 2-3 additional parameters - sometimes even those which aren't web related. – Aaron Digulla Mar 03 '11 at 10:41
  • This is a fine approach but has little to do with the original question about asynchronous request processing... – Dobes Vandermeer Jan 31 '12 at 02:41
  • 1
    @DobesVandermeer: Sorry, forgot to mention that I then extend this handler class so I can keep references to all the data that I need for the job. Hence no need for `ThreadLocal` or any such hacks. – Aaron Digulla Feb 13 '12 at 11:21
  • @AaronDigulla 2001 called and it wants it custom-web-framework-Servlet-API-wrapper back. Most people don't do use the API but a wrapper (like Spring MVC). The only way to avoid threadlocal is to pass around a context/request object everywhere which is disgusting. – Adam Gent May 05 '12 at 18:16
0

One solution is to not use ThreadLocal but rather use a singleton that contains a static array of the objects you want to make global. This object would contain a "threadName" field that you set. You first set the current thread's name (in doGet, doPost) to some random unique value (like a UUID), then store it as part of the object that contains the data you want stored in the singleton. Then whenever some part of your code needs to access the data, it simply goes through the array and checks for the object with the threadName that is currently running and retrieve the object. You'll need to add some cleanup code to remove the object from the array when the http request completes.

Johann
  • 27,536
  • 39
  • 165
  • 279