Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stackoverflow exception in a PersistentFSM actor

Tags:

scala

akka

Here is the code:

import akka.actor.{ActorSystem, Props}
import akka.persistence.fsm.PersistentFSM
import akka.persistence.fsm.PersistentFSM.FSMState

import scala.reflect._
import scala.reflect.ClassTag

/**
  * Created by IDEA on 4/4/16.
  */

object Vendor {

  sealed trait State extends FSMState

  case object Dumb extends State {
    override def identifier = "dumb-state"
  }

  case object Smart extends State {
    override def identifier = "smart-state"
  }

  val prices = Map(
    "cola-light" -> 1.23,
    "cola-fanta" -> 1.23,
    "cola" -> 1.23,
    "lays-natural" -> 3.92,
    "lays-paprika" -> 3.24,
    "lays-mix" -> 2.56,
    "lays-superchips" -> 3.99
  )

  case class Data(var userBalance: Double, val stock: collection.mutable.Map[String, Int])

  sealed trait DomainEvent

  case class DoTransaction(item: String, amount: Int, change: Double) extends DomainEvent

  case class StoreTransaction(credit: Double, reason: String) extends DomainEvent

  case object DoReturnMoney extends DomainEvent

  case class Reject(reason: String) extends DomainEvent

  sealed trait Msg

  case class Transaction(item: String, amount: Int, input: Double) extends Msg

  case object ToggleSmart extends Msg

  case object ReturnMoney extends Msg

}

class Vendor extends PersistentFSM[Vendor.State, Vendor.Data, Vendor.DomainEvent] {

  import Vendor._

  val initData = Data(0d, collection.mutable.Map(
    "cola-light" -> 10,
    "cola-fanta" -> 10,
    "cola" -> 10,
    "lays-natural" -> 10,
    "lays-paprika" -> 10,
    "lays-mix" -> 10,
    "lays-superchips" -> 10
  ))

  override implicit def domainEventClassTag: ClassTag[DomainEvent] = classTag[DomainEvent]

  override def applyEvent(domainEvent: DomainEvent, currentData: Data): Data = {
    domainEvent match {
      case DoTransaction(item, amount, change) =>
        val (verb, suffix) = if (amount > 1) ("are", "s") else ("is", "")
        val changeInfo = if (change > 0) {
          s"And here is your change: €${change}. "
        } else ""
        println(s"Here $verb $amount item$suffix of $item. ${changeInfo}Thanks!")
        currentData.stock(item) -= amount
        currentData.userBalance = 0
        currentData

      case StoreTransaction(credit, reason: String) =>
        println(s"Transaction not successful. \n${reason}\n€${credit} stored on your balance.")
        currentData.userBalance += credit
        currentData

      case DoReturnMoney =>
        println(s"Here is your money: €${currentData.userBalance}.")
        currentData.userBalance = 0
        currentData

      case Reject(reason) =>
        println(s"Transaction rejected due to this reason: $reason")
        currentData
    }
  }

  startWith(Dumb, initData)

  when(Dumb) {
    case Event(Transaction(item, amount, input), currentData) =>
      if (input <= 0) {
        stay applying Reject("you didn't put in money.")
      }
      if (!currentData.stock.contains(item)) {
        val reason = s"We don't have ${item}."
        stay applying StoreTransaction(input, reason)
      }
      if (currentData.stock(item) < amount) {
        val reason = s"We have only ${currentData.stock(item)} ${item} in stock."
        stay applying StoreTransaction(input, reason)
      }
      val change = input + currentData.userBalance - prices(item) * amount
      if (change >= 0) {
        stay applying DoTransaction(item, amount, change)
      } else {
        val reason = s"Insufficient credit, €${-change} in short."
        stay applying StoreTransaction(input, reason)
      }

    case Event(ReturnMoney, _) =>
      stay applying DoReturnMoney

    case Event(ToggleSmart, _) =>
      goto(Smart)
  }


  when(Smart) {
    case Event(Transaction(item, amount, input), currentData) =>
      if (input <= 0) {
        stay applying Reject("you didn't put in money.")
      }
      if (!currentData.stock.contains(item)) {
        val suggestion = currentData.stock.keys.find(x => x.startsWith(item))
        suggestion match {
          case None =>
            stay applying StoreTransaction(input, s"We can't find anything similar to ${item}")
          case Some(item1) =>
            self ! Transaction(item1, amount, input)
        }
      }
      if (currentData.stock(item) < amount) {
        val reason = s"We have only ${currentData.stock(item)} ${item} in stock."
        stay applying StoreTransaction(input, reason)
      }
      val change = input + currentData.userBalance - prices(item) * amount
      if (change >= 0) {
        stay applying DoTransaction(item, amount, change)
      } else {
        val reason = s"Insufficient credit, €${-change} in short."
        stay applying StoreTransaction(input, reason)
      }

    case Event(ReturnMoney, _) =>
      stay applying DoReturnMoney

    case Event(ToggleSmart, _) =>
      goto(Dumb)

  }

  override def persistenceId: String = "persistence-vendor"
}

object VendorApp extends App {
  import Vendor._
  val asys = ActorSystem("persistence-vendor-app")
  val vendor = asys.actorOf(Props[Vendor])
  vendor ! Transaction("cola-light", 2, 5)
  Thread.sleep(1000)
  asys.terminate()
}

Here is the error:

/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/bin/java -Didea.launcher.port=7532 "-Didea.launcher.bin.path=/Applications/IntelliJ IDEA CE.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_72.jdk/Contents/Home/lib/tools.jar:/Users/kaiyin/IdeaProjects/Learning Akka (video) code/section4/persistent-fsm&query/target/scala-2.11/classes:/Users/kaiyin/.ivy2/cache/com.google.guava/guava/bundles/guava-16.0.1.jar:/Users/kaiyin/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.11.7.jar:/Users/kaiyin/.ivy2/cache/org.reactivestreams/reactive-streams/jars/reactive-streams-1.0.0.jar:/Users/kaiyin/.ivy2/cache/org.iq80.leveldb/leveldb-api/jars/leveldb-api-0.7.jar:/Users/kaiyin/.ivy2/cache/org.iq80.leveldb/leveldb/jars/leveldb-0.7.jar:/Users/kaiyin/.ivy2/cache/org.fusesource.leveldbjni/leveldbjni-all/bundles/leveldbjni-all-1.8.jar:/Users/kaiyin/.ivy2/cache/com.typesafe.akka/akka-testkit_2.11/jars/akka-testkit_2.11-2.4.0.jar:/Users/kaiyin/.ivy2/cache/com.typesafe.akka/akka-stream-experimental_2.11/jars/akka-stream-experimental_2.11-1.0.jar:/Users/kaiyin/.ivy2/cache/com.typesafe.akka/akka-protobuf_2.11/jars/akka-protobuf_2.11-2.4.0.jar:/Users/kaiyin/.ivy2/cache/com.typesafe.akka/akka-persistence_2.11/jars/akka-persistence_2.11-2.4.0.jar:/Users/kaiyin/.ivy2/cache/com.typesafe.akka/akka-persistence-query-experimental_2.11/jars/akka-persistence-query-experimental_2.11-2.4.0.jar:/Users/kaiyin/.ivy2/cache/com.typesafe.akka/akka-actor_2.11/jars/akka-actor_2.11-2.4.0.jar:/Users/kaiyin/.ivy2/cache/com.typesafe/config/bundles/config-1.3.0.jar:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar" com.intellij.rt.execution.application.AppMain VendorApp
Uncaught error from thread [persistence-vendor-app-akka.actor.default-dispatcher-3] shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled for ActorSystem[persistence-vendor-app]
java.lang.StackOverflowError
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)
  at Vendor.domainEventClassTag(Vendor.scala:70)


  ...

Process finished with exit code 255

build.sbt:

name := "Persistence"

version := "1.0"

scalaVersion := "2.11.7"

sbtVersion := "0.13.5"

libraryDependencies ++= Seq(
    "com.typesafe.akka"           %% "akka-actor"       % "2.4.0",
    "com.typesafe.akka"           %% "akka-persistence" % "2.4.0",
    "org.iq80.leveldb"            % "leveldb"           % "0.7",
    "org.fusesource.leveldbjni"   % "leveldbjni-all"    % "1.8",
    "com.typesafe.akka" %% "akka-persistence-query-experimental" % "2.4.0",
    "com.typesafe.akka" % "akka-stream-experimental_2.11" % "1.0

application.conf:

akka {
  persistence {
    journal {
      plugin = "akka.persistence.journal.leveldb",
      leveldb {
        dir = "target/example/journal",
        native = false
      }
    },
    snapshot-store {
      plugin = "akka.persistence.snapshot-store.local",
      local {
        dir = "target/example/snapshots"
      }
    }
  }
}

Any idea what went wrong?

like image 480
qed Avatar asked Apr 05 '16 13:04

qed


2 Answers

Instead of

override implicit def domainEventClassTag: ClassTag[DomainEvent] classTag[DomainEvent]

it should be

override def domainEventClassTag: ClassTag[DomainEvent] = classTag[DomainEvent]
like image 73
yoshimori95 Avatar answered Nov 08 '22 21:11

yoshimori95


I managed to get it to work: If you look at the tests of PersistentFSM (https://github.com/akka/akka/blob/master/akka-persistence/src/test/scala/akka/persistence/fsm/PersistentFSMSpec.scala#L411)

You see they use an implicit constructor parameter instead of implementing domainEventClassTag in the body. Somehow this, together with the calling site on props, works.

I will get something like:

...
class Vendor(implicit val domainEventClassTag: ClassTag[Vendor.DomainEvent]) extends PersistentFSM[Vendor.State, Vendor.Data, Vendor.DomainEvent] {
...
like image 31
Tim Avatar answered Nov 08 '22 19:11

Tim