Акка Актер не заканчивается, если исключено исключение

В настоящее время я пытаюсь начать работу с Akka, и у меня возникла странная проблема. У меня есть следующий код для моего актера:

class AkkaWorkerFT extends Actor {
  def receive = {
    case Work(n, c) if n < 0 => throw new Exception("Negative number")
    case Work(n, c) => self reply n.isProbablePrime(c);
  }
}

И вот как я начинаю своих рабочих:

val workers = Vector.fill(nrOfWorkers)(actorOf[AkkaWorkerFT].start());
val router = Routing.loadBalancerActor(SmallestMailboxFirstIterator(workers)).start()

И вот как я все закрываю:

futures.foreach( _.await )
router ! Broadcast(PoisonPill)
router ! PoisonPill

Теперь, когда я отправляю рабочие сообщения с n > 0 (исключение не генерируется), все работает нормально, и приложение отключается должным образом. Однако, как только я отправляю ему одно сообщение, которое приводит к исключению, приложение не завершается, потому что все еще работает актер, но я не могу понять, откуда он.

В случае, если это помогает, это стек соответствующего потока:

  Thread [akka:event-driven:dispatcher:event:handler-6] (Suspended) 
    Unsafe.park(boolean, long) line: not available [native method]  
    LockSupport.park(Object) line: 158  
    AbstractQueuedSynchronizer$ConditionObject.await() line: 1987   
    LinkedBlockingQueue<E>.take() line: 399 
    ThreadPoolExecutor.getTask() line: 947  
    ThreadPoolExecutor$Worker.run() line: 907   
    MonitorableThread(Thread).run() line: 680   
    MonitorableThread.run() line: 182   

PS: Нить, которая не завершается, не является ничем из рабочих потоков, потому что я добавил обратный вызов postStop, каждый из них останавливается должным образом.

PPS: Actors.registry.shutdownAll Обходные пути проблемы, но я думаю, shutdownAll следует использовать только в качестве последнего средства, не так ли?

Ответ 1

Правильный способ справиться с проблемами внутри аккских акков - это не исключение, а установление иерархии диспетчера

"Выбрасывание исключения в параллельном коде (предположим, что мы используем не связанных между собой участников), просто просто взорвет поток, который в настоящее время исполняет актер.

Невозможно узнать, что все пошло не так (кроме проверка трассировки стека). Вы ничего не можете с этим поделать.

см. Отказоустойчивость через иерархии супервизора (1.2)

* note * выше, это верно для старых версий Akka (1.2) В более новых версиях (например, 2.2) вы все равно установили иерархию диспетчера, но в нем будут помещены исключения, вызванные дочерними процессами. например.

class Child extends Actor {
    var state = 0
    def receive = {
      case ex: Exception ⇒ throw ex
      case x: Int        ⇒ state = x
      case "get"         ⇒ sender ! state
    }
  }

и в супервизоре:

class Supervisor extends Actor {
    import akka.actor.OneForOneStrategy
    import akka.actor.SupervisorStrategy._
    import scala.concurrent.duration._

    override val supervisorStrategy =
      OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
        case _: ArithmeticException      ⇒ Resume
        case _: NullPointerException     ⇒ Restart
        case _: IllegalArgumentException ⇒ Stop
        case _: Exception                ⇒ Escalate
      }

    def receive = {
      case p: Props ⇒ sender ! context.actorOf(p)
    }
  }

см. Отказоустойчивость через иерархии супервизора (2.2)

Ответ 2

Отключение регистрации, чтобы убедиться, что все закончилось, как предложил Виктор, немного странно. Вместо этого вы можете:

EventHandler.shutdown()

который полностью отключает всех слушателей (регистраторов), которые заставляют мир работать после исключения:

def shutdown() {
  foreachListener(_.stop())
  EventHandlerDispatcher.shutdown()
}

Ответ 3

Поворот регистратора в akka.conf