Как нам с вами известно из опыта программирования на Erlang , при работе с акторами иногда могут возникать дэдлоки. Актор А1 шлет запрос с помощью gen_server::call актору А2, тот в свою очередь спрашивает что-то у А1, но А1 ему не отвечает, так как сам еще ждет ответа от А2. Дэдлок, отваливаемся по таймауту. К счастью, когда вы сталкиваетесь с этой проблемой, в логах есть стектрейсы, позволяющие легко диагностировать и исправить ошибку. Так вот, а совсем недавно я узнал, что в Akka дэдлок в этом случае не произойдет… за исключением одного граничного случая, о котором будет рассказано далее.

Рассмотрим следующие код:

package me. eax . akka_examples

import akka. actor . _
import akka. pattern . { ask, pipe }
import akka. util . Timeout

import scala. concurrent . _
import scala. concurrent . duration . _
import scala. concurrent . ExecutionContext . Implicits . global

case class SecondActorRequest ( second : ActorRef )
case class SecondActorResponse ( trace : Seq [ String ] )

case object FirstActorRequest
case class FirstActorResponse ( trace : Seq [ String ] )

case object FirstActorSimpleRequest
case class FirstActorSimpleResponse ( trace : Seq [ String ] )

class SecondActor extends Actor {
implicit val timeout = Timeout ( 5 seconds )

def receive = {
case r : SecondActorRequest =>
val fResp = /*** UNDER CONSTRUCTION ***/
fResp. map ( resp => SecondActorResponse ( resp. trace : + «a2_resp» ) )
. pipeTo ( sender )
}
}

class FirstActor extends Actor {
implicit val timeout = Timeout ( 5 seconds )
val secondActor = context. actorOf ( Props ( new SecondActor ) , «A2» )

def receive = {
case FirstActorRequest =>
val fResp = ( secondActor ? SecondActorRequest ( self ) )
. mapTo [ SecondActorResponse ]
fResp. map ( resp => FirstActorResponse ( resp. trace : + «a1_resp» ) )
. pipeTo ( sender )

case FirstActorSimpleRequest =>
sender ! FirstActorSimpleResponse ( Seq ( «a1_simple_resp» ) )
}
}

object Example3 extends App {
implicit val timeout = Timeout ( 5 seconds )
val system = ActorSystem ( «system» )
val firstActor = system. actorOf ( Props ( new FirstActor ) , «A1» )

val fResp = (
firstActor ? FirstActorRequest
) . mapTo [ FirstActorResponse ]
fResp map { resp =>
println ( s «RESPONSE: $resp» )
system. shutdown ( )
}

system. awaitTermination ( )
}

Итак, в A1 приходит запрос от пользователя, он шлет запрос A2… в этом месте возможны варианты. Рассмотрим самый простой, A2 вообще пока что не ходит в актор A1:

val fResp = Future successful {
FirstActorResponse ( Seq ( «fake_a1_resp» ) )
}

В результате A1 получает ответ и в свою очередь шлет ответ пользователю. При этом сообщения, обмен которым производится, содержат в себе трейс всего происходящего. В этом, самом простом, случае трейс будет таким:

List(fake_a1_resp, a2_resp, a1_resp)

Теперь, собственно, рассмотрим интересующий нас случай. При получении запроса от A1 актор A2 в свою очередь ходит за какой-то информацией обратно в актор A1:

val fResp = ( r. second ? FirstActorSimpleRequest )
. mapTo [ FirstActorSimpleResponse ]

Как уже отмечалось, в Erlang в этом случае мы бы получили дэдлок. Однако в Akka все прекрасно отработает:

List(a1_simple_resp, a2_resp, a1_resp)

Так происходит по той причине, что акторы в Akka не блокируются, а сразу возвращают футуры . Очень хорошо!

Наконец, рассмотрим обещанный граничный случай. При получении запроса от A1 актор A2 идет в A1 с тем же запросом, что вызвал обращение к A2:

val fResp = (r.second ? FirstActorRequest).mapTo[FirstActorResponse]

То есть, получается, что A1 спрашивает что-то у A2, тот спрашивает у A1, тот снова спрашивает у A2 и так до бесконечности. При этом создаются новые и новые футуры. Легко догадаться, что память очень быстро закончится (или не так уж быстро, если вы забыли про -Xmx ) и мы увидим:

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread «system-scheduler-1»

Строго говоря, это не дэдлок — программа делает в точности то, что вы ей сказали, непрерывно шлет запросы от A1 к A2 и обратно.

Такое вот интересное наблюдение.

Дополнение: Пример работы с шедулером в Akka

EnglishRussianUkrainian