I've a PropertiesStore actor that holds a mutable.Buffer of runtime configuration that's manipulated through a rest API. You read and write from said store using these objects:
Read(None, Property(key ="MyConfigKey", value = None))
to which the store responds with the Property and its value.
In order to ease the locating of this store, I have another actor (a cameo) that I create on the fly:
object PropertyLookup {
def apply(propertyKey: String, target: Option[ActorRef]): Props = {
Props(new PropertyLookup(propertyKey, target))
}
}
class PropertyLookup(key: String, target: Option[ActorRef]) extends Actor with ActorLogging {
val PropertiesStore = "/user/Master/DataStore/PropertiesStore"
val propertiesActor = context.actorSelection(PropertiesStore)
def receive: Actor.Receive = {
case _=>
log.info(s"Looking up ${key} via ${propertiesActor}")
propertiesActor.tell(Read(None, Property(key, None)), target.getOrElse(sender()))
log.info(s"Sent property lookup to PropertiesStore: ${key}")
// context.stop(self)
}
}
Which enables me to keep the "locating" of said actor (i.e. via its path) in one place, avoiding too much rework if it's moved. This PropertyLookup has an optional target actor that the PropertiesStore will send the result to once the lookup is performed. I use all this like so in another actor to get a config value before I do some work:
context.actorOf(PropertyLookup(PropertyKeys.SpreadsheetURL, None), s"PropertiesLookup_${self.path.name}") ! None
but when I do so, I get the following warning:
Message [domain.Read] from
Actor[akka://NN/user/$a/Master/DataStore/PlayerStore#-1100303450] to
Actor[akka://NN/user/Master/DataStore/PropertiesStore] was not
delivered. [2] dead letters encountered. This logging can be turned
off or adjusted with configuration settings 'akka.log-dead-letters'
and 'akka.log-dead-letters-during-shutdown'
and I never receive my Property instance in the callee. The cameo (instance of PropertyLookup) does do its stuff and log, so I know that part is working.
What's happening? Is my cameo all wrong? Any better ways to do this? How do I understand what's happening around this warning>
Is your code and log outputs up to date?
First you gave definition of Read as
Read(Property(key ="MyConfigKey", value = None))
but then you are using it as
Read(None, Property(key, None))
Secondly you are creating actor with "PropertiesLookup_${self.path.name}" in name. But your log output shows that this actor name doesn't contain "PropertiesLookup" string.
In addition, notice that your path as logged includes a $a which suggests that an anonymous actor is in fact in the path, which isn't what's in your actorSelection call.
Related
Let's say I have an Actor (OrderGenerator) that in response to an incoming message (GenerateOrder), sends a message to another Actor (OrderProcessor) of type Order.
case class Order(orderId: String, partNumber: String, orderQty: Int) // ...etc.
In OrderGenerator's receiveMessage() method for the GenerateOrder message, we create an Order object. The orderId value is generated at this time (let's say it's a UUID generated on the fly). It then sends this Order as a message to the OrderProcessor Actor.
Behaviors.receiveMessage {
case GenerateOrder(partNumber: String, orderQty: Int) =>
val newOrder = Order(orderId=GenerateUUID(), partNumber=partNumber, orderQty=orderQty)
orderProcessor ! newOrder
Behaviors.same
Ok, now I want to test this.
val inbox = TestInbox[Order]()
val TestKit = BehaviorTestKit(OrderGenerator(orderProcessor=inbox.ref))
testKit.run(GenerateOrder(partNo="widgetX", orderQty=5))
inbox.expectMessage(Order( ??????? ))
As you can see, I don't know what the full details are of the expected Order, since the orderId is generated by OrderGenerator at runtime, and I can't predict what the UUID will be.
How can I test that an Order message is sent, without knowing what the details will be (or only some of them)?
I know this might sound kind of an "unsafe approach" (I don't think there's any other choice though), but just took a look at docs of akka TestInbox, you can use these 2 methods and use other fields of your order as assertions:
assert(inbox.hasMessages())
val message = inbox.receiveMessage()
assert(message.isInstanceOf[Order]) // or essentially any other comparison, like message.partNo == "widgetX"
I am creating an actor via:
system.actorOf(Props(....))
or
system.actorOf(SmallestMailboxPool(instances).props(Props(....))).
I usually block the thread calling system.actorOf till actorSelection works.
Await.result(system.actorSelection("/user/" + name).resolveOne(), timeout.duration)
I am wondering if this is at all needed or I can immediately start using the actorRef and send (tell) messages to the actor/actor pool.
So the question boils down to, if I have an actorRef, does that mean the mailbox is already created or it might so happen that the messages I sent immediately after calling system.actorOf might get dropped?
If you drill down the the implementation of system.actorOf, you see a call to a method names makeChild. Internally, this utilizes a lengthy method on the ActorRefProvider trait (internally using LocalActorRefProvider) called actorOf. This rather lengthy method initializes the child actor. Relevant parts are:
val props2 =
// mailbox and dispatcher defined in deploy should override props
(if (lookupDeploy) deployer.lookup(path) else deploy) match {
case Some(d) ⇒
(d.dispatcher, d.mailbox) match {
case (Deploy.NoDispatcherGiven, Deploy.NoMailboxGiven) ⇒ props
case (dsp, Deploy.NoMailboxGiven) ⇒ props.withDispatcher(dsp)
case (Deploy.NoMailboxGiven, mbx) ⇒ props.withMailbox(mbx)
case (dsp, mbx) ⇒ props.withDispatcher(dsp).withMailbox(mbx)
}
case _ ⇒ props // no deployment config found
}
Or if a Router is explicitly provided:
val routerDispatcher = system.dispatchers.lookup(p.routerConfig.routerDispatcher)
val routerMailbox = system.mailboxes.getMailboxType(routerProps, routerDispatcher.configurator.config)
// routers use context.actorOf() to create the routees, which does not allow us to pass
// these through, but obtain them here for early verification
val routeeDispatcher = system.dispatchers.lookup(p.dispatcher)
val routeeMailbox = system.mailboxes.getMailboxType(routeeProps, routeeDispatcher.configurator.config)
new RoutedActorRef(system, routerProps, routerDispatcher, routerMailbox, routeeProps, supervisor, path).initialize(async)
Which means that once you get back an ActorRef, the mailbox has been initialized and you shouldn't be scared of sending it messages.
If you think about the semantics of what an ActorRef stands for, it would be a bit pointless to provide one with an ActorRef which is partly/not initialized. It would make system guarantees weak and would make the programmer think twice before passing messages, which is the opposite desire of the framework.
While refactoring actor codes written by some other programmers, I encountered the usage of Future.onComplete callback within actor A, which goes against the best practice of using akka.pattern.pipe. It's a bad idea since it exposes the possibility of race conditions as the Future instance might be executed on a different thread.
Looking at the code, we see that there are neither sender nor any mutable vars being referred within the onComplete block so it seems pretty safe, at least for this specific occasion. However, one grey area that leaves me wondering are the references to url and especially text.
Is it possible that similar to the Closing Over An Akka Actor Sender In The Receive problem, a race condition happens such that at the time when the onComplete callback is invoked, the value of text already refers to a different actor message, causing all hells to break loose?
class B extends akka.actor.Actor {
def receive = {
case urlAndText: (String, String) => // do something
}
}
class A extends akka.actor.Actor {
case class Insert(url: String)
def fileUpload(content: String): String = ??? // returns the url of the uploaded content
val b = context.actorOf(Props(classOf[B]))
def receive = {
case text: String =>
Future {
fileUpload(text)
} onComplete {
case Success(url) =>
b ! Insert(url, text) // will this be
}
}
}
The reference to text should be fine. The difference is that each "instance" of text is a new variable bound to the scope of the current match block (starting at case text ...). Thus the Future created closes over the value of text just fine.
This is different from sender (or sender() when de-sugared) which is actually a method defined on the Actor trait which returns the ActorRef of the sender of the most recent message received by the actor on which it is called, and so can give a different value when called later (when the Future's onComplete is finally called).
You are right to be suspect about the use of onComplete, too. A better option would be:
case text: String =>
Future {
fileUpload(text)
} map { url =>
Insert(url, text)
} pipeTo b
Now failures will also be sent on to b, rather than being quietly swallowed.
In this case you aren't going to run into any race conditions although as you noted it is not a particularly good idea to structure code this way in general.
The references to both url and text are fine. The value for url is simply an extraction of a successfully completed Future and that doesn't change whether you're in an Actor or not. The value for text is an immutable string and closing over that value in a Future shouldn't cause problems because that string instance is immutable.
As you noted closing over sender or a var is a problem and that's because being mutable objects those values could change by the time the Future completes unlike immutable values which will remain constant even when you close over them.
Akka Cluster-Sharding looks like it matches well with a use case I have to create single instances of stateful persistent actors across Akka nodes.
I'm not clear if it is possible though to have an Entry actor type that requires arguments to construct it. Or maybe I need to reconsider how the Entry actor gets this information.
Object Account {
def apply(region: String, accountId: String): Props = Props(new Account(region, accountId))
}
class Account(val region: String, val accountId: String) extends Actor with PersistentActor { ... }
Whereas the ClusterSharding.start takes in a single Props instance for creating all Entry actors.
From akka cluster-sharding:
val counterRegion: ActorRef = ClusterSharding(system).start(
typeName = "Counter",
entryProps = Some(Props[Counter]),
idExtractor = idExtractor,
shardResolver = shardResolver)
And then it resolves the Entry actor that receives the message based on how you define the idExtractor. From the source code for shard it can be seen it uses the id as the name for a given Entry actor instance:
def getEntry(id: EntryId): ActorRef = {
val name = URLEncoder.encode(id, "utf-8")
context.child(name).getOrElse {
log.debug("Starting entry [{}] in shard [{}]", id, shardId)
val a = context.watch(context.actorOf(entryProps, name))
idByRef = idByRef.updated(a, id)
refById = refById.updated(id, a)
state = state.copy(state.entries + id)
a
}
}
It seems I should instead have my Entry actor figure out its region and accountId by the name it is given, although this does feel a bit hacky now that I'll be parsing it out of a string instead of directly getting the values. Is this my best option?
I am in a very similar situation as yours. I don't have an exact answer but I can share with you and the readers what I did/tried/thought.
Option 1) As you mentioned, you can extract id, shard and region information from how you name your stuff and parsing the path. The upside is
a) that it's kind of easy to do.
The downsides are that
a) Akka encodes actor paths as UTF-8, so if you are using anything as a separator that is not a standard url character (such as || or w/e) you will need to first decode it from utf8. Note that inside Akka utf8 is hard-coded as encoding method, there is no way to extract the encoding format as in a function, so if tomorrow akka changes you'll have to adapt your code too.
b) your system is not preserving homomorphism anymore (what you mean by "it feels kinda hacky"). Which implies that you are adding the risk that your data, one day, may contain your information separator string as meaningful data and your system may mess up.
Option 2) Sharding will spawn your actor if it doesn't exist. So you can force your code to always send an init message to non initialized actors, which contains your constructor parameters. Your sharded actors will have something inside of them of the kind:
val par1: Option[param1Type] = None
def receive = {
case init(par1value) => par1 = Some(par1value)
case query(par1) => sender ! par1
}
And from your region access actor you can always send first the query message and then the init message if the return is None. This assumes that your region access actor does not mantain a list of the initialized actors, in which case you can just spawn with init and then use them normally.
The upside is
a) It's elegant
b) it "feels" right
Downside: a) it takes 2x messages (if you don't maintain a list of initialized actors)
Option 3) THIS OPTION HAS BEEN TESTED AND DOESN'T WORK. I'll just leave it here for people to avoid wasting time trying the same.
I have no idea if this works, I haven't tested because I'm using this scenario in production with special constraints and fancy stuff is not allowed ^_^ But feel free to try and please let me know with a pm or comment!
Basically, you start your region with
val counterRegion: ActorRef = ClusterSharding(system).start(
typeName = "Counter",
entryProps = Some(Props[Counter]),
idExtractor = idExtractor,
shardResolver = shardResolver)
What if you, in your region creation actor, do something like:
var providedPar1 = v1
def providePar1 = providedPar1
val counterRegion: ActorRef = ClusterSharding(system).start(
typeName = "Counter",
entryProps = Some(Props(classOf[Counter], providePar1),
idExtractor = idExtractor,
shardResolver = shardResolver)
And then you change the value of providedPar1 for each creation? The downside of this is that, in the option it works, you'd need to avoid changing the value of providedPar1 until you are 100% sure that the actor has been created, or you may risk it accessing the new, wrong value (yay, race conditions!)
In general you are better off with option 2 imho, but in most scenarios the risks introduced by 1 are small and you can mitigate them properly given the simplicity (and performance) advantages.
Hope this rant helps, let me know if you try 3 out how it works!
I am new to Scala programming and having some trouble understanding how actors works and how to use them correctly.
Looking at the source code of an Akka actor, the following is exposed:
trait Actor {
def receive: Actor.Receive // Actor.Receive = PartialFunction[Any, Unit]
}
My first impression of this is that Actor is a trait that exposes one abstract method, receive which takes no arguments and then returns a partial function. First question, is this the correct interpretation of the API?
Next, I looked at the documentation for how to implement actors. The examples look something like this:
class HelloActor extends Actor {
def receive = {
case "hello" => println("hello back at you")
case _ => println("huh?")
}
}
Clearly there is some pattern matching going on here, but I'm having trouble understanding how this works. For instance, let's say I wanted to invoke the receive method directly without using something like send, how would I do it?
as others already answered, you never should directly call methods on Actors. But your question seems to be more about "how does PartialFunction work in Scala?", right?
In Scala the PartialFunction[In, Out] has a bit of compiler generated code for methods like isDefinedAt, so you can ask a partial function if it's defined for a certain argument:
scala> val fun: PartialFunction[Any, String] = {
| case 42 => "yes"
| }
fun: PartialFunction[Any,String] = <function1>
scala> fun.isDefinedAt(12)
res0: Boolean = false
scala> fun.isDefinedAt(42)
res1: Boolean = true
scala> fun(42)
res2: String = yes
scala> fun(12)
scala.MatchError: 12 (of class java.lang.Integer)
```
We use the isDefinedAt method before we apply a message to a receive function, and if it's not able to handle the message we pass it into unhandled(msg), which usually just logs the message to dead letters so you can figure out that your Actor didn't handle a message.
For instance, let's say I wanted to invoke the receive method directly without using something like send, how would I do it?
You would not.
Directly invoking the receive function of an actor without going through its ActorRef breaks the guarantees of the actor model:
Actors process exactly one message at a time.
An actor's internal state can only be mutated from the receive function.
(among others, see What is an actor?)
When you send an ActorRef a message, you aren't directly talking to the actor. Hidden behind the ActorRef are any number of implementation details—you could be sending a message to a router or a remote actor or a dead actor (or all three!). Isolating an actor's details behind this reference allows the guarantees that are the entire point of the actor model.