A suggestion how things could be done otherwise.
This commit is contained in:
parent
58dcfb1004
commit
cc3f576f0a
4 changed files with 39 additions and 57 deletions
|
|
@ -9,7 +9,8 @@ import akka.japi.{ Function ⇒ JFunc, Procedure ⇒ JProc }
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import scala.concurrent.stm._
|
import scala.concurrent.stm._
|
||||||
import scala.concurrent.{ Future, Promise, Await }
|
import concurrent.{ ExecutionContext, Future, Promise, Await }
|
||||||
|
import concurrent.util.Duration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used internally to send functions.
|
* Used internally to send functions.
|
||||||
|
|
@ -22,7 +23,7 @@ private[akka] case object Get
|
||||||
* Factory method for creating an Agent.
|
* Factory method for creating an Agent.
|
||||||
*/
|
*/
|
||||||
object Agent {
|
object Agent {
|
||||||
def apply[T](initialValue: T)(implicit system: ActorSystem) = new Agent(initialValue, system)
|
def apply[T](initialValue: T)(implicit system: ActorSystem) = new Agent(initialValue, system, system)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -95,9 +96,11 @@ object Agent {
|
||||||
* agent4.close
|
* agent4.close
|
||||||
* }}}
|
* }}}
|
||||||
*/
|
*/
|
||||||
class Agent[T](initialValue: T, system: ActorSystem) {
|
class Agent[T](initialValue: T, refFactory: ActorRefFactory, system: ActorSystem) {
|
||||||
private val ref = Ref(initialValue)
|
private val ref = Ref(initialValue)
|
||||||
private val updater = system.actorOf(Props(new AgentUpdater(this, ref))).asInstanceOf[InternalActorRef] //TODO can we avoid this somehow?
|
private val updater = refFactory.actorOf(Props(new AgentUpdater(this, ref))).asInstanceOf[InternalActorRef] //TODO can we avoid this somehow?
|
||||||
|
|
||||||
|
def this(initialValue: T, system: ActorSystem) = this(initialValue, system, system)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the internal state of the agent.
|
* Read the internal state of the agent.
|
||||||
|
|
@ -114,23 +117,25 @@ class Agent[T](initialValue: T, system: ActorSystem) {
|
||||||
*/
|
*/
|
||||||
def send(f: T ⇒ T): Unit = {
|
def send(f: T ⇒ T): Unit = {
|
||||||
def dispatch = updater ! Update(f)
|
def dispatch = updater ! Update(f)
|
||||||
val txn = Txn.findCurrent
|
Txn.findCurrent match {
|
||||||
if (txn.isDefined) Txn.afterCommit(status ⇒ dispatch)(txn.get)
|
case Some(txn) ⇒ Txn.afterCommit(status ⇒ dispatch)(txn)
|
||||||
else dispatch
|
case _ ⇒ dispatch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a function to update the internal state, and return a Future where
|
* Dispatch a function to update the internal state, and return a Future where
|
||||||
* that new state can be obtained within the given timeout.
|
* that new state can be obtained within the given timeout.
|
||||||
*/
|
*/
|
||||||
def alter(f: T ⇒ T)(timeout: Timeout): Future[T] = {
|
def alter(f: T ⇒ T)(implicit timeout: Timeout): Future[T] = {
|
||||||
def dispatch = ask(updater, Alter(f))(timeout).asInstanceOf[Future[T]]
|
def dispatch = ask(updater, Alter(f)).asInstanceOf[Future[T]]
|
||||||
val txn = Txn.findCurrent
|
Txn.findCurrent match {
|
||||||
if (txn.isDefined) {
|
case Some(txn) ⇒
|
||||||
val result = Promise[T]()
|
val result = Promise[T]()
|
||||||
Txn.afterCommit(status ⇒ result completeWith dispatch)(txn.get)
|
Txn.afterCommit(status ⇒ result completeWith dispatch)(txn)
|
||||||
result.future
|
result.future
|
||||||
} else dispatch
|
case _ ⇒ dispatch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -143,7 +148,7 @@ class Agent[T](initialValue: T, system: ActorSystem) {
|
||||||
* Dispatch a new value for the internal state. Behaves the same
|
* Dispatch a new value for the internal state. Behaves the same
|
||||||
* as sending a function (x => newValue).
|
* as sending a function (x => newValue).
|
||||||
*/
|
*/
|
||||||
def update(newValue: T) = send(newValue)
|
def update(newValue: T): Unit = send(newValue)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a function to update the internal state but on its own thread.
|
* Dispatch a function to update the internal state but on its own thread.
|
||||||
|
|
@ -151,11 +156,10 @@ class Agent[T](initialValue: T, system: ActorSystem) {
|
||||||
* or blocking operations. Dispatches using either `sendOff` or `send` will
|
* or blocking operations. Dispatches using either `sendOff` or `send` will
|
||||||
* still be executed in order.
|
* still be executed in order.
|
||||||
*/
|
*/
|
||||||
def sendOff(f: T ⇒ T): Unit = {
|
def sendOff(f: T ⇒ T)(implicit ec: ExecutionContext): Unit = {
|
||||||
send((value: T) ⇒ {
|
send((value: T) ⇒ {
|
||||||
suspend()
|
suspend()
|
||||||
val threadBased = system.actorOf(Props(new ThreadBasedAgentUpdater(this, ref)).withDispatcher("akka.agent.send-off-dispatcher"))
|
Future(ref.single.transformAndGet(f)).andThen({ case _ ⇒ resume() })
|
||||||
threadBased ! Update(f)
|
|
||||||
value
|
value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -167,12 +171,11 @@ class Agent[T](initialValue: T, system: ActorSystem) {
|
||||||
* or blocking operations. Dispatches using either `alterOff` or `alter` will
|
* or blocking operations. Dispatches using either `alterOff` or `alter` will
|
||||||
* still be executed in order.
|
* still be executed in order.
|
||||||
*/
|
*/
|
||||||
def alterOff(f: T ⇒ T)(timeout: Timeout): Future[T] = {
|
def alterOff(f: T ⇒ T)(implicit timeout: Timeout, ec: ExecutionContext): Future[T] = {
|
||||||
val result = Promise[T]()
|
val result = Promise[T]()
|
||||||
send((value: T) ⇒ {
|
send((value: T) ⇒ {
|
||||||
suspend()
|
suspend()
|
||||||
val threadBased = system.actorOf(Props(new ThreadBasedAgentUpdater(this, ref)).withDispatcher("akka.agent.alter-off-dispatcher"))
|
result completeWith Future(ref.single.transformAndGet(f)).andThen({ case _ ⇒ resume() })
|
||||||
result completeWith ask(threadBased, Alter(f))(timeout).asInstanceOf[Future[T]]
|
|
||||||
value
|
value
|
||||||
})
|
})
|
||||||
result.future
|
result.future
|
||||||
|
|
@ -182,7 +185,7 @@ class Agent[T](initialValue: T, system: ActorSystem) {
|
||||||
* A future to the current value that will be completed after any currently
|
* A future to the current value that will be completed after any currently
|
||||||
* queued updates.
|
* queued updates.
|
||||||
*/
|
*/
|
||||||
def future(implicit timeout: Timeout): Future[T] = (updater ? Get).asInstanceOf[Future[T]]
|
def future(implicit timeout: Timeout): Future[T] = (updater ? Get).asInstanceOf[Future[T]] //Known to be safe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets this agent's value after all currently queued updates have completed.
|
* Gets this agent's value after all currently queued updates have completed.
|
||||||
|
|
@ -237,7 +240,7 @@ class Agent[T](initialValue: T, system: ActorSystem) {
|
||||||
* Dispatch a function to update the internal state, and return a Future where that new state can be obtained
|
* Dispatch a function to update the internal state, and return a Future where that new state can be obtained
|
||||||
* within the given timeout
|
* within the given timeout
|
||||||
*/
|
*/
|
||||||
def alter(f: JFunc[T, T], timeout: Long): Future[T] = alter(x ⇒ f(x))(timeout)
|
def alter(f: JFunc[T, T], timeout: Duration): Future[T] = alter(x ⇒ f(x))(timeout)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java API:
|
* Java API:
|
||||||
|
|
@ -246,7 +249,7 @@ class Agent[T](initialValue: T, system: ActorSystem) {
|
||||||
* or blocking operations. Dispatches using either `sendOff` or `send` will
|
* or blocking operations. Dispatches using either `sendOff` or `send` will
|
||||||
* still be executed in order.
|
* still be executed in order.
|
||||||
*/
|
*/
|
||||||
def sendOff(f: JFunc[T, T]): Unit = sendOff(x ⇒ f(x))
|
def sendOff(f: JFunc[T, T], ec: ExecutionContext): Unit = sendOff(x ⇒ f(x))(ec)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java API:
|
* Java API:
|
||||||
|
|
@ -256,7 +259,7 @@ class Agent[T](initialValue: T, system: ActorSystem) {
|
||||||
* or blocking operations. Dispatches using either `alterOff` or `alter` will
|
* or blocking operations. Dispatches using either `alterOff` or `alter` will
|
||||||
* still be executed in order.
|
* still be executed in order.
|
||||||
*/
|
*/
|
||||||
def alterOff(f: JFunc[T, T], timeout: Long): Unit = alterOff(x ⇒ f(x))(timeout)
|
def alterOff(f: JFunc[T, T], timeout: Duration, ec: ExecutionContext): Unit = alterOff(x ⇒ f(x))(Timeout(timeout), ec)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java API:
|
* Java API:
|
||||||
|
|
@ -293,29 +296,4 @@ private[akka] class AgentUpdater[T](agent: Agent[T], ref: Ref[T]) extends Actor
|
||||||
}
|
}
|
||||||
|
|
||||||
def update(function: T ⇒ T): T = ref.single.transformAndGet(function)
|
def update(function: T ⇒ T): T = ref.single.transformAndGet(function)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Thread-based agent updater actor. Used internally for `sendOff` actions.
|
|
||||||
*
|
|
||||||
* INTERNAL API
|
|
||||||
*/
|
|
||||||
private[akka] class ThreadBasedAgentUpdater[T](agent: Agent[T], ref: Ref[T]) extends Actor {
|
|
||||||
def receive = {
|
|
||||||
case u: Update[_] ⇒ try {
|
|
||||||
update(u.function.asInstanceOf[T ⇒ T])
|
|
||||||
} finally {
|
|
||||||
agent.resume()
|
|
||||||
context.stop(self)
|
|
||||||
}
|
|
||||||
case a: Alter[_] ⇒ try {
|
|
||||||
sender ! update(a.function.asInstanceOf[T ⇒ T])
|
|
||||||
} finally {
|
|
||||||
agent.resume()
|
|
||||||
context.stop(self)
|
|
||||||
}
|
|
||||||
case _ ⇒ context.stop(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
def update(function: T ⇒ T): T = ref.single.transformAndGet(function)
|
|
||||||
}
|
|
||||||
|
|
@ -39,6 +39,7 @@ class AgentSpec extends AkkaSpec {
|
||||||
"maintain order between send and sendOff" in {
|
"maintain order between send and sendOff" in {
|
||||||
val countDown = new CountDownFunction[String]
|
val countDown = new CountDownFunction[String]
|
||||||
val l1, l2 = new CountDownLatch(1)
|
val l1, l2 = new CountDownLatch(1)
|
||||||
|
import system.dispatcher
|
||||||
|
|
||||||
val agent = Agent("a")
|
val agent = Agent("a")
|
||||||
agent send (_ + "b")
|
agent send (_ + "b")
|
||||||
|
|
@ -58,10 +59,10 @@ class AgentSpec extends AkkaSpec {
|
||||||
val l1, l2 = new CountDownLatch(1)
|
val l1, l2 = new CountDownLatch(1)
|
||||||
val agent = Agent("a")
|
val agent = Agent("a")
|
||||||
|
|
||||||
val r1 = agent.alter(_ + "b")(5000)
|
val r1 = agent.alter(_ + "b")
|
||||||
val r2 = agent.alterOff((s: String) ⇒ { l1.countDown; l2.await(5, TimeUnit.SECONDS); s + "c" })(5000)
|
val r2 = agent.alterOff((s: String) ⇒ { l1.countDown; l2.await(5, TimeUnit.SECONDS); s + "c" })
|
||||||
l1.await(5, TimeUnit.SECONDS)
|
l1.await(5, TimeUnit.SECONDS)
|
||||||
val r3 = agent.alter(_ + "d")(5000)
|
val r3 = agent.alter(_ + "d")
|
||||||
val result = Future.sequence(Seq(r1, r2, r3)).map(_.mkString(":"))
|
val result = Future.sequence(Seq(r1, r2, r3)).map(_.mkString(":"))
|
||||||
l2.countDown
|
l2.countDown
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ package docs.agent;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContext;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
@ -30,10 +31,12 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
public class AgentDocTest {
|
public class AgentDocTest {
|
||||||
|
|
||||||
private static ActorSystem testSystem;
|
private static ActorSystem testSystem;
|
||||||
|
private static ExecutionContext ec;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void beforeAll() {
|
public static void beforeAll() {
|
||||||
testSystem = ActorSystem.create("AgentDocTest", AkkaSpec.testConf());
|
testSystem = ActorSystem.create("AgentDocTest", AkkaSpec.testConf());
|
||||||
|
ec = testSystem.dispatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
|
|
@ -81,7 +84,7 @@ public class AgentDocTest {
|
||||||
|
|
||||||
//#send-off
|
//#send-off
|
||||||
// sendOff a function
|
// sendOff a function
|
||||||
agent.sendOff(longRunningOrBlockingFunction);
|
agent.sendOff(longRunningOrBlockingFunction, ec);
|
||||||
//#send-off
|
//#send-off
|
||||||
|
|
||||||
//#read-await
|
//#read-await
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class AgentDocSpec extends AkkaSpec {
|
||||||
|
|
||||||
"send and sendOff" in {
|
"send and sendOff" in {
|
||||||
val agent = Agent(0)
|
val agent = Agent(0)
|
||||||
|
import system.dispatcher
|
||||||
//#send
|
//#send
|
||||||
// send a value
|
// send a value
|
||||||
agent send 7
|
agent send 7
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue