grails / grails-core

The Grails Web Application Framework
http://grails.org
Apache License 2.0
2.78k stars 951 forks source link

Jetty throws NotSerializableException for GrailsParameterMap #12688

Open transentia opened 2 years ago

transentia commented 2 years ago

Expected Behavior

I have a fully working application that has always used Tomcat. Thought I'd see if Jetty would give me a bit more performance. Converted my build by referencing https://github.com/puneetbehl/grails-jetty-example NO source changes outside build.gradle. Expected no problems. It seems to work so far, but I get MANY warnings on each page view about "java.io.NotSerializableException: grails.web.servlet.mvc.GrailsParameterMap"

Indeed:

public class GrailsParameterMap extends TypeConvertingMap implements Cloneable {
...

Actual Behaviour

I get:

20:11:52.187  WARN --- [tp1678636614-81] org.eclipse.jetty.server.session         : 

org.eclipse.jetty.server.session.UnwriteableSessionDataException: Unwriteable session node0z6iucusdvrgf126xzbk1y64f60 for node0__0.0.0.0
    at org.eclipse.jetty.server.session.FileSessionDataStore.doStore(FileSessionDataStore.java:330)
    at org.eclipse.jetty.server.session.AbstractSessionDataStore$1.run(AbstractSessionDataStore.java:142)
    at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1507)
    at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1544)
    at org.eclipse.jetty.server.session.SessionContext.run(SessionContext.java:92)
    at org.eclipse.jetty.server.session.AbstractSessionDataStore.store(AbstractSessionDataStore.java:155)
    at org.eclipse.jetty.server.session.AbstractSessionCache.release(AbstractSessionCache.java:581)
    at org.eclipse.jetty.server.session.SessionHandler.complete(SessionHandler.java:369)
    at org.eclipse.jetty.server.Request.lambda$leaveSession$0(Request.java:408)
    at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1525)
    at org.eclipse.jetty.server.Request.leaveSession(Request.java:408)
    at org.eclipse.jetty.server.Request.onCompleted(Request.java:1550)
    at org.eclipse.jetty.server.HttpChannel.onCompleted(HttpChannel.java:917)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:467)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
    at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131)
    at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:409)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.io.NotSerializableException: grails.web.servlet.mvc.GrailsParameterMap
    at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1192)
    at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:358)
    at org.eclipse.jetty.server.session.SessionData.serializeAttributes(SessionData.java:117)
    at org.eclipse.jetty.server.session.FileSessionDataStore.save(FileSessionDataStore.java:478)
    at org.eclipse.jetty.server.session.FileSessionDataStore.doStore(FileSessionDataStore.java:322)
    ... 25 common frames omitted

I note taht this is only a WARN and I could probably just silence this in my logback config but it is unexpected, gives me a feeling of uncertainty and may kill the performance that is my justification for using Jetty.

Steps To Reproduce

No response

Environment Information

Windows 11 Java openjdk-18.0.2.1_windows-x64_bin Grails 5.2.3

Example Application

No response

Version

5.2.3

transentia commented 2 years ago

I wonder if this arises from my SecurityInterceptor (excerpted):

@CompileStatic
@Slf4j
class SecurityInterceptor {

    ...

    boolean before() {
        final isLoggedIn = session['userid'] && session['role']

        log.debug("{}/{}; isLoggedIn: {}", controllerName, actionName, isLoggedIn)

        if (! isLoggedIn) {
            // save where we were trying to get to for use in AuthController if login is successful
            **session['filter'] = [controller: controllerName ?: 'home', action: actionName, params: params] as Map**
            redirect controller: 'auth', action: 'login', params: params
        }

        isLoggedIn
    }

   ...
}

I'm not sure I see any (reasonable) alternative to putting params into the session.

It seems to me that GrailsParameterMap should be serializable...for this use-case.

I'm also surprised that Tomcat doesn't complain.

transentia commented 2 years ago

My workaround:

            // Jetty complains that params is not serializable...
            // Make a proper serializable map to use instead...a nice, simple workaround...:
            Map ppp = [:]
            ppp.putAll(params)

            session['filter'] = [controller: controllerName ?: 'home', action: actionName, params: ppp] as Map

Seems to do the job.