Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Scala fail to instantiate a Companion Object?

Tags:

scala

akka

stm

I am new to Scala and Akka so forgive me if this is a newb question, but I can't find the answer anywhere else...

For the record I am using Scala 2.9.0-1 and Akka 1.1.3 and have included my SBT 0.10.1 setup as well.

I have written the code that follows this message as an experiment in Akka; it is a toy version of a user database and registration facilities. The basic idea is there is an ActorPool of UserServer actors each of which have an instance of a MemoryUserDatabase which uses STM to interact with a Map of Users keyed off the users' email addresses - pretty simple, right?

The problem can be reproduced by compiling the file and running the following in two separate consoles:

Console #1:

import toy.service.user._; ServiceRunner.run

Console #2:

import toy.service.user._; ClientRunner.run

This is the output from the server console (#1)

Aug 31, 2011 5:21:29 PM org.multiverse.api.GlobalStmInstance <clinit>
INFO: Initializing GlobalStmInstance using factoryMethod 'org.multiverse.stms.alpha.AlphaStm.createFast'.
Aug 31, 2011 5:21:29 PM org.multiverse.stms.alpha.AlphaStm <init>
INFO: Created a new AlphaStm instance
Aug 31, 2011 5:21:29 PM org.multiverse.api.GlobalStmInstance <clinit>
INFO: Successfully initialized GlobalStmInstance using factoryMethod 'org.multiverse.stms.alpha.AlphaStm.createFast'.
[ERROR]   [8/31/11 5:21 PM] [akka:event-driven:dispatcher:global-3] [LocalActorRef] Availability(foo)
java.lang.NoClassDefFoundError: Could not initialize class toy.service.user.memory.MemoryUserDatabase$
    at toy.service.user.memory.MemoryUserDatabase$$anonfun$getUser$1.apply(Registration.scala:96)
    at toy.service.user.memory.MemoryUserDatabase$$anonfun$getUser$1.apply(Registration.scala:96)
    at toy.service.user.memory.MemoryUserDatabase$$anonfun$getUser$2.apply(Registration.scala:96)
    at toy.service.user.memory.MemoryUserDatabase$$anonfun$getUser$2.apply(Registration.scala:96)
    at akka.stm.Stm$$anon$1.call(Stm.scala:51)
    at org.multiverse.templates.TransactionBoilerplate.executeWithTransaction(TransactionBoilerplate.java:279)
    at org.multiverse.templates.TransactionBoilerplate.executeChecked(TransactionBoilerplate.java:218)
    at org.multiverse.templates.TransactionBoilerplate.execute(TransactionBoilerplate.java:169)
    at akka.stm.Stm$class.atomic(Stm.scala:50)
    at akka.stm.package$.atomic(package.scala:10)
    at akka.stm.Stm$class.atomic(Stm.scala:47)
    at akka.stm.package$.atomic(package.scala:10)
    at toy.service.user.memory.MemoryUserDatabase.getUser(Registration.scala:95)
    at toy.service.user.UserDatabase$class.available(Registration.scala:88)
    at toy.service.user.memory.MemoryUserDatabase.available(Registration.scala:92)
    at toy.service.user.UserServer$$anonfun$receive$1.apply(Registration.scala:77)
    at toy.service.user.UserServer$$anonfun$receive$1.apply(Registration.scala:76)
    at akka.actor.Actor$class.apply(Actor.scala:478)
    at toy.service.user.UserServer.apply(Registration.scala:74)
    at akka.actor.LocalActorRef.invoke(ActorRef.scala:860)
    at akka.dispatch.MessageInvocation.invoke(MessageHandling.scala:26)
    at akka.dispatch.ExecutableMailbox$class.processMailbox(ExecutorBasedEventDrivenDispatcher.scala:214)
    at akka.dispatch.ExecutorBasedEventDrivenDispatcher$$anon$4.processMailbox(ExecutorBasedEventDrivenDispatcher.scala:120)
    at akka.dispatch.ExecutableMailbox$class.run(ExecutorBasedEventDrivenDispatcher.scala:186)
    at akka.dispatch.ExecutorBasedEventDrivenDispatcher$$anon$4.run(ExecutorBasedEventDrivenDispatcher.scala:120)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:680)
    at akka.dispatch.MonitorableThread.run(ThreadPoolBuilder.scala:181)

The error is slightly more interesting using Scala 2.9.1.final:

