A suggestion how things could be done otherwise.

This commit is contained in:
Viktor Klang 2012-07-25 21:24:15 +02:00
parent 58dcfb1004
commit cc3f576f0a
4 changed files with 39 additions and 57 deletions

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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