Почему Scala не создает экземпляр объекта Companion?

Я новичок в Scala и Akka, простите меня, если это новый вопрос, но я не могу найти ответ нигде...

Для записи я использую Scala 2.9.0-1 и Akka 1.1.3, а также включил мою настройку SBT 0.10.1.

Я написал код, который следует за этим сообщением как эксперимент в Акке; это игрушечная версия пользовательской базы данных и средств регистрации. Основная идея заключается в том, что есть ActorPool для пользователей UserServer, у каждого из которых есть экземпляр базы данных MemoryUserDatabase, которая использует STM для взаимодействия с Map of Users, с помощью которых удаляются адреса электронной почты пользователей - довольно просто, не так ли?

Проблема может быть воспроизведена путем компиляции файла и выполнения следующего в двух отдельных консолях:

Консоль # 1:

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

Консоль # 2:

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

Это вывод с консоли сервера (# 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)

Ошибка несколько интереснее, используя 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

Эта оперативная часть кода:

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]())
}

Я не понимаю, почему объект Companion не может быть инициализирован. Самая странная часть - это то, что вы можете сделать то же самое, но если на консоли сервера (№1) вы сначала получите доступ к объекту Companion:

MemoryUserDatabase

прежде чем запускать ServerRunner, он инициализируется просто отлично, а затем все остальное просто денди.

Может кто-нибудь объяснить, почему это?

Спасибо! Идан

PS. Это мой первый код Scala, поэтому старайтесь не слишком усмехаться... и любые другие предложения (стилистические, филисофические, теологические,...) приветствуются.

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 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"))
  }
}

Ответ 1

У меня была аналогичная проблема, когда я инициализировал TMap в конструкторе объекта, созданного во время загрузки моего приложения. Как-то мой объект создавался до запуска статических инициализаторов STM. Я сделал свое поле объекта ленивым, и это устранило проблему.

... на самом деле это просто отложило проблему до конца в программе. Grrrr.

Ответ 2

Я столкнулся с той же проблемой. Добавление зависимостей "org.multiverse.multiverse-alpha-unborn" решило проблему для меня.