Session/context lost in Play async/await

2019-05-31 08:16发布

It seems the await() method loses context:

public static action() {
    session.put("key", "value");
    await(someAsyncCall());

    // Now, for some reason the session doesn't have "key"
}

Is this a known issue? Any workarounds?

2条回答
SAY GOODBYE
2楼-- · 2019-05-31 08:48

Workaround for Play 1.2.5, if all that is required is to persist the session id, use the following in place of direct call to await(...)

protected static <T> T awaitSessionAware(Future<T> future) {
    final String sessionId = session.getId();
    T result = await(future);
    session.put("___ID", sessionId);
    return result;
}

The code above is a workaround for the issue outlined here where a new session is created after the await(..) call instead of reusing the existing session. A reference to the original session id is used to reset the session id after the await call (i.e. session.put("___ID", sessionId) resets the session id to its pre-await value).

查看更多
倾城 Initia
3楼-- · 2019-05-31 08:55

That is unfortunate. Since session is a thread local variable it does not get passed between new threads (which happens in your example). What is misleading and surprising, is that when the code resumes after the await method there is a session variable (but it is a different instance).

I would say this is a bug - I'd expect the session context to be maintained around the await call.

That said, I understand why this is tricky. When you use await, you are actually writing code in at least three threads. The before part, the job/async call, and the after part. Trace into it, it is kind of amazing.

Even so, I agree that the session state for the request should be maintained, so I suggest you file an issue: https://play.lighthouseapp.com/projects/57987-play-framework/tickets/new

Below is a workaround that copies the session map by passing it through the async call. You could write a simple wrapper Job that always does this.

public static void test() {
    Logger.debug("before: Session.current() " + Session.current());
    Session.current().put("key", new Date().toString());
    Job<Session> async = new Job<Session>() {
        Session sessionPassed = Session.current();

        @Override
        public Session doJobWithResult() throws Exception {
            Logger.debug("during job: Session.current() "
                    + Session.current());
            Logger.debug("during job: sessionPassed " + sessionPassed);
            Thread.sleep(1000L);

            // you could do something like this to wrap a real 
            // async call and maintain the session context.  If 
            // the async job returns a result, you'll have to return 
            // a map or POJO with the session and the result.

            actualJob.now();  

            return sessionPassed;
        }
    };
    Session sessionReturned = await(async.now());
    Logger.debug("after: Session.current() ="
            + (Session.current() == null ? "no session" : Session.current()));
    Logger.debug("after: " + sessionReturned);

    Session.current().all().putAll(sessionReturned.all());

    Logger.debug("finally: "
            + (Session.current() == null ? "no session" : Session.current()));
}

EDIT:

Alternatively, you could store the session map using Cache.set() - this is perhaps cleaner than passing it around.

As an aside, I seldom use session to store user data. Each cookie (which is what a session is in play) slows down your http requests (read about how cookies work). What I prefer to do is create a map on the server side using the Cache (eg Cache.set(session.getId(),userDataMap)). Clearly each use case may be different, but I prefer this way to maintain user state.

查看更多
登录 后发表回答