I have a case object in an actor like this:
object UserActor{
case object getResult
def props = Props(new UserActor)
}
class UserActor extends Actor {
def receive = {
case getResult =>
val reply = sender
reply ! "Hello world"
}
}
which I want to use in a route as this:
val route: Route =
path("users") { id =>
get {
complete {
userActor ? getResult
}
}
}
However, I got the error, expected ToResponseMarshallable. So question, how do I marshall case object using SprayJsonSupport?
Related
I have a simple actor system with default configuration.
I have a class extended Actor
class Test extend Actor {
def receive: Receive = {
case Foo(collection) => sender ! extract(collection)
}
private def extract(c: List[FooItem]): List[BarItem] = ???
}
This actor have a companion object
object Test {
def props: Props = ???
}
Is there are safe to do function extract like this:
object Test {
def props: Props = ???
def extract(c: List[FooItem]): List[BarItem] = ???
}
and use from another Actor ?
Yes, it's ok to define a method on a companion and then import and use that method in an actor class. Something like this would work just fine:
object Test {
def props: Props = Props[Test]
def extract(c: List[FooItem]): List[BarItem] = {
. . .
}
}
class Test extend Actor {
import Test._
def receive: Receive = {
case Foo(collection) => sender ! extract(collection)
}
}
The following code example (which you can copy and run) shows a MyParentActor that creates a MyChildActor.
The MyChildActor throws an exception for its first message which causes it to be restarted.
However, what I want to achieve is for "Message 1" to still be processed before "Message 2" on restart of the MyChildActor.
Instead, what is happening is that Message 1 is added to the tail of the mailbox queue, and so Message 2 is processed first.
How do I achieve ordering of the original messages on restart of an actor, without having to create my own mailbox etc?
object TestApp extends App {
var count = 0
val actorSystem = ActorSystem()
val parentActor = actorSystem.actorOf(Props(classOf[MyParentActor]))
parentActor ! "Message 1"
parentActor ! "Message 2"
class MyParentActor extends Actor with ActorLogging{
var childActor: ActorRef = null
#throws[Exception](classOf[Exception])
override def preStart(): Unit = {
childActor = context.actorOf(Props(classOf[MyChildActor]))
}
override def receive = {
case message: Any => {
childActor ! message
}
}
override def supervisorStrategy: SupervisorStrategy = {
OneForOneStrategy() {
case _: CustomException => Restart
case _: Exception => Restart
}
}
}
class MyChildActor extends Actor with ActorLogging{
override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
message match {
case Some(e) => self ! e
}
}
override def receive = {
case message: String => {
if (count == 0) {
count += 1
throw new CustomException("Exception occurred")
}
log.info("Received message {}", message)
}
}
}
class CustomException(message: String) extends RuntimeException(message)
}
You could mark the failing message with a special envelope and stash everything up to the receiving of that message (see child actor implementation). Just define a behaviour where the actor stashes every message except for the specific envelope, processes it's payload and then unstashes all other messages and returns to it's normal behaviour.
This gives me:
INFO TestApp$MyChildActor - Received message Message 1
INFO TestApp$MyChildActor - Received message Message 2
object TestApp extends App {
var count = 0
val actorSystem = ActorSystem()
val parentActor = actorSystem.actorOf(Props(classOf[MyParentActor]))
parentActor ! "Message 1"
parentActor ! "Message 2"
class MyParentActor extends Actor with ActorLogging{
var childActor: ActorRef = null
#throws[Exception](classOf[Exception])
override def preStart(): Unit = {
childActor = context.actorOf(Props(classOf[MyChildActor]))
}
override def receive = {
case message: Any => {
childActor ! message
}
}
override def supervisorStrategy: SupervisorStrategy = {
OneForOneStrategy() {
case e: CustomException => Restart
case _: Exception => Restart
}
}
}
class MyChildActor extends Actor with Stash with ActorLogging{
override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
message match {
case Some(e) =>
self ! Unstash(e)
}
}
override def postRestart(reason: Throwable): Unit = {
context.become(stashing)
preStart()
}
override def receive = {
case message: String => {
if (count == 0) {
count += 1
throw new CustomException("Exception occurred")
}
log.info("Received message {}", message)
}
}
private def stashing: Receive = {
case Unstash( payload ) =>
receive(payload)
unstashAll()
context.unbecome()
case m =>
stash()
}
}
case class Unstash( payload: Any )
class CustomException(message: String) extends RuntimeException(message)
}
Actually I`m having trouble with getting my actor (router) system to work correctly.
My Setup:
I`m trying to use an akka router within an play controller. For dependency injection I use scaldi.
scaldi module:
class UserDAOModule extends Module {
binding to new ExampleRouter
binding toProvider new UserDAOWorker
}
akka router:
class UserDAORouter(implicit inj:Injector) extends Actor with AkkaInjectable {
val userDAOProps = injectActorProps[UserDAOWorker]
var router = {
val routees = Vector.fill(5) {
val r = context.actorOf(userDAOProps)
context watch r
ActorRefRoutee(r)
}
Router(RoundRobinRoutingLogic(), routees)
}
override def receive: Receive = {
case mm: MongoDBMessage =>
router.route(mm, sender)
case Terminated(a) =>
router = router.removeRoutee(a)
val r = context.actorOf(userDAOProps)
context watch r
router = router.addRoutee(r)
}
}
worker:
class UserDAOWorker(implicit inj:Injector) extends Actor with Injectable {
val db = inject[DefaultDB]
val collection:JSONCollection = db("users")
val currentSender = sender
override def receive: Receive = {
case InsertUser(user) => insertUser(user)
}
def insertUser(user:User) = {
collection.save(user).onComplete {
case Failure(e) => currentSender ! new UserDAOReturnMessage(Some(e), None)
case Success(lastError) => currentSender ! new UserDAOReturnMessage(None, lastError)
}
}
}
When I send a message (insertUser message) to the router, it is routed correctly and the worker receives the message, but when the worker sends a message back to the sender it cant be delivered, so it is send to dead letter office. I cant figure out how to fix this. Is there someone able to help me?
Thanks in advance
I guess the problem is that the currentSender is initialized with null (i.e. ActorRef.noSender) in the constructor on actor creation. 'sender' is only valid in context of receiving a message in receive(). Sending a message to ActorRef.noSender is an equivalent of sending to dead letter queue.
Something like this should work:
class UserDAOWorker(implicit inj:Injector) extends Actor with Injectable {
val db = inject[DefaultDB]
val collection:JSONCollection = db("users")
override def receive: Receive = {
case InsertUser(user) => {
insertUser(sender, user)
}
}
def insertUser(currentSender : ActorRef, user:User) = {
collection.save(user).onComplete {
case Failure(e) => currentSender ! new UserDAOReturnMessage(Some(e), None)
case Success(lastError) => currentSender ! new UserDAOReturnMessage(None, lastError)
}
}
}
i have a next code
class MySystem(outerResourse: OuterResourse) extends Actor {
val firstActor = context.actorOf(Props(new FirstActor(outerResourse)), "first")
val secondActor = context.actorOf(Props(new SecondActor(firstActor)), "second")
secondActor ! Go
def receive = {
case x: AnotherMessage => printl(s"another message: $x")
case x => println(x)
}
}
class FirstActor(outerResourse: OuterResourse) extends Actor {
def receive = {
case Test =>
context.parent ! AnotherMessage
sender ! "ok"
}
}
class SecondActor(firstActor: ActorRef) extends Actor {
def receive = {
case Go => firstActor ! Test
case "ok" => println("ok")
}
}
Here OuterResourse is a any resourse - file, internet connection...
i would like to check a behavior, but i in embarrassment, i do not know how to check that second actor will be got a "ok", and mySystem actor will be got a AnotherMessage
class MyTest(_system: ActorSystem) extends TestKit(_system)
with ImplicitSender with FunSpecLike with Matchers {
def this() = this(ActorSystem("myTest"))
val outerResourse = new OuterResourse()
val mySystem = system.actorOf(Props(new MySytem(outerResourse)))
describe("Actors") {
it("should get AnotherMessage message and ok message") {
???
}
}
}
Hot to check, that a secondActor got a "ok" message?
You could set custom OutputStream for println usnig Console.withOut. It could be mock and "wait" for OK message from first actor. But it is not very nice....
// edit: please read documentation http://doc.akka.io/docs/akka/snapshot/scala/testing.html
Akka provide they owm testing "framework"
I have the next code:
//TestActor got some message
class TestActor extends Actor {
def receive = {
case string: String => //....
}
}
//TestReg when create get ActorRef, when i call `pass` method, then should pass text to ActorRef
class TestReg(val actorRef: ActorRef) {
def pass(text: String) {
actorRef ! text
}
}
When i wrote test:
class TestActorReg extends TestKit(ActorSystem("system")) with ImplicitSender
with FlatSpecLike with MustMatchers with BeforeAndAfterAll {
override def afterAll() {
system.shutdown()
}
"actorReg" should "pass text to actorRef" in {
val probe = TestProbe()
val testActor = system.actorOf(Props[TestActor])
probe watch testActor
val testReg = new TestReg(testActor)
testReg.pass("test")
probe.expectMsg("test")
}
}
I got error:
java.lang.AssertionError: assertion failed: timeout (3 seconds) during expectMsg while waiting for test
How to check what the actor got a text?
probe.expectMsg() is calling the assertion on the probe. But you passed the testActor into your TestReg class
change it to the following line and it will work
val testReg = new TestReg(probe.ref)
have to call .ref to make the probe into an ActorRef
And you want to do it here not at the instantiation of the variable to avoid
certain bugs that are outside of the scope of this response
the error in the logic as I see it is you are thinking that the watch method makes probe see what test actor does. but its death watch not message watch. which is different.
Create application.conf file with this:
akka {
test {
timefactor = 1.0
filter-leeway = 999s
single-expect-default = 999s
default-timeout = 999s
calling-thread-dispatcher {
type = akka.testkit.CallingThreadDispatcherConfigurator
}
}
actor {
serializers {
test-message-serializer = "akka.testkit.TestMessageSerializer"
}
serialization-identifiers {
"akka.testkit.TestMessageSerializer" = 23
}
serialization-bindings {
"akka.testkit.JavaSerializable" = java
}
}
}