Aug 31, 2011 5:38:35 PM org.multiverse.api.GlobalStmInstance <clinit>
INFO: Initializing GlobalStmInstance using factoryMethod 'org.multiverse.stms.alpha.AlphaStm.createFast'.
Aug 31, 2011 5:38:35 PM org.multiverse.api.GlobalStmInstance getMethod
INFO: Failed to initialize GlobalStmInstance through System property 'org.multiverse.api.GlobalStmInstance.factoryMethod' with value 'org.multiverse.stms.alpha.AlphaStm'.'org.multiverse.stms.alpha.AlphaStm.createFast' is not an existing class (it can't be found using the Thread.currentThread.getContextClassLoader).
[ERROR]   [8/31/11 5:38 PM] [akka:event-driven:dispatcher:global-3] [LocalActorRef] Availability(foo)
java.lang.ExceptionInInitializerError
    at akka.stm.TransactionFactory.<init>(TransactionFactory.scala:172)
    at akka.stm.TransactionFactory$.apply(TransactionFactory.scala:122)
    at akka.stm.Stm$class.$init$(Stm.scala:44)
    at akka.stm.package$.<init>(package.scala:10)
    at akka.stm.package$.<clinit>(package.scala)
    at toy.service.user.memory.MemoryUserDatabase.getUser(Registration.scala:95)
    at toy.service.user.UserDatabase$class.available(Registration.scala:88)
    at toy.service.user.memory.MemoryUserDatabase.available(Registration.scala:92)
    at toy.service.user.UserServer$$anonfun$receive$1.apply(Registration.scala:77)
    at toy.service.user.UserServer$$anonfun$receive$1.apply(Registration.scala:76)
    at akka.actor.Actor$class.apply(Actor.scala:478)
    at toy.service.user.UserServer.apply(Registration.scala:74)
    at akka.actor.LocalActorRef.invoke(ActorRef.scala:860)
    at akka.dispatch.MessageInvocation.invoke(MessageHandling.scala:26)
    at akka.dispatch.ExecutableMailbox$class.processMailbox(ExecutorBasedEventDrivenDispatcher.scala:214)
    at akka.dispatch.ExecutorBasedEventDrivenDispatcher$$anon$4.processMailbox(ExecutorBasedEventDrivenDispatcher.scala:120)
    at akka.dispatch.ExecutableMailbox$class.run(ExecutorBasedEventDrivenDispatcher.scala:186)
    at akka.dispatch.ExecutorBasedEventDrivenDispatcher$$anon$4.run(ExecutorBasedEventDrivenDispatcher.scala:120)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:680)
    at akka.dispatch.MonitorableThread.run(ThreadPoolBuilder.scala:181)
