Reputation: 17501
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 var
s 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
}
}
}
Upvotes: 5
Views: 288
Reputation: 15074
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.
Upvotes: 5
Reputation: 4716
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.
Upvotes: 1