The idea is to filter the sources, replacing @<var>@ occurrences with the mapping for <var> (which is currently hard-coded). @@ -> @. In order to make this work, I had to move the doc sources one directory down (into akka-docs/rst) so that the filtered result could be in a sibling directory so that relative links (to _sphinx plugins or real code) would continue to work. While I was at it I also changed it so that WARNINGs and ERRORs are not swallowed into the debug dump anymore but printed at [warn] level (minimum). One piece of fallout is that the (online) html build is now run after the normal one, not in parallel.
393 lines
11 KiB
Scala
393 lines
11 KiB
Scala
/**
|
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
|
*/
|
|
package docs.future
|
|
|
|
import language.postfixOps
|
|
|
|
import akka.testkit._
|
|
import akka.actor.{ Actor, Props }
|
|
import akka.actor.Status
|
|
import akka.util.Timeout
|
|
import scala.concurrent.util.duration._
|
|
import java.lang.IllegalStateException
|
|
import scala.concurrent.{ Await, ExecutionContext, Future, Promise }
|
|
import scala.util.{ Failure, Success }
|
|
|
|
object FutureDocSpec {
|
|
|
|
class MyActor extends Actor {
|
|
def receive = {
|
|
case x: String ⇒ sender ! x.toUpperCase
|
|
case x: Int if x < 0 ⇒ sender ! Status.Failure(new ArithmeticException("Negative values not supported"))
|
|
case x: Int ⇒ sender ! x
|
|
}
|
|
}
|
|
|
|
case object GetNext
|
|
|
|
class OddActor extends Actor {
|
|
var n = 1
|
|
def receive = {
|
|
case GetNext ⇒
|
|
sender ! n
|
|
n += 2
|
|
}
|
|
}
|
|
}
|
|
|
|
class FutureDocSpec extends AkkaSpec {
|
|
import FutureDocSpec._
|
|
import system.dispatcher
|
|
"demonstrate usage custom ExecutionContext" in {
|
|
val yourExecutorServiceGoesHere = java.util.concurrent.Executors.newSingleThreadExecutor()
|
|
//#diy-execution-context
|
|
import scala.concurrent.{ ExecutionContext, Promise }
|
|
|
|
implicit val ec = ExecutionContext.fromExecutorService(yourExecutorServiceGoesHere)
|
|
|
|
// Do stuff with your brand new shiny ExecutionContext
|
|
val f = Promise.successful("foo")
|
|
|
|
// Then shut your ExecutionContext down at some
|
|
// appropriate place in your program/application
|
|
ec.shutdown()
|
|
//#diy-execution-context
|
|
}
|
|
|
|
"demonstrate usage of blocking from actor" in {
|
|
val actor = system.actorOf(Props[MyActor])
|
|
val msg = "hello"
|
|
//#ask-blocking
|
|
import scala.concurrent.Await
|
|
import akka.pattern.ask
|
|
import akka.util.Timeout
|
|
import scala.concurrent.util.duration._
|
|
|
|
implicit val timeout = Timeout(5 seconds)
|
|
val future = actor ? msg // enabled by the “ask” import
|
|
val result = Await.result(future, timeout.duration).asInstanceOf[String]
|
|
//#ask-blocking
|
|
result must be("HELLO")
|
|
}
|
|
|
|
"demonstrate usage of mapTo" in {
|
|
val actor = system.actorOf(Props[MyActor])
|
|
val msg = "hello"
|
|
implicit val timeout = Timeout(5 seconds)
|
|
//#map-to
|
|
import scala.concurrent.Future
|
|
import akka.pattern.ask
|
|
|
|
val future: Future[String] = ask(actor, msg).mapTo[String]
|
|
//#map-to
|
|
Await.result(future, timeout.duration) must be("HELLO")
|
|
}
|
|
|
|
"demonstrate usage of simple future eval" in {
|
|
//#future-eval
|
|
import scala.concurrent.Await
|
|
import scala.concurrent.Future
|
|
import scala.concurrent.util.duration._
|
|
|
|
val future = Future {
|
|
"Hello" + "World"
|
|
}
|
|
val result = Await.result(future, 1 second)
|
|
//#future-eval
|
|
result must be("HelloWorld")
|
|
}
|
|
|
|
"demonstrate usage of map" in {
|
|
//#map
|
|
val f1 = Future {
|
|
"Hello" + "World"
|
|
}
|
|
val f2 = f1 map { x ⇒
|
|
x.length
|
|
}
|
|
val result = Await.result(f2, 1 second)
|
|
result must be(10)
|
|
f1.value must be(Some(Success("HelloWorld")))
|
|
//#map
|
|
}
|
|
|
|
"demonstrate wrong usage of nested map" in {
|
|
//#wrong-nested-map
|
|
val f1 = Future {
|
|
"Hello" + "World"
|
|
}
|
|
val f2 = Future.successful(3)
|
|
val f3 = f1 map { x ⇒
|
|
f2 map { y ⇒
|
|
x.length * y
|
|
}
|
|
}
|
|
//#wrong-nested-map
|
|
Await.ready(f3, 1 second)
|
|
}
|
|
|
|
"demonstrate usage of flatMap" in {
|
|
//#flat-map
|
|
val f1 = Future {
|
|
"Hello" + "World"
|
|
}
|
|
val f2 = Future.successful(3)
|
|
val f3 = f1 flatMap { x ⇒
|
|
f2 map { y ⇒
|
|
x.length * y
|
|
}
|
|
}
|
|
val result = Await.result(f3, 1 second)
|
|
result must be(30)
|
|
//#flat-map
|
|
}
|
|
|
|
"demonstrate usage of filter" in {
|
|
//#filter
|
|
val future1 = Future.successful(4)
|
|
val future2 = future1.filter(_ % 2 == 0)
|
|
val result = Await.result(future2, 1 second)
|
|
result must be(4)
|
|
|
|
val failedFilter = future1.filter(_ % 2 == 1).recover {
|
|
case m: NoSuchElementException ⇒ 0 //When filter fails, it will have a java.util.NoSuchElementException
|
|
}
|
|
val result2 = Await.result(failedFilter, 1 second)
|
|
result2 must be(0) //Can only be 0 when there was a MatchError
|
|
//#filter
|
|
}
|
|
|
|
"demonstrate usage of for comprehension" in {
|
|
//#for-comprehension
|
|
val f = for {
|
|
a ← Future(10 / 2) // 10 / 2 = 5
|
|
b ← Future(a + 1) // 5 + 1 = 6
|
|
c ← Future(a - 1) // 5 - 1 = 4
|
|
if c > 3 // Future.filter
|
|
} yield b * c // 6 * 4 = 24
|
|
|
|
// Note that the execution of futures a, b, and c
|
|
// are not done in parallel.
|
|
|
|
val result = Await.result(f, 1 second)
|
|
result must be(24)
|
|
//#for-comprehension
|
|
}
|
|
|
|
"demonstrate wrong way of composing" in {
|
|
val actor1 = system.actorOf(Props[MyActor])
|
|
val actor2 = system.actorOf(Props[MyActor])
|
|
val actor3 = system.actorOf(Props[MyActor])
|
|
val msg1 = 1
|
|
val msg2 = 2
|
|
implicit val timeout = Timeout(5 seconds)
|
|
import scala.concurrent.Await
|
|
import akka.pattern.ask
|
|
//#composing-wrong
|
|
|
|
val f1 = ask(actor1, msg1)
|
|
val f2 = ask(actor2, msg2)
|
|
|
|
val a = Await.result(f1, 1 second).asInstanceOf[Int]
|
|
val b = Await.result(f2, 1 second).asInstanceOf[Int]
|
|
|
|
val f3 = ask(actor3, (a + b))
|
|
|
|
val result = Await.result(f3, 1 second).asInstanceOf[Int]
|
|
//#composing-wrong
|
|
result must be(3)
|
|
}
|
|
|
|
"demonstrate composing" in {
|
|
val actor1 = system.actorOf(Props[MyActor])
|
|
val actor2 = system.actorOf(Props[MyActor])
|
|
val actor3 = system.actorOf(Props[MyActor])
|
|
val msg1 = 1
|
|
val msg2 = 2
|
|
implicit val timeout = Timeout(5 seconds)
|
|
import scala.concurrent.Await
|
|
import akka.pattern.ask
|
|
//#composing
|
|
|
|
val f1 = ask(actor1, msg1)
|
|
val f2 = ask(actor2, msg2)
|
|
|
|
val f3 = for {
|
|
a ← f1.mapTo[Int]
|
|
b ← f2.mapTo[Int]
|
|
c ← ask(actor3, (a + b)).mapTo[Int]
|
|
} yield c
|
|
|
|
val result = Await.result(f3, 1 second).asInstanceOf[Int]
|
|
//#composing
|
|
result must be(3)
|
|
}
|
|
|
|
"demonstrate usage of sequence with actors" in {
|
|
implicit val timeout = Timeout(5 seconds)
|
|
val oddActor = system.actorOf(Props[OddActor])
|
|
//#sequence-ask
|
|
// oddActor returns odd numbers sequentially from 1 as a List[Future[Int]]
|
|
val listOfFutures = List.fill(100)(akka.pattern.ask(oddActor, GetNext).mapTo[Int])
|
|
|
|
// now we have a Future[List[Int]]
|
|
val futureList = Future.sequence(listOfFutures)
|
|
|
|
// Find the sum of the odd numbers
|
|
val oddSum = Await.result(futureList.map(_.sum), 1 second).asInstanceOf[Int]
|
|
oddSum must be(10000)
|
|
//#sequence-ask
|
|
}
|
|
|
|
"demonstrate usage of sequence" in {
|
|
//#sequence
|
|
val futureList = Future.sequence((1 to 100).toList.map(x ⇒ Future(x * 2 - 1)))
|
|
val oddSum = Await.result(futureList.map(_.sum), 1 second).asInstanceOf[Int]
|
|
oddSum must be(10000)
|
|
//#sequence
|
|
}
|
|
|
|
"demonstrate usage of traverse" in {
|
|
//#traverse
|
|
val futureList = Future.traverse((1 to 100).toList)(x ⇒ Future(x * 2 - 1))
|
|
val oddSum = Await.result(futureList.map(_.sum), 1 second).asInstanceOf[Int]
|
|
oddSum must be(10000)
|
|
//#traverse
|
|
}
|
|
|
|
"demonstrate usage of fold" in {
|
|
//#fold
|
|
val futures = for (i ← 1 to 1000) yield Future(i * 2) // Create a sequence of Futures
|
|
val futureSum = Future.fold(futures)(0)(_ + _)
|
|
Await.result(futureSum, 1 second) must be(1001000)
|
|
//#fold
|
|
}
|
|
|
|
"demonstrate usage of reduce" in {
|
|
//#reduce
|
|
val futures = for (i ← 1 to 1000) yield Future(i * 2) // Create a sequence of Futures
|
|
val futureSum = Future.reduce(futures)(_ + _)
|
|
Await.result(futureSum, 1 second) must be(1001000)
|
|
//#reduce
|
|
}
|
|
|
|
"demonstrate usage of recover" in {
|
|
implicit val timeout = Timeout(5 seconds)
|
|
val actor = system.actorOf(Props[MyActor])
|
|
val msg1 = -1
|
|
//#recover
|
|
val future = akka.pattern.ask(actor, msg1) recover {
|
|
case e: ArithmeticException ⇒ 0
|
|
}
|
|
//#recover
|
|
Await.result(future, 1 second) must be(0)
|
|
}
|
|
|
|
"demonstrate usage of recoverWith" in {
|
|
implicit val timeout = Timeout(5 seconds)
|
|
val actor = system.actorOf(Props[MyActor])
|
|
val msg1 = -1
|
|
//#try-recover
|
|
val future = akka.pattern.ask(actor, msg1) recoverWith {
|
|
case e: ArithmeticException ⇒ Future.successful(0)
|
|
case foo: IllegalArgumentException ⇒ Future.failed[Int](new IllegalStateException("All br0ken!"))
|
|
}
|
|
//#try-recover
|
|
Await.result(future, 1 second) must be(0)
|
|
}
|
|
|
|
"demonstrate usage of zip" in {
|
|
val future1 = Future { "foo" }
|
|
val future2 = Future { "bar" }
|
|
//#zip
|
|
val future3 = future1 zip future2 map { case (a, b) ⇒ a + " " + b }
|
|
//#zip
|
|
Await.result(future3, 1 second) must be("foo bar")
|
|
}
|
|
|
|
"demonstrate usage of andThen" in {
|
|
def loadPage(s: String) = s
|
|
val url = "foo bar"
|
|
def log(cause: Throwable) = ()
|
|
def watchSomeTV = ()
|
|
//#and-then
|
|
val result = Future { loadPage(url) } andThen {
|
|
case Failure(exception) ⇒ log(exception)
|
|
} andThen {
|
|
case _ ⇒ watchSomeTV
|
|
}
|
|
//#and-then
|
|
Await.result(result, 1 second) must be("foo bar")
|
|
}
|
|
|
|
"demonstrate usage of fallbackTo" in {
|
|
val future1 = Future { "foo" }
|
|
val future2 = Future { "bar" }
|
|
val future3 = Future { "pigdog" }
|
|
//#fallback-to
|
|
val future4 = future1 fallbackTo future2 fallbackTo future3
|
|
//#fallback-to
|
|
Await.result(future4, 1 second) must be("foo")
|
|
}
|
|
|
|
"demonstrate usage of onSuccess & onFailure & onComplete" in {
|
|
{
|
|
val future = Future { "foo" }
|
|
//#onSuccess
|
|
future onSuccess {
|
|
case "bar" ⇒ println("Got my bar alright!")
|
|
case x: String ⇒ println("Got some random string: " + x)
|
|
}
|
|
//#onSuccess
|
|
Await.result(future, 1 second) must be("foo")
|
|
}
|
|
{
|
|
val future = Future.failed[String](new IllegalStateException("OHNOES"))
|
|
//#onFailure
|
|
future onFailure {
|
|
case ise: IllegalStateException if ise.getMessage == "OHNOES" ⇒
|
|
//OHNOES! We are in deep trouble, do something!
|
|
case e: Exception ⇒
|
|
//Do something else
|
|
}
|
|
//#onFailure
|
|
}
|
|
{
|
|
val future = Future { "foo" }
|
|
def doSomethingOnSuccess(r: String) = ()
|
|
def doSomethingOnFailure(t: Throwable) = ()
|
|
//#onComplete
|
|
future onComplete {
|
|
case Success(result) ⇒ doSomethingOnSuccess(result)
|
|
case Failure(failure) ⇒ doSomethingOnFailure(failure)
|
|
}
|
|
//#onComplete
|
|
Await.result(future, 1 second) must be("foo")
|
|
}
|
|
}
|
|
|
|
"demonstrate usage of Future.successful & Future.failed" in {
|
|
//#successful
|
|
val future = Future.successful("Yay!")
|
|
//#successful
|
|
//#failed
|
|
val otherFuture = Future.failed[String](new IllegalArgumentException("Bang!"))
|
|
//#failed
|
|
Await.result(future, 1 second) must be("Yay!")
|
|
intercept[IllegalArgumentException] { Await.result(otherFuture, 1 second) }
|
|
}
|
|
|
|
"demonstrate usage of pattern.after" in {
|
|
//#after
|
|
import akka.pattern.after
|
|
|
|
val delayed = after(200 millis, using = system.scheduler)(Future.failed(
|
|
new IllegalStateException("OHNOES")))
|
|
val future = Future { Thread.sleep(1000); "foo" }
|
|
val result = future either delayed
|
|
//#after
|
|
intercept[IllegalStateException] { Await.result(result, 2 second) }
|
|
}
|
|
|
|
}
|