altoo-ag/akka-kryo-serialization

When updating from 2.0.1 it fails to serialize a case class with NonEmptySet

umbreak opened this issue · 3 comments

Version 2.0.1 works fine for us. However when updating to 2.1.0 a case class that contains cats.data.NonEmptySet fails with something on the lines of:

Caused by: com.esotericsoftware.kryo.KryoException: Unable to find class: ch.epfl.bluebrain.nexus.delta.sdk.model.projects.ProjectRef$$$Lambda$2832/0x0000000800f3a040
Serialization trace:
f$1 (cats.kernel.Order$$anon$2)
A0$486 (cats.kernel.instances.TupleOrderInstances$$anon$486)
ev$1 (cats.kernel.Order$$anon$2)
$outer (cats.kernel.Order$$anon$1)

NonEmptySet[A] requires this Order[A] in order to construct an instance...so I guess is something on those lines.

hi @umbreak
Hmm that's strange. The only big change from 2.0.1 to 2.1.0 is added support for akka typed that you do not use...
The only changes that seem relevant are akka 2.6.10 -> 2.6.12 and kryo 5.0.1 -> 5.0.3.
I would recommend to force the version of kryo to 5.0.1 and see if that's the cause.

v2.0.1...v2.1.0

You are right, the problem was not introduced recently. However with the newer version it crashes and with the previous it failed silently with a NullPointerException.

I've done my testings here: https://github.com/umbreak/kryo-test

NonEmptySet in cats it is just a type. What is actually used is TreeSet. And kryo complained because there was no instance of Ordering[A] needed in order to construct the TreeSet.

I've fixed that having a custom serializer for the ordering I'm using (in my case, ordering for Key):

class KryoSerializerInit extends DefaultKryoInitializer {

  override def postInit(kryo: ScalaKryo, system: ExtendedActorSystem): Unit = {
    super.postInit(kryo, system)
    kryo.addDefaultSerializer(classOf[Ordering[Key]], classOf[OrderingKeySerializer])
    kryo.register(classOf[Ordering[Key]], new OrderingKeySerializer)
    ()
  }
}

object KryoSerializerInit {

  class OrderingKeySerializer extends Serializer[Ordering[Key]] {
    override def write(kryo: Kryo, output: Output, `object`: Ordering[Key]): Unit = ()

    override def read(kryo: Kryo, input: Input, `type`: Class[_ <: Ordering[Key]]): Ordering[Key] =
      implicitly[Ordering[Key]]
  }

}

Then I add this to the application.conf field akka-kryo-serialization.kryo-initializer and that's it.

It is there a simpler way to do this?

If not, it is worth it to mention somewhere on the readme that when using TreeSet, SortedSet or similars, you'll need to provide manually the Ordering for it

I'm glad you found a solution!
Your solution is in my opinion the only proper way to do this with a TreeSet. Actually kryo (or any proper serializer) should only serialize the data but not the functions/classes (otherwise you'll get all nasty security issues like in the java serializer)...

final class TreeSet[A] private[immutable] (private[immutable] val tree: RB.Tree[A, Any])(implicit val ordering: Ordering[A])

The other way would be to use another Set implementation that does not use an Ordering. A good convention is also to use immutable structures in serialized messages...

If not, it is worth it to mention somewhere on the readme that when using TreeSet, SortedSet or similars, you'll need to provide manually the Ordering for it

Jep that's a good addition to a faq...