Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't place CrawlerSessionManagerValve into session, place data-holder only #154

Closed
wants to merge 2 commits into from

Conversation

mlem
Copy link

@mlem mlem commented Apr 8, 2019

It helps to serialize this sessions with libraries like kryo.

Background:
we've found out, that our kryo library serializes the whole valve.
Our stack indicates, that jdk internals are getting serialized into the
sessions. It's unnecessary and can be solves more easily with this fix.

It helps to serialize this sessions with libraries like kryo.

Background:
we've found out, that our kryo library serializes the whole valve.
Our stack indicates, that jdk internals are getting serialized into the
sessions. It's unnecessary and can be solves more easily with this fix.
@mlem
Copy link
Author

mlem commented Apr 8, 2019

We are getting this message in our log all the time.
I've found out, that our serialization library (Kryo) is serializing your valve into the session and is causing troubles with OpenJDK 11, because it wants to access a private field of the class loader

28-Mar-2019 11:30:11.530 WARNING [msm-storage-thread-15] de.javakaffee.web.msm.BackupSessionTask.call FAILED for session id AF378F36B5D8310F0CF5B1800B89364B
 de.javakaffee.web.msm.TranscoderDeserializationException: com.esotericsoftware.kryo.KryoException: java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.List java.lang.module.Configuration.parents accessible: module java.base does not "opens java.lang.module" to unnamed module @4eba42b8
Serialization trace:
cf (java.lang.ModuleLayer)
classLoaderValueMap (jdk.internal.loader.ClassLoaders$AppClassLoader)
classloader (java.security.ProtectionDomain)
context (java.security.AccessControlContext)
acc (java.net.URLClassLoader)
classloader (java.security.ProtectionDomain)
context (java.security.AccessControlContext)
acc (org.apache.catalina.loader.ParallelWebappClassLoader)
classLoaderLoggers (org.apache.juli.ClassLoaderLogManager)
manager (org.apache.juli.AsyncFileHandler)
handlers (java.util.logging.Logger$ConfigurationData)
config (java.util.logging.Logger)
logger (org.apache.juli.logging.DirectJDKLog)
containerLog (org.apache.catalina.valves.AccessLogValve)
logs (org.apache.catalina.core.AccessLogAdapter)
accessLog (org.apache.catalina.core.StandardHost)
container (org.apache.catalina.valves.CrawlerSessionManagerValve)
	at de.javakaffee.web.msm.serializer.kryo.KryoTranscoder.serializeAttributes(KryoTranscoder.java:240)
	at de.javakaffee.web.msm.TranscoderService.serializeAttributes(TranscoderService.java:151)
	at de.javakaffee.web.msm.BackupSessionTask.serializeAttributes(BackupSessionTask.java:179)
	at de.javakaffee.web.msm.BackupSessionTask.call(BackupSessionTask.java:109)
	at de.javakaffee.web.msm.BackupSessionTask.call(BackupSessionTask.java:50)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: com.esotericsoftware.kryo.KryoException: java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.List java.lang.module.Configuration.parents accessible: module java.base does not "opens java.lang.module" to unnamed module @4eba42b8