Caused by: java.lang.IllegalArgumentException: Failed to initialize GlobalStmInstance through System property 'org.multiverse.api.GlobalStmInstance.factoryMethod' with value 'org.multiverse.stms.alpha.AlphaStm'.'org.multiverse.stms.alpha.AlphaStm.createFast' is not an existing class (it can't be found using the Thread.currentThread.getContextClassLoader).
    at org.multiverse.api.GlobalStmInstance.getMethod(GlobalStmInstance.java:82)
    at org.multiverse.api.GlobalStmInstance.<clinit>(GlobalStmInstance.java:38)
    ... 22 more
Caused by: java.lang.ClassNotFoundException: org.multiverse.stms.alpha.AlphaStm
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at org.multiverse.api.GlobalStmInstance.getMethod(GlobalStmInstance.java:76)
    ... 23 more

This operative part of the code is:

class MemoryUserDatabase extends UserDatabase {
  import MemoryUserDatabase._

  def getUser(email: String) = atomic {
    users.get.get(email)
  }
  def register(user: User) = atomic {
    getUser(user.email) match {
      case None =>
        users alter (_ + (user.email -> user))
        true
      case Some(found) => false
    }
  }
}
object MemoryUserDatabase {
  import scala.collection.mutable.{ Map => MutMap }
  private val users = Ref(MutMap[String, User]())
}

I don't understand why the Companion Object can't be initialized. The strangest part is that you can do the same thing, but if in the server console (#1) you first access the Companion Object:

MemoryUserDatabase

before you run the ServerRunner it initializes just fine and subsequently everything else is just dandy.

Can anyone explain why this is?

Thanks! Idan

PS. This is my first Scala code so try not to laugh too hard... and any other suggestions (stylistic, philisophical, theological, ...) are welcome.

package toy.service.user

import scala.collection.mutable.HashMap

import akka.actor.{ Actor, ActorRef }
import akka.config.Supervision.{ OneForOneStrategy, Permanent }
import Actor._
import akka.routing._
import akka.stm._
import akka.actor.TypedActor
import akka.event.EventHandler

class User(var email: String,
           var password: String) extends Serializable

/** Registration message types.
 */
sealed trait RegistrationMessage
case class Availability(email: String) extends RegistrationMessage
case class GetUser(email: String) extends RegistrationMessage
case class Register(user: User) extends RegistrationMessage

// Client ---------------------------------------
class UserClient(defaultTimeout: Int = 1000) {
  val userService = Actor.remote.actorFor(UserService.USER_SERVICE_ID, "localhost", UserService.USER_SERVICE_PORT)
  EventHandler.info(this, "remote UserService: id(" + userService.id + "), uuid(" + userService.uuid + ")")

  def getUser(email: String, timeout: Int = defaultTimeout): Option[User] = (userService !! (GetUser(email), timeout)).as[User]

  def available(email: String, timeout: Int = defaultTimeout): Boolean =
    (userService !! (Availability(email), timeout)).as[Boolean].getOrElse(throw new RuntimeException("Oi!"))

  def register(user: User, timeout: Int = defaultTimeout): Boolean =
    (userService !! (Register(user), timeout)).as[Boolean].getOrElse(throw new RuntimeException("Got bogus (None) response from " + UserService.USER_SERVICE_ID))
}

// Service Pool ---------------------------------
object UserService {
  val USER_SERVICE_ID = "user:service"
  val USER_SERVICE_PORT = 2662
  val host = "localhost"
}

class UserService extends Actor
  with DefaultActorPool
  with BoundedCapacityStrategy
  with MailboxPressureCapacitor
  with SmallestMailboxSelector
  with BasicFilter {

  import toy.service.user.memory._

  def receive = _route // DefaultActorPool's receive
  def lowerBound = 1
  def upperBound = 5
  def pressureThreshold = 1
  def partialFill = true // never send duplicate messages to same actor (only meaningful if selectionCount > 1)
  def selectionCount = 1 // How many in pool should receive each message
  def rampupRate = 0.1 // increase by 10% capacity (# num actors)
  def backoffRate = 0.50 // halve capacity once backoffThreshold is reached
  def backoffThreshold = 0.50
  def instance = actorOf(new UserServer(new MemoryUserDatabase))

  override def preStart() {
    import UserService.{ host, USER_SERVICE_ID, USER_SERVICE_PORT }

    remote.start(host, UserService.USER_SERVICE_PORT);
    remote.register(UserService.USER_SERVICE_ID, self) //Register the actorPool with the specified service id
    EventHandler.info(this, "Prestart: Started UserService(" + self.uuid + ") on %s:%s".format(host, UserService.USER_SERVICE_PORT.toString()))
  }
}

// Service --------------------------------------
class UserServer(db: UserDatabase) extends Actor {

  def receive = {
    case Availability(email) => self.reply(db.available(email))
    case GetUser(email)      => self.reply(db.getUser(email))
    case Register(user)      => self.reply(db.register(user))
  }
}

// Database -------------------------------------
trait UserDatabase {
  def getUser(email: String): Option[User]
  def register(user: User): Boolean

  def available(email: String): Boolean = getUser(email) == None
}

package memory {
  class MemoryUserDatabase extends UserDatabase {
    import MemoryUserDatabase._

    def getUser(email: String) = atomic {
      users.get.get(email)
    }

    def register(user: User) = atomic {
      getUser(user.email) match {
        case None =>
          users alter (_ + (user.email -> user))
          true
        case Some(found) => false
      }
    }
  }

  object MemoryUserDatabase {
    import scala.collection.mutable.{ Map => MutMap }

    private val users = Ref(MutMap[String, User]())
  }
}

object ServerRunner {
  def run() {
    actorOf[UserService].start()
  }
}

object ClientRunner {
  def run {
    val client = new UserClient
    EventHandler.info(this, client.available("foo"))
  }
}
like image 413
TwistedNoodle Avatar asked Aug 31 '11 20:08

TwistedNoodle


People also ask

What is a companion object in Scala?

A companion object in Scala is an object that's declared in the same file as a class , and has the same name as the class. For instance, when the following code is saved in a file named Pizza.scala, the Pizza object is considered to be a companion object to the Pizza class: class Pizza { } object Pizza { }

Is companion object singleton in Scala?

In scala, when you have a class with same name as singleton object, it is called companion class and the singleton object is called companion object. The companion class and its companion object both must be defined in the same source file.

What is the advantage of companion objects in Scala?

Advantages of Companion Objects in Scala Companion objects provide a clear separation between static and non-static methods in a class because everything that is located inside a companion object is not a part of the class's runtime objects but is available from a static context and vice versa.

How do I create a static variable in Scala?

There are no static variables in Scala. Fields are variables that belong to an object. The fields are accessible from inside every method in the object. Fields can also be accessible outside the object, depending on what access modifiers the field is declared with.


1 Answers

I had a similar problem when I was initializing a TMap in a constructor of an object created during the booting of my app. Somehow my object was being created before the STM static initializers ran. I made my object's field lazy and it fixed the problem.

... actually it just delayed the problem to later in the program. Grrrr.

like image 147
Adam Rosien Avatar answered Nov 15 '22 19:11

Adam Rosien