Search code examples
scalaserializationakkatraitscase-class

scala/akka of case class with trait


I am having trouble getting case classes within a trait to work with Akka serialization. The following code works as expected:

import akka.actor.ActorSystem
import akka.serialization.SerializationExtension

/*trait TestProtocol {*/
  sealed abstract class Expr /* extends Serializable */
  final case class Literal(v: Double) extends Expr
  final case class Plus(a: Expr, b: Expr) extends Expr
  final case class Minus(a: Expr, b: Expr) extends Expr
  final case class Times(a: Expr, b: Expr) extends Expr
  final case class Divide(a: Expr, b: Expr) extends Expr
/*}*/

class Foo /* extends Serializable with TestProtocol */ {
  val system = ActorSystem("snitch")
  def sample = List(
    Plus(Literal(9),Literal(5)),
    Times(Plus(Literal(1),Literal(18)),Literal(2))
  )
  val serialization = SerializationExtension(system)
  val serializer = serialization.findSerializerFor(sample)
  val bytes = serializer.toBinary(sample)
  val back = serializer.fromBinary(bytes, manifest = None)
  println(s">>>>>>> pre-serialize: ${sample}")
  println(s">>>>>>>  deserialized: ${back}")
}

object Main extends App {
  val bar = new Foo
  bar.system.terminate
}

but if I remove the comments, an exception is thrown:

Exception in thread "main" java.io.NotSerializableException: akka.serialization.Serialization
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at scala.collection.immutable.List$SerializationProxy.writeObject(List.scala:468)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at akka.serialization.JavaSerializer$$anonfun$toBinary$1.apply$mcV$sp(Serializer.scala:235)
    at akka.serialization.JavaSerializer$$anonfun$toBinary$1.apply(Serializer.scala:235)
    at akka.serialization.JavaSerializer$$anonfun$toBinary$1.apply(Serializer.scala:235)
    at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
    at akka.serialization.JavaSerializer.toBinary(Serializer.scala:235)
    at buggy.Foo.<init>(Main.scala:22)
    at buggy.Main$.delayedEndpoint$buggy$Main$1(Main.scala:29)

when trying to deserialize the byte string. I get the exception even after I try to make any relevant classes serializable (as seen in the comments).

Does anyone have any idea how to make this work or why it fails? I'm using Scala 2.11.8 and Akka 2.4.8; I build and test with sbt assembly...

thanks very much for any insight!


Solution

  • The reason you get an exception when you remove the comments is that in that code, you are making the classes that are being serialized (Plus, Minus, etc...) a part of the class Foo as you are mixing them in. As such, in order to serialize those classes, the enclosing class must also be serialized too. That's why you had to make Foo inherit from Serializable in the first place, which should have been the first red flag. Now, the actor system and the serialization extension are fields on Foo, so the serialization code also things it needs to serialize them too, which is where the failure comes from.

    You can fix this in any number of ways. One would be to define TestProtocol as an object and then import it inside of Foo instead of mixing it in as a trait. If you are set on mixing it in, then you can make you code look something like this:

    object Main extends App {
      val bar = new Foo
      bar.run
    }
    
    trait TestProtocol {
      sealed abstract class Expr extends Serializable
      final case class Literal(v: Double) extends Expr
      final case class Plus(a: Expr, b: Expr) extends Expr
      final case class Minus(a: Expr, b: Expr) extends Expr
      final case class Times(a: Expr, b: Expr) extends Expr
      final case class Divide(a: Expr, b: Expr) extends Expr
    }
    
    class Foo extends Serializable with TestProtocol  {
    
      def run = {
        val system = ActorSystem("snitch")
        def sample = List(
          Plus(Literal(9),Literal(5)),
          Times(Plus(Literal(1),Literal(18)),Literal(2))
        )
        val serialization = SerializationExtension(system)
        val serializer = serialization.findSerializerFor(sample)
        val bytes = serializer.toBinary(sample)
        val back = serializer.fromBinary(bytes, manifest = None)
        println(s">>>>>>> pre-serialize: ${sample}")
        println(s">>>>>>>  deserialized: ${back}")
        system.terminate
      }
    }
    

    Here, there are no fields in Foo, as I have pushed the actor system and such down into the run method. This is just another such way to get around your issue.