Serialization trace:
cf (java.lang.ModuleLayer)
classLoaderValueMap (jdk.internal.loader.ClassLoaders$AppClassLoader)
classloader (java.security.ProtectionDomain)
context (java.security.AccessControlContext)
acc (java.net.URLClassLoader)
classloader (java.security.ProtectionDomain)
context (java.security.AccessControlContext)
acc (org.apache.catalina.loader.ParallelWebappClassLoader)
classLoaderLoggers (org.apache.juli.ClassLoaderLogManager)
manager (org.apache.juli.AsyncFileHandler)
handlers (java.util.logging.Logger$ConfigurationData)
config (java.util.logging.Logger)
logger (org.apache.juli.logging.DirectJDKLog)
containerLog (org.apache.catalina.valves.AccessLogValve)
logs (org.apache.catalina.core.AccessLogAdapter)
accessLog (org.apache.catalina.core.StandardHost)
container (org.apache.catalina.valves.CrawlerSessionManagerValve)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:101)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
	at com.esotericsoftware.kryo.serializers.CollectionSerializer.write(CollectionSerializer.java:100)
	at com.esotericsoftware.kryo.serializers.CollectionSerializer.write(CollectionSerializer.java:40)
	at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
	at com.esotericsoftware.kryo.serializers.MapSerializer.write(MapSerializer.java:113)
	at com.esotericsoftware.kryo.serializers.MapSerializer.write(MapSerializer.java:39)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:361)
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:302)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObjectOrNull(Kryo.java:629)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:86)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:361)
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:302)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObjectOrNull(Kryo.java:629)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:86)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
	at com.esotericsoftware.kryo.serializers.MapSerializer.write(MapSerializer.java:106)
	at com.esotericsoftware.kryo.serializers.MapSerializer.write(MapSerializer.java:39)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
	at com.esotericsoftware.kryo.serializers.CollectionSerializer.write(CollectionSerializer.java:100)
	at com.esotericsoftware.kryo.serializers.CollectionSerializer.write(CollectionSerializer.java:40)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObjectOrNull(Kryo.java:629)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:86)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:361)
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:302)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
	at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
	at com.esotericsoftware.kryo.serializers.MapSerializer.write(MapSerializer.java:113)
	at com.esotericsoftware.kryo.serializers.MapSerializer.write(MapSerializer.java:39)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:557)
	at de.javakaffee.web.msm.serializer.kryo.KryoTranscoder.serializeAttributes(KryoTranscoder.java:237)
	... 8 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.List java.lang.module.Configuration.parents accessible: module java.base does not "opens java.lang.module" to unnamed module @4eba42b8
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:340)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:280)
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:176)
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:170)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.buildValidFields(FieldSerializer.java:283)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.rebuildCachedFields(FieldSerializer.java:216)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.rebuildCachedFields(FieldSerializer.java:157)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.<init>(FieldSerializer.java:150)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.<init>(FieldSerializer.java:134)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.<init>(FieldSerializer.java:130)
	at de.javakaffee.web.msm.serializer.kryo.DefaultFieldSerializerFactory.newDefaultSerializer(DefaultFieldSerializerFactory.java:34)
	at de.javakaffee.web.msm.serializer.kryo.KryoDefaultSerializerFactory$SerializerFactoryAdapter.makeSerializer(KryoDefaultSerializerFactory.java:45)
	at com.esotericsoftware.kryo.Kryo.newDefaultSerializer(Kryo.java:396)
	at com.esotericsoftware.kryo.Kryo.getDefaultSerializer(Kryo.java:380)
	at de.javakaffee.web.msm.serializer.kryo.KryoTranscoder$2.getDefaultSerializer(KryoTranscoder.java:168)
	at com.esotericsoftware.kryo.util.DefaultClassResolver.registerImplicit(DefaultClassResolver.java:74)
	at com.esotericsoftware.kryo.Kryo.getRegistration(Kryo.java:508)
	at com.esotericsoftware.kryo.Kryo.getSerializer(Kryo.java:528)
	at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:82)
	... 83 more

@kkolinko
Copy link

kkolinko commented Apr 8, 2019

  1. Your title is lacking. Your "valve" is CrawlerSessionManagerValve.

  2. Behaviour of CrawlerSessionManagerValve versus serialization of sessions is a valid question. At least it needs some comments and documentation improvements. I think this should be filed into Bugzilla and discussed there. (Pull requests come and go, and Bugzilla is what is referenced in changelog).

  3. Your pull request does not make any sense to me.

The current implementation of CrawlerSessionManagerValve does not implement java.io.Serializable, so it is not written out. Likewise, your CrawlerHttpSessionBindingListener does not implement Serializable. (*)

The hashmaps of ids owned by CrawlerSessionManagerValve should exist in 1 instance only (in memory). It does not make sense to serialize them with a session.

What am I missing, and what exactly is your problem? (And again, problems would better be filed into Bugzilla).

(*) See javadoc for CrawlerSessionManagerValve => "All implemented interfaces":
http://tomcat.apache.org/tomcat-9.0-doc/api/org/apache/catalina/valves/CrawlerSessionManagerValve.html

@mlem
Copy link
Author

mlem commented Apr 8, 2019

I've filed an issue: https://bz.apache.org/bugzilla/show_bug.cgi?id=63324

The problem is, that we serialize sessions between tomcat instances and the CrawlerSessionManagerValve has dependencies on unwanted content (like JDK internals)

About the understanding:
Yes, it doesn't implement serializable, the library we are using is doing it for us. Even if you don't provide a serializable interface, we can serialize it.

I wanted to write a private inner class accessing your maps directly, but I think, that the scope of this class would contain the outer class (not so with private static inner classes). This would bring us the desired effect.
That's why I'm passing in the maps. They still exist just once in memory, they are not copies, they are references to the original maps.
There are some tricks in java 8 to pass in functions to retrieve the correct maps, but this looks more like a hack than what I did.

@mlem mlem changed the title Don't serialize the whole valve into session, but use a smaller object. Don't serialize CrawlerSessionManagerValve into session attributes Apr 8, 2019
@ChristopherSchultz
Copy link
Contributor

Perhaps the title of this PR should be "don't place CrawlerSessionManagerValve into session, place data-holder only" and it will make more sense.

I think this idea does make sense. There is no reason to put a Valve into the session.

But the implementation doesn't actually stop anything from being serialized: those two big maps are still serialized along with the rest of the stream. What you are avoiding is serializing the class loader which is happening because of the Valve itself.

You should not serialize the whole session mapping into a single session. You shouldn't have to serialize anything, actually. The session already knows its own session id.

The trick is that the CrawlerSessionManagerValve needs to be notified if a session is terminated.

Instead of storing something in the session, CrawlerSessionManagerValve should instead be changed to be an HttpSessionListener which won't be serialized when the session is written-out. It has the added benefit of being simpler than the current code.

@markt-asf
Copy link
Contributor

Using an HttpSessionListener gets tricky because it has to be registered with every web application. That is do-able but will add a fair amount of complexity - particularly to handle automatic deployment.

The patch looks reasonable. I can't think of a better way to handle this.

@mlem
Copy link
Author

mlem commented Apr 9, 2019

But the implementation doesn't actually stop anything from being serialized: those two big maps are still serialized along with the rest of the stream. What you are avoiding is serializing the class loader which is happening because of the Valve itself.

In comparison to the container object (which holds plenty of stuff, as I've seen) this doesn't seem like a big deal. As I understood, there is one entry for each crawler (1 crawler = 1 entry in map x2 = clientId x2 + sessionId x2). we would need to have plenty of crawlers within 60 seconds to fill this map big enough to make it serious.

Instead of storing something in the session, CrawlerSessionManagerValve should instead be changed to be an HttpSessionListener which won't be serialized when the session is written-out. It has the added benefit of being simpler than the current code.

That sounds good, but I would need to hook into the lifecycle of this valve (or into the lifecycle of the web application itself). Due to the dynamic nature of your valves, I don't know where this class get's initialized. Can you point me out, where I would register this listener?

Using an HttpSessionListener gets tricky because it has to be registered with every web application. That is do-able but will add a fair amount of complexity - particularly to handle automatic deployment.

The patch looks reasonable. I can't think of a better way to handle this.

I see it the same way. And thanks.

I'll change the title again.
Do you need a changed commit message too?

@mlem mlem changed the title Don't serialize CrawlerSessionManagerValve into session attributes Don't place CrawlerSessionManagerValve into session, place data-holder only Apr 9, 2019
@kkolinko
Copy link

kkolinko commented Apr 9, 2019

I commented in Bugzilla, starting with
https://bz.apache.org/bugzilla/show_bug.cgi?id=63324#c2

It helps to serialize this sessions with libraries like kryo.

Apply feedback from PR review

fix 63324
@markt-asf
Copy link
Contributor

Patch applied manually with a few changes to keep Checkstyle etc happy. I also added a changelog entry.

@markt-asf markt-asf closed this May 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants