ASYNC FUN
and some Monix bits
About Me:
Software Engineer @
almendar
Scala since ~2011
Tomasz Kogut Warsaw
scala-poland
Asynchrony and other parasites
Scala Future - primer and beyond
Monix - your helping hand
3
Agenda
4
Concurrency
Parallelism
Multithreading
Single
threading
Asynchrony
5
6
TIME
7
TIME
Main
8
TIME
Main
9
TIME
Main
Asynchrony
ā— Outside of main computation
flow
ā— Delivers result with a callback
ā— May not deliver result back
10
TIME
Main
Multithreading
Single
threading
11
TIME
Main
Concurrency
12
TIME
1
2
Main
Concurrency
13
TIME
2
1
Main
Concurrency
14
TIME
2
1
Main
Concurrency
15
TIME
Main
Parallelism
16
TIME
Parallelism
0 0 0 0 0 1 1 1 1 1
17
Asynchrony
We want to:
1. express computation that will run outside the main
flow,
2. handle errors if they occur during processing,
3. tell our computation how to return the result to us
4. use type-safe constructs
We don’t want to:
1. constraint on how the execution is carried out on the
processor
2. explicitly block our main flow, it should make
progress if it can
18
def compute[A](): Async[A] = ???
19
type Async[A] = (Try[A] => Unit) => Unit
20
type Callback[A] = (Try[A] => Unit)
type Async[A] = Callback[A] => Unit
21
type Async[A] = (Try[A] => Unit) => Unit
def divisionService(arg: Int): Async[Int] = cb =>
cb(Try(22 / arg))
22
type Async[A] = (Try[A] => Unit) => Unit
def divisionService(arg: Int): Async[Int] = cb =>
cb(Try(22 / arg))
divisionService(44)(println)
23
type Async[A] = (Try[A] => Unit) => Unit
def divisionService(arg: Int): Async[Int] = cb =>
cb(Try(22 / arg))
divisionService(44)(println)
def giveOxygenToAstronauts(arg: Int): Unit = ???
24
type Async[A] = (Try[A] => Unit) => Unit
def divisionService(arg: Int): Async[Int] = cb =>
cb(Try(22 / arg))
divisionService(44)(println)
def giveOxygenToAstronauts(arg: Int): Unit = ???
divisionService(33){
case Success(x) => giveOxygenToAstronauts(x)
case Failure(t) => throw new HumanLifeEndangeredException(t)
}
25
def divisionService(arg: Int): Async[Int] = cb => cb(Try(22 / arg))
Int => Callback[Int] => Unit
Any => Unit //actor’s signature
arg
22 / arg
DivisionService
26
def divisionService(arg: Int): Async[Int] = cb => cb(Try(22 / arg))
Int => Callback[Int] => Unit
Any => Unit //actor’s signature
(Any,ActorRef) => Unit //actor’s true signature
arg
22 / arg
DivisionService
27
Asynchrony escape hatch illusion
A.K.A.
make semicolon (;) great again
def await[A](fa: Async[A]): A
28
Problems with semicolon driven programming:
1. The lesson of Distributed programming, it’s not always
our program we call
2. You kill parallelism (Amdahl Law)
3. Your platform has less threads than you think it does
29
Amdahl Law
ā— 20 hours of computing
ā— 1 hour cannot be parallelized
ā— 19 hours can be parallelized
ā— Max speedup is 20x no matter how much cores you have
ā— With await you lose all of this.
30
Execution platforms
ā— 1:1 kernel level thread like in JVM where blocking is
problematic
ā— M:N green threading model (Haskell, Golang, Erlang)
where you can ā€œblockā€
ā— M:1 where you have only one thread to execute
31
Execution platforms
ā— 1:1 kernel level thread like in JVM where blocking is
problematic
ā— M:N green threading model (Haskell, Golang, Erlang)
where you can ā€œblockā€
ā— M:1 where you have only one thread to execute
JavaScript
32
Async pollution
def tweets(text: String): Int = ???
def fb(text: String): Int = ???
def totalMentions(text: String): Int = fb(text) + tweets(text)
33
Async pollution
def tweets(text: String): Async[Int] = ???
def fb(text: String): Async[Int] = ???
def totalMentions(text: String): Async[Int] = ???
34
Async pollution
def totalMentions(text: String): Async[Int] = { cb =>
tweets(text){tNrTry =>
fb(text){fNrTry =>
(tNrTry, fNrTry) match {
case (Success(tNr), Success(fNr)) => cb(Success(fNr + tNr))
case _ => cb(Failure(new Exception("Failed")))
}
}
}
}
35
Async pollution
def tweets(text: String): Async[Int] = ???
def fb(text: String): Async[Int] = ???
def instagram(text: String): Async[Int] = ???
def tumblr(text: String): Async[Int] = ???
def reddit(text: String): Async[Int] = ???
(...)
36
Async
☹
37
Async + parallel
😱
38
type Async[A] = (A => Unit) => Unit
def map2[A,B,C](x: Async[A], y: Async[B])(f: (A,B) => C): Async[C] = {cb =>
var a:A = _
var b:B = _
x apply {a:A => //(...) much fun }
y apply {b:B => //(...) much fun }
}
1.We’re on a 1:1 platform so we need to synchronize access
2.cb must be called only once in one of the branches, we need a state machine
e.g. WaitingForX, WaitingForY states
3.Locks can decrease liveness property of our code, because of blocking
4.There is AtomicReference which helps but still has cost
5.In result we might be processing longer than in the sequential version if
we’re CPU bound (sic!)
39
Stackoverflow problem
def map2[A,B,C](x: Async[A], y: Async[B])
(f: (A,B) => C): Async[C]
def sequence[A](jobs: List[Async[A]]): Async[List[A]]
40
def async1: Async[Int] = cb => cb(Success(1))
async1 { x1 =>
async1 { x2 =>
async1 { x3 =>
println(
Thread
.currentThread()
.getStackTrace()
.length
)
}
}
}
//46
async1 { x1 =>
async1 { x2 =>
async1 { x3 =>
async1 { x4 =>
println(
Thread
.currentThread()
.getStackTrace()
.length
)
}
}
}
}
//51
41
Crossing the async boundary
import scala.concurrent.ExecutionContext.Implicits.global
def async1Exc: Async[Int] = cb => global.execute(() => cb(Success(1)))
async1Exc { x1 =>
async1Exc { x2 =>
async1Exc { x3 =>
async1Exc { x4 =>
println(
Thread
.currentThread()
.getStackTrace()
.length
)
}
}
}
}
//12
async1Exc { x1 =>
async1Exc { x2 =>
async1Exc { x3 =>
async1Exc { x4 =>
async1Exc { x5 =>
println(
Thread
.currentThread()
.getStackTrace()
.length
)
}
}
}
}
}
//12
42
Scala Future recap
ā— onComplete is basically our Async with ExecutionContext
ā— It models asynchronous computations (might be parallel)
ā— Parallelism depends on policy of passed ExecutionContext
ā— Future can run if you have only one thread (JS-approved)
ā— It’s stack stafe
ā— Every combinator like map, flatMap ect. needs an ExecutionContext
ā— The fact that every transformation is scheduled has implications:
ā—‹ degree of non-determinism is hard to control
ā—‹ performance with CPU bound tasks can suffer
43
def getClientDataFromS3(id: Long): Future[CSV] //CSV ~2Gb
//composition of multiple A => Future[B] functions
def aggregate(input: CSV): Future[Int]
val userList: List[Long] = (...) //~100 clients
val storeJobs: List[Future[Int]] = userList.map { user =>
getClientDataFromS3(user).flatMap(csvInput => aggregate(csvInput))
}
val batchedJob: Future[List[Int]] = Future.sequence(storeJobs)
Await.ready(batchedJob, 8.hours)
Nondeterminism
What is wrong with this code?
44
Jumping threads
def pureDataTransformation1(input:CSV): Seq[Seq[Int]] = ???
def pureDataTransformation2(input: Seq[Seq[Int]]): Seq[Int] = ???
def pureDataTransformation3(input: Seq[Int]): Int = ???
Future(pureDataTransformation1(csv))
.map(pureDataTransformation2)
.map(pureDataTransformation3)
Future {
val pdr1 = pureDataTransformation1(csv)
val pdr2 = pureDataTransformation2(pdr1)
pureDataTransformation3(pdr2)
}
45
private[concurrent] object InternalCallbackExecutor extends ExecutionContext with
BatchingExecutor {
override protected def unbatchedExecute(r: Runnable): Unit =
r.run()
override def reportFailure(t: Throwable): Unit =
throw new IllegalStateException("problem in scala.concurrent internal callback", t)
}
def sequence[A, M[X] <: TraversableOnce[X]]
(in: M[Future[A]])(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]],
executor: ExecutionContext): Future[M[A]] = {
in.foldLeft(successful(cbf(in))) {
(fr, fa) => fr.zipWith(fa)(_ += _)
}.map(_.result())(InternalCallbackExecutor)
}
Future local optimizations
46
/**
* Provides alternative implementations of the basic transformation operations
defined on [[scala.concurrent.Future]],
* which try to avoid scheduling to an [[scala.concurrent.ExecutionContext]] if
possible, i.e. if the given future
* value is already present.
*/
class FastFuture[A](val future: Future[A]) extends AnyVal {
(...)
}
Akka knows this too
47
48
49
ā— Try to avoid creating pure computation
functions that return Future
ā— Don’t pollute your interfaces with function
that return Future just because root of
your computation starts with a Future (e.g.
database call)
ā— Avoid async if possible
ā— Use other tools if you can (i.e. Task)
Rules to follow
50
Getting high performance
with parallelism
ā— We want as much parallelism as possible
ā— We have only so many cores
ā— High number of threads will result in
often context switches
How to scale number of threads?
51
Looking at the global EC
ā— The global EC is backed by a ForkJoinPool
ā— Thread construction is controlled by a
specialized ThreadFactory
ā— In general
Runtime.getRuntime.availableProcessors is
the number of threads that will be
created
ā— Extra threads creation possible up to 256
52
Parallelism and blocking
An algorithm is called nonblocking if failure or
suspension of any thread cannot cause failure or
suspension of another thread
Java concurrency in practice
53
What is blocking?
1. Waiting on lock?
2. Doing Thread.sleep?
3. Doing pure super-long-expensive computation
4. Calling blocking IO APIs like JDBC?
5. Doing CAS operation in High Contention environment?
54
Use case of
scala.concurrent.blocking
Future {
blocking {
Thread.sleep(999999)
}
}
55
def blocking[T](body: =>T): T =
BlockContext.current.blockOn(body)(scala.concurrent.AwaitPermission)
//inside global EC thread factory
def newThread(fjp: ForkJoinPool): ForkJoinWorkerThread = {
(...)this thread will try to compensate blocking
new ForkJoinWorkerThread(fjp) with BlockContext
(...)
}
//during execution
Thread.currentThread match {
case ctx: BlockContext => ctx
case _ => DefaultBlockContext
}
private object DefaultBlockContext extends BlockContext {
override def blockOn[T](thunk: =>T)(implicit permission: CanAwait): T = thunk
}
56
Monix
57
ā— Monix is a library for combining asynchronous and event-based programs
targeting both JVM and JS
ā— Developed in open source by Alexandru Nedelcu
ā— Very similar to ReactiveX project with nice Scala API
ā— Typelevel project, has integration with cats.
ā— It seems that other typelevel project might have option to plug-in
Monix as backend (e.g. Doobie, Http4s)
ā— Slowly it’s getting traction and ecosystem starts to emerge like e.g.
monix-kafka project
ā— It’s easy to use it with Future-based project, so you don’t need to do
a revolution in your code
58
Task
59
Task
import monix.eval.Task
//I’m wrapping global EC
import monix.execution.Scheduler.Implicits.global
def divisionService2(arg: Int) = Task(22 / arg)
val divisionTask = divisionService(50) //I'm lazy
divisionTask.runOnComplete {
case Success(x) => println(x)
case Failure(t) => t.printStackTrace()
}
60
Task
import monix.eval.Task
//I’m wrapping global EC
import monix.execution.Scheduler.Implicits.global
def divisionService2(arg: Int) = Task(22 / arg)
val divisionTask = divisionService(50) //I'm lazy
divisionTask.runOnComplete {
case Success(x) => println(x)
case Failure(t) => t.printStackTrace()
}(implicit s: Scheduler)
61
Task
import monix.eval.Task
//I’m wrapping global EC
import monix.execution.Scheduler.Implicits.global
def divisionService2(arg: Int) = Task(22 / arg)
val divisionTask = divisionService(50) //I'm lazy
divisionTask.runOnComplete {
case Success(x) => println(x)
case Failure(t) => t.printStackTrace()
}
val divisionTaskMultipliedBy2 = divisionTask.map( _ * 2)
val runningTask: CancelableFuture[Int] = divisionTaskMultipliedBy2.runAsync
runningTask.map(_ * 4).onComplete(println)
62
scala> :paste
val p = for {
i <- Task(1)
j <- Task(2)
_ <- Task(println("Hello world"))
} yield i+j
// Exiting paste mode, now interpreting.
p: monix.eval.Task[Int] = Task.FlatMap(Task@1633066228, $$Lambda$1615/1219583471@23cafb14)
p.runAsync
res0: monix.execution.CancelableFuture[Int] =
monix.execution.CancelableFuture$Implementation@f96168d
Hello world
scala> res0.value
res1: Option[scala.util.Try[Int]] = Some(Success(3))
63
scala> val now = Task.now { println("Hello"); 22 }
Hello
now: monix.eval.Task[Int] = Task.Now(22)
-------------------------------------------------------------------------------------------------
scala> val eval = Task.eval{ println("Hello"); 22}
eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80)
scala> eval.runSyncMaybe
Hello
res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22)
-------------------------------------------------------------------------------------------------
scala> val once = Task.evalOnce { println("Hello"); 22 }
once: monix.eval.Task[Int] = Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d))
scala> once.foreach(println)
Hello
22
scala> once.foreach(println)
22
res108: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Now@310d507
-------------------------------------------------------------------------------------------------
scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } }
task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
64
scala> val now = Task.now { println("Hello"); 22 }
Hello
now: monix.eval.Task[Int] = Task.Now(22)
-------------------------------------------------------------------------------------------------
scala> val eval = Task.eval{ println("Hello"); 22}
eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80)
scala> eval.runSyncMaybe
Hello
res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22)
-------------------------------------------------------------------------------------------------
scala> val once = Task.evalOnce { println("Hello"); 22 }
once: monix.eval.Task[Int] = Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d))
scala> once.foreach(println)
Hello
22
scala> once.foreach(println)
22
res108: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Now@310d507
-------------------------------------------------------------------------------------------------
scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } }
task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
65
scala> val now = Task.now { println("Hello"); 22 }
Hello
now: monix.eval.Task[Int] = Task.Now(22)
-------------------------------------------------------------------------------------------------
scala> val eval = Task.eval{ println("Hello"); 22}
eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80)
scala> eval.runSyncMaybe
Hello
res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22)
-------------------------------------------------------------------------------------------------
scala> val once = Task.evalOnce { println("Hello"); 22 }
once: monix.eval.Task[Int] = Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d))
scala> once.foreach(println)
Hello
22
scala> once.foreach(println)
22
res108: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Now@310d507
-------------------------------------------------------------------------------------------------
scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } }
task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
66
scala> val now = Task.now { println("Hello"); 22 }
Hello
now: monix.eval.Task[Int] = Task.Now(22)
-------------------------------------------------------------------------------------------------
scala> val eval = Task.eval{ println("Hello"); 22}
eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80)
scala> eval.runSyncMaybe
Hello
res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22)
-------------------------------------------------------------------------------------------------
scala> val once = Task.evalOnce { println("Hello"); 22 }
once: monix.eval.Task[Int] =
Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d))
scala> once.foreach(println)
Hello
22
scala> once.foreach(println)
22
res108: monix.execution.CancelableFuture[Unit] =
monix.execution.CancelableFuture$Now@310d507
-------------------------------------------------------------------------------------------------
scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } }
task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
67
scala> val now = Task.now { println("Hello"); 22 }
Hello
now: monix.eval.Task[Int] = Task.Now(22)
-------------------------------------------------------------------------------------------------
scala> val eval = Task.eval{ println("Hello"); 22}
eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80)
scala> eval.runSyncMaybe
Hello
res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22)
-------------------------------------------------------------------------------------------------
scala> val once = Task.evalOnce { println("Hello"); 22 }
once: monix.eval.Task[Int] = Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d))
scala> once.foreach(println)
Hello
22
scala> once.foreach(println)
22
res108: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Now@310d507
-------------------------------------------------------------------------------------------------
scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } }
task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
68
scala> val forked = Task.fork{Task.now(1)}
forked: monix.eval.Task[Int] = Task.FlatMap(Task@1822739293,
monix.eval.Task$$$Lambda$1614/131301275@3427f53a)
scala> forked.runSyncMaybe
res114: Either[monix.execution.CancelableFuture[Int],Int] =
Left(monix.execution.CancelableFuture$Implementation@9b393c7)
-------------------------------------------------------------------------------------------------
lazy val io = Scheduler.io(name="scalawaw-io")
val source = Task(println(s"Running on thread: ${Thread.currentThread.getName}"))
val forked = Task.fork(source, io)
forked.foreach(println)
Running on thread: scalawaw-io-233
-------------------------------------------------------------------------------------------------
scala> val danger = Task.delay {
if(Random.nextInt % 2 == 0) throw new Exception("Boom")
else 2
}.memoizeOnSuccess
danger: monix.eval.Task[Int] = Task.Eval(<function0>)
scala> danger.runOnComplete{x => println(x)}
Failure(java.lang.Exception: Boom)
scala> danger.runOnComplete{x => println(x)}
Failure(java.lang.Exception: Boom)
scala> danger.runOnComplete{x => println(x)}
Success(2)
scala> List.fill(1000)(danger.runSyncMaybe).forall(_.isRight)
res48: Boolean = true
69
scala> val forked = Task.fork{Task.now(1)}
forked: monix.eval.Task[Int] = Task.FlatMap(Task@1822739293,
monix.eval.Task$$$Lambda$1614/131301275@3427f53a)
scala> forked.runSyncMaybe
res114: Either[monix.execution.CancelableFuture[Int],Int] =
Left(monix.execution.CancelableFuture$Implementation@9b393c7)
-------------------------------------------------------------------------------------------------
lazy val io = Scheduler.io(name="scalawaw-io")
val source = Task(println(s"Running on thread: ${Thread.currentThread.getName}"))
val forked = Task.fork(source, io)
forked.foreach(println)
Running on thread: scalawaw-io-233
-------------------------------------------------------------------------------------------------
scala> val danger = Task.delay {
if(Random.nextInt % 2 == 0) throw new Exception("Boom")
else 2
}.memoizeOnSuccess
danger: monix.eval.Task[Int] = Task.Eval(<function0>)
scala> danger.runOnComplete{x => println(x)}
Failure(java.lang.Exception: Boom)
scala> danger.runOnComplete{x => println(x)}
Failure(java.lang.Exception: Boom)
scala> danger.runOnComplete{x => println(x)}
Success(2)
scala> List.fill(1000)(danger.runSyncMaybe).forall(_.isRight)
res48: Boolean = true
70
scala> val forked = Task.fork{Task.now(1)}
forked: monix.eval.Task[Int] = Task.FlatMap(Task@1822739293,
monix.eval.Task$$$Lambda$1614/131301275@3427f53a)
scala> forked.runSyncMaybe
res114: Either[monix.execution.CancelableFuture[Int],Int] =
Left(monix.execution.CancelableFuture$Implementation@9b393c7)
-------------------------------------------------------------------------------------------------
lazy val io = Scheduler.io(name="scalawaw-io")
val source = Task(println(s"Running on thread: ${Thread.currentThread.getName}"))
val forked = Task.fork(source, io)
forked.foreach(println)
Running on thread: scalawaw-io-233
-------------------------------------------------------------------------------------------------
scala> val danger = Task.delay {
if(Random.nextInt % 2 == 0) throw new Exception("Boom")
else 2
}.memoizeOnSuccess
danger: monix.eval.Task[Int] = Task.Eval(<function0>)
scala> danger.runOnComplete{x => println(x)}
Failure(java.lang.Exception: Boom)
scala> danger.runOnComplete{x => println(x)}
Failure(java.lang.Exception: Boom)
scala> danger.runOnComplete{x => println(x)}
Success(2)
scala> List.fill(1000)(danger.runSyncMaybe).forall(_.isRight)
res48: Boolean = true
71
scala> val forked = Task.fork{Task.now(1)}
forked: monix.eval.Task[Int] = Task.FlatMap(Task@1822739293, monix.eval.Task$$$Lambda$1614/131301275@3427f53a)
scala> forked.runSyncMaybe
res114: Either[monix.execution.CancelableFuture[Int],Int] =
Left(monix.execution.CancelableFuture$Implementation@9b393c7)
-------------------------------------------------------------------------------------------------
lazy val io = Scheduler.io(name="scalawaw-io")
val source = Task(println(s"Running on thread: ${Thread.currentThread.getName}"))
val forked = Task.fork(source, io)
forked.foreach(println)
Running on thread: scalawaw-io-233
-------------------------------------------------------------------------------------------------
scala> val danger = Task.delay {
if(Random.nextInt % 2 == 0) throw new Exception("Boom")
else 2
}.memoizeOnSuccess
danger: monix.eval.Task[Int] = Task.Eval(<function0>)
scala> danger.runOnComplete{x => println(x)}
Failure(java.lang.Exception: Boom)
scala> danger.runOnComplete{x => println(x)}
Failure(java.lang.Exception: Boom)
scala> danger.runOnComplete{x => println(x)}
Success(2)
scala> List.fill(1000)(danger.runSyncMaybe).forall(_.isRight)
res48: Boolean = true
72
def runForrest()(implicit ec: ExecutionContext): Future[Unit] =
Future(println("Run Forrest"))
def runForrestL(): Task[Unit] =
Task.deferFutureAction { implicit scheduler => runForrest()}
73
def runForrest()(implicit ec: ExecutionContext): Future[Unit] =
Future(println("Run Forrest"))
def runForrestL(): Task[Unit] =
Task.deferFutureAction { implicit scheduler => runForrest()}
trait Database {
def getClient(id:Long)(implicit ec: ExecutionContext): Future[Customer]
}
74
def runForrest()(implicit ec: ExecutionContext): Future[Unit] =
Future(println("Run Forrest"))
def runForrestL(): Task[Unit] =
Task.deferFutureAction { implicit scheduler => runForrest()}
trait Database {
def getClient(id:Long)(implicit ec: ExecutionContext): Future[Customer]
}
trait DatabaseL {
protected val db:Database
def getClient(id:Long): Task[Customer] = Task.deferFutureAction{
implicit scheduler => db.getClient(id)
}
}
Profit! No more implicit ExecutionContext passed around
75
def task(taskName: String): Task[String] = Task {
println(s"$taskName at ${LocalDateTime.now}"); taskName
}
val batch = for {
t1 <- task("t1").delayExecution(10.seconds)
t2 <- task("t2").delayExecution(5.seconds)
t3 <- task("t3")
} yield s"$t1 $t2 $t3"
println(LocalDateTime.now)
batch.foreach(println)
// Exiting paste mode, now interpreting.
2017-06-27T14:07:17.116
task: (taskName: String)monix.eval.Task[String]
batch: monix.eval.Task[String] = Task.FlatMap(Task@705632156, $$Lambda$3756/1733192919@f25496d)
res7: monix.execution.CancelableFuture[Unit] =
monix.execution.CancelableFuture$Implementation@1b19a48a
t1 at 2017-06-27T14:07:27.120
t2 at 2017-06-27T14:07:32.122
t3 at 2017-06-27T14:07:32.124
t1 t2 t3
76
def task(taskName: String): Task[String] = Task {
println(s"$taskName at ${LocalDateTime.now}"); taskName
}
val batch = Task.zipMap3 (
task("t1").delayExecution(10.seconds),
task("t2").delayExecution(5.seconds),
task("t3"))
{(t1,t2,t3) => s"$t1 $t2 $t3"}
println(LocalDateTime.now)
batch.foreach(println)
// Exiting paste mode, now interpreting.
2017-06-27T14:12:27.466
t3 at 2017-06-27T14:12:27.479
batch: monix.eval.Task[String] =
Task.Async(monix.eval.internal.TaskMapBoth$$$Lambda$3762/845438120@d41c7a7)
res9: monix.execution.CancelableFuture[Unit] =
monix.execution.CancelableFuture$Implementation@2b26ab67
t2 at 2017-06-27T14:12:32.484
t1 at 2017-06-27T14:12:37.484
t1 t2 t3
77
Observable &
friends
78
Single Multiple
Synchronous A Iterable[A]
Asynchronous Future[A] / Task[A] Observable[A]
Observable is like Task but with multiple
values available at some point in time
79
//Like normal scala collection
val obs = Observable(1,2,3)
val plusOneObs = obs.map(_ + 1)// lazy 😁
plusOneObs.foreach(println)// prints 2,3,4
80
//Infinite asynchronous
val infAsyncObs: Observable[Int] =
Observable.repeatEval(Random.nextInt).flatMap(x =>
Observable.fromFuture(Future(x * x * x))
).filter(_ % 2 == 0)
val task: Task[List[Int]] = infAsyncObs.take(10).toListL
task.runOnComplete(x => println(x))
Success(List(-1617719296, -570202176, -970884696, 1265664000, -812102280,
464640576, -1594939576, 1491391144, 2144446976, 742992448))
81
//Time based
val await = Observable.zip2(
Observable.intervalAtFixedRate(3.seconds),
Observable.repeatEval(LocalDateTime.now())
)
.map { case (_, t) => t }.take(3).foreach(println)
Await.ready(await, 1.minute)
2017-06-27T15:33:20.855
2017-06-27T15:33:23.826
2017-06-27T15:33:26.826
82
//Parallel processing
val maxConcurrentThreads = new AtomicInteger(0)
val allValuesOfmaxConcurrentThreads =
new AtomicReference[List[Int]](Nil)
val allJobs = Observable
.repeatEval(Thread.currentThread().getId)
.take(1000)
.mapAsync(50)(threadId =>
Task { blocking {
val see = maxConcurrentThreads.incrementAndGet()
allValuesOfmaxConcurrentThreads
.updateAndGet(t => see :: t)
maxConcurrentThreads.decrementAndGet()
threadId } })
.toListL
.runAsync
val threadIdsList: Seq[Long] =
Await.result(allJobs, 5.minutes)
println(s"Highest observed no. of threads
${allValuesOfmaxConcurrentThreads.get().max}")
println(s"Threads used
${threadIdsList.distinct.length}")
//Without blocking
Highest observed nr of threads 3
Threads used 5
//With Blocking
Highest observed nr of threads 4
Threads used 49
83
def getClientDataFromS3(id: Long): Future[CSV] = Future {
blocking {
println(s"Getting data for $id")
Thread.sleep(500)
CSV(id.toString)
}
}
def aggregate(input: CSV): Future[Int] = Future {
println(s"Aggregating data for ${input.a}")
1
}
val userList: List[Long] = (1L to 7L).toList
val storeJobs: List[Future[Int]] = userList.map { user =>
getClientDataFromS3(user).flatMap(csvInput => aggregate(csvInput))
}
val batchedJob: Future[List[Int]] = Future.sequence(storeJobs)
Await.ready(batchedJob, 8.hours)
Let’s revisit our broken code
84
def getClientDataFromS3(id: Long): Task[CSV] = Task {
blocking {
println(s"Getting data for $id")
Thread.sleep(500)
CSV(id.toString)
}
}
def aggregate(input: CSV): Task[Int] = Task {
println(s"Aggregating data for ${input.a}")
1
}
val userList: List[Long] = (1L to 7L).toList
val storeJobs: List[Task[Int]] = userList.map { user =>
getClientDataFromS3(user).flatMap(csvInput => aggregate(csvInput))
}
val batchedJob: Task[List[Int]] = Task.sequence(storeJobs)
Await.ready(batchedJob.runAsync, 8.hours)
Let’s revisit our broken code
85
Future
Getting data for 1
Getting data for 3
Getting data for 2
Getting data for 4
Getting data for 5
Getting data for 6
Getting data for 7
Aggregating data for 3
Aggregating data for 5
Aggregating data for 6
Aggregating data for 2
Aggregating data for 4
Aggregating data for 1
Aggregating data for 7
Task
Getting data for 1
Aggregating data for 1
Getting data for 2
Aggregating data for 2
Getting data for 3
Aggregating data for 3
Getting data for 4
Aggregating data for 4
Getting data for 5
Aggregating data for 5
Getting data for 6
Aggregating data for 6
Getting data for 7
Aggregating data for 7
86
def getClientDataFromS3(id: Long): Task[CSV] = Task {
blocking {
println(s"Getting data for $id")
Thread.sleep(500)
CSV(id.toString)
}
}
def aggregate(input: CSV): Task[Int] = Task {
println(s"Aggregating data for ${input.a}")
1
}
val userList: List[Long] = (1L to 7L).toList
val storeJobs: List[Task[Int]] = Observable.fromIterable(userList).mapAsync(3){ user =>
getClientDataFromS3(user).flatMap(csvInput => aggregate(csvInput))
}
val batchedJob: Task[List[Int]] = storeJobs.toListL
Await.ready(batchedJob.runAsync, 8.hours)
Let’s revisit our broken code
87
Future
Getting data for 1
Getting data for 3
Getting data for 2
Getting data for 4
Getting data for 5
Getting data for 6
Getting data for 7
Aggregating data for 3
Aggregating data for 5
Aggregating data for 6
Aggregating data for 2
Aggregating data for 4
Aggregating data for 1
Aggregating data for 7
Task
Getting data for 1
Aggregating data for 1
Getting data for 2
Aggregating data for 2
Getting data for 3
Aggregating data for 3
Getting data for 4
Aggregating data for 4
Getting data for 5
Aggregating data for 5
Getting data for 6
Aggregating data for 6
Getting data for 7
Aggregating data for 7
Observable
Getting data for 2
Getting data for 3
Getting data for 1
Aggregating data for 3
Aggregating data for 1
Aggregating data for 2
Getting data for 4
Getting data for 5
Getting data for 6
Aggregating data for 4
Aggregating data for 6
Aggregating data for 5
Getting data for 7
Aggregating data for 7
Thank you!
Questions time!
88almendar

Async fun

  • 1.
  • 2.
    About Me: Software Engineer@ almendar Scala since ~2011 Tomasz Kogut Warsaw scala-poland
  • 3.
    Asynchrony and otherparasites Scala Future - primer and beyond Monix - your helping hand 3 Agenda
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
    9 TIME Main Asynchrony ā— Outside ofmain computation flow ā— Delivers result with a callback ā— May not deliver result back
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
    17 Asynchrony We want to: 1.express computation that will run outside the main flow, 2. handle errors if they occur during processing, 3. tell our computation how to return the result to us 4. use type-safe constructs We don’t want to: 1. constraint on how the execution is carried out on the processor 2. explicitly block our main flow, it should make progress if it can
  • 18.
  • 19.
    19 type Async[A] =(Try[A] => Unit) => Unit
  • 20.
    20 type Callback[A] =(Try[A] => Unit) type Async[A] = Callback[A] => Unit
  • 21.
    21 type Async[A] =(Try[A] => Unit) => Unit def divisionService(arg: Int): Async[Int] = cb => cb(Try(22 / arg))
  • 22.
    22 type Async[A] =(Try[A] => Unit) => Unit def divisionService(arg: Int): Async[Int] = cb => cb(Try(22 / arg)) divisionService(44)(println)
  • 23.
    23 type Async[A] =(Try[A] => Unit) => Unit def divisionService(arg: Int): Async[Int] = cb => cb(Try(22 / arg)) divisionService(44)(println) def giveOxygenToAstronauts(arg: Int): Unit = ???
  • 24.
    24 type Async[A] =(Try[A] => Unit) => Unit def divisionService(arg: Int): Async[Int] = cb => cb(Try(22 / arg)) divisionService(44)(println) def giveOxygenToAstronauts(arg: Int): Unit = ??? divisionService(33){ case Success(x) => giveOxygenToAstronauts(x) case Failure(t) => throw new HumanLifeEndangeredException(t) }
  • 25.
    25 def divisionService(arg: Int):Async[Int] = cb => cb(Try(22 / arg)) Int => Callback[Int] => Unit Any => Unit //actor’s signature arg 22 / arg DivisionService
  • 26.
    26 def divisionService(arg: Int):Async[Int] = cb => cb(Try(22 / arg)) Int => Callback[Int] => Unit Any => Unit //actor’s signature (Any,ActorRef) => Unit //actor’s true signature arg 22 / arg DivisionService
  • 27.
    27 Asynchrony escape hatchillusion A.K.A. make semicolon (;) great again def await[A](fa: Async[A]): A
  • 28.
    28 Problems with semicolondriven programming: 1. The lesson of Distributed programming, it’s not always our program we call 2. You kill parallelism (Amdahl Law) 3. Your platform has less threads than you think it does
  • 29.
    29 Amdahl Law ā— 20hours of computing ā— 1 hour cannot be parallelized ā— 19 hours can be parallelized ā— Max speedup is 20x no matter how much cores you have ā— With await you lose all of this.
  • 30.
    30 Execution platforms ā— 1:1kernel level thread like in JVM where blocking is problematic ā— M:N green threading model (Haskell, Golang, Erlang) where you can ā€œblockā€ ā— M:1 where you have only one thread to execute
  • 31.
    31 Execution platforms ā— 1:1kernel level thread like in JVM where blocking is problematic ā— M:N green threading model (Haskell, Golang, Erlang) where you can ā€œblockā€ ā— M:1 where you have only one thread to execute JavaScript
  • 32.
    32 Async pollution def tweets(text:String): Int = ??? def fb(text: String): Int = ??? def totalMentions(text: String): Int = fb(text) + tweets(text)
  • 33.
    33 Async pollution def tweets(text:String): Async[Int] = ??? def fb(text: String): Async[Int] = ??? def totalMentions(text: String): Async[Int] = ???
  • 34.
    34 Async pollution def totalMentions(text:String): Async[Int] = { cb => tweets(text){tNrTry => fb(text){fNrTry => (tNrTry, fNrTry) match { case (Success(tNr), Success(fNr)) => cb(Success(fNr + tNr)) case _ => cb(Failure(new Exception("Failed"))) } } } }
  • 35.
    35 Async pollution def tweets(text:String): Async[Int] = ??? def fb(text: String): Async[Int] = ??? def instagram(text: String): Async[Int] = ??? def tumblr(text: String): Async[Int] = ??? def reddit(text: String): Async[Int] = ??? (...)
  • 36.
  • 37.
  • 38.
    38 type Async[A] =(A => Unit) => Unit def map2[A,B,C](x: Async[A], y: Async[B])(f: (A,B) => C): Async[C] = {cb => var a:A = _ var b:B = _ x apply {a:A => //(...) much fun } y apply {b:B => //(...) much fun } } 1.We’re on a 1:1 platform so we need to synchronize access 2.cb must be called only once in one of the branches, we need a state machine e.g. WaitingForX, WaitingForY states 3.Locks can decrease liveness property of our code, because of blocking 4.There is AtomicReference which helps but still has cost 5.In result we might be processing longer than in the sequential version if we’re CPU bound (sic!)
  • 39.
    39 Stackoverflow problem def map2[A,B,C](x:Async[A], y: Async[B]) (f: (A,B) => C): Async[C] def sequence[A](jobs: List[Async[A]]): Async[List[A]]
  • 40.
    40 def async1: Async[Int]= cb => cb(Success(1)) async1 { x1 => async1 { x2 => async1 { x3 => println( Thread .currentThread() .getStackTrace() .length ) } } } //46 async1 { x1 => async1 { x2 => async1 { x3 => async1 { x4 => println( Thread .currentThread() .getStackTrace() .length ) } } } } //51
  • 41.
    41 Crossing the asyncboundary import scala.concurrent.ExecutionContext.Implicits.global def async1Exc: Async[Int] = cb => global.execute(() => cb(Success(1))) async1Exc { x1 => async1Exc { x2 => async1Exc { x3 => async1Exc { x4 => println( Thread .currentThread() .getStackTrace() .length ) } } } } //12 async1Exc { x1 => async1Exc { x2 => async1Exc { x3 => async1Exc { x4 => async1Exc { x5 => println( Thread .currentThread() .getStackTrace() .length ) } } } } } //12
  • 42.
    42 Scala Future recap ā—onComplete is basically our Async with ExecutionContext ā— It models asynchronous computations (might be parallel) ā— Parallelism depends on policy of passed ExecutionContext ā— Future can run if you have only one thread (JS-approved) ā— It’s stack stafe ā— Every combinator like map, flatMap ect. needs an ExecutionContext ā— The fact that every transformation is scheduled has implications: ā—‹ degree of non-determinism is hard to control ā—‹ performance with CPU bound tasks can suffer
  • 43.
    43 def getClientDataFromS3(id: Long):Future[CSV] //CSV ~2Gb //composition of multiple A => Future[B] functions def aggregate(input: CSV): Future[Int] val userList: List[Long] = (...) //~100 clients val storeJobs: List[Future[Int]] = userList.map { user => getClientDataFromS3(user).flatMap(csvInput => aggregate(csvInput)) } val batchedJob: Future[List[Int]] = Future.sequence(storeJobs) Await.ready(batchedJob, 8.hours) Nondeterminism What is wrong with this code?
  • 44.
    44 Jumping threads def pureDataTransformation1(input:CSV):Seq[Seq[Int]] = ??? def pureDataTransformation2(input: Seq[Seq[Int]]): Seq[Int] = ??? def pureDataTransformation3(input: Seq[Int]): Int = ??? Future(pureDataTransformation1(csv)) .map(pureDataTransformation2) .map(pureDataTransformation3) Future { val pdr1 = pureDataTransformation1(csv) val pdr2 = pureDataTransformation2(pdr1) pureDataTransformation3(pdr2) }
  • 45.
    45 private[concurrent] object InternalCallbackExecutorextends ExecutionContext with BatchingExecutor { override protected def unbatchedExecute(r: Runnable): Unit = r.run() override def reportFailure(t: Throwable): Unit = throw new IllegalStateException("problem in scala.concurrent internal callback", t) } def sequence[A, M[X] <: TraversableOnce[X]] (in: M[Future[A]])(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]], executor: ExecutionContext): Future[M[A]] = { in.foldLeft(successful(cbf(in))) { (fr, fa) => fr.zipWith(fa)(_ += _) }.map(_.result())(InternalCallbackExecutor) } Future local optimizations
  • 46.
    46 /** * Provides alternativeimplementations of the basic transformation operations defined on [[scala.concurrent.Future]], * which try to avoid scheduling to an [[scala.concurrent.ExecutionContext]] if possible, i.e. if the given future * value is already present. */ class FastFuture[A](val future: Future[A]) extends AnyVal { (...) } Akka knows this too
  • 47.
  • 48.
  • 49.
    49 ā— Try toavoid creating pure computation functions that return Future ā— Don’t pollute your interfaces with function that return Future just because root of your computation starts with a Future (e.g. database call) ā— Avoid async if possible ā— Use other tools if you can (i.e. Task) Rules to follow
  • 50.
    50 Getting high performance withparallelism ā— We want as much parallelism as possible ā— We have only so many cores ā— High number of threads will result in often context switches How to scale number of threads?
  • 51.
    51 Looking at theglobal EC ā— The global EC is backed by a ForkJoinPool ā— Thread construction is controlled by a specialized ThreadFactory ā— In general Runtime.getRuntime.availableProcessors is the number of threads that will be created ā— Extra threads creation possible up to 256
  • 52.
    52 Parallelism and blocking Analgorithm is called nonblocking if failure or suspension of any thread cannot cause failure or suspension of another thread Java concurrency in practice
  • 53.
    53 What is blocking? 1.Waiting on lock? 2. Doing Thread.sleep? 3. Doing pure super-long-expensive computation 4. Calling blocking IO APIs like JDBC? 5. Doing CAS operation in High Contention environment?
  • 54.
    54 Use case of scala.concurrent.blocking Future{ blocking { Thread.sleep(999999) } }
  • 55.
    55 def blocking[T](body: =>T):T = BlockContext.current.blockOn(body)(scala.concurrent.AwaitPermission) //inside global EC thread factory def newThread(fjp: ForkJoinPool): ForkJoinWorkerThread = { (...)this thread will try to compensate blocking new ForkJoinWorkerThread(fjp) with BlockContext (...) } //during execution Thread.currentThread match { case ctx: BlockContext => ctx case _ => DefaultBlockContext } private object DefaultBlockContext extends BlockContext { override def blockOn[T](thunk: =>T)(implicit permission: CanAwait): T = thunk }
  • 56.
  • 57.
    57 ā— Monix isa library for combining asynchronous and event-based programs targeting both JVM and JS ā— Developed in open source by Alexandru Nedelcu ā— Very similar to ReactiveX project with nice Scala API ā— Typelevel project, has integration with cats. ā— It seems that other typelevel project might have option to plug-in Monix as backend (e.g. Doobie, Http4s) ā— Slowly it’s getting traction and ecosystem starts to emerge like e.g. monix-kafka project ā— It’s easy to use it with Future-based project, so you don’t need to do a revolution in your code
  • 58.
  • 59.
    59 Task import monix.eval.Task //I’m wrappingglobal EC import monix.execution.Scheduler.Implicits.global def divisionService2(arg: Int) = Task(22 / arg) val divisionTask = divisionService(50) //I'm lazy divisionTask.runOnComplete { case Success(x) => println(x) case Failure(t) => t.printStackTrace() }
  • 60.
    60 Task import monix.eval.Task //I’m wrappingglobal EC import monix.execution.Scheduler.Implicits.global def divisionService2(arg: Int) = Task(22 / arg) val divisionTask = divisionService(50) //I'm lazy divisionTask.runOnComplete { case Success(x) => println(x) case Failure(t) => t.printStackTrace() }(implicit s: Scheduler)
  • 61.
    61 Task import monix.eval.Task //I’m wrappingglobal EC import monix.execution.Scheduler.Implicits.global def divisionService2(arg: Int) = Task(22 / arg) val divisionTask = divisionService(50) //I'm lazy divisionTask.runOnComplete { case Success(x) => println(x) case Failure(t) => t.printStackTrace() } val divisionTaskMultipliedBy2 = divisionTask.map( _ * 2) val runningTask: CancelableFuture[Int] = divisionTaskMultipliedBy2.runAsync runningTask.map(_ * 4).onComplete(println)
  • 62.
    62 scala> :paste val p= for { i <- Task(1) j <- Task(2) _ <- Task(println("Hello world")) } yield i+j // Exiting paste mode, now interpreting. p: monix.eval.Task[Int] = Task.FlatMap(Task@1633066228, $$Lambda$1615/1219583471@23cafb14) p.runAsync res0: monix.execution.CancelableFuture[Int] = monix.execution.CancelableFuture$Implementation@f96168d Hello world scala> res0.value res1: Option[scala.util.Try[Int]] = Some(Success(3))
  • 63.
    63 scala> val now= Task.now { println("Hello"); 22 } Hello now: monix.eval.Task[Int] = Task.Now(22) ------------------------------------------------------------------------------------------------- scala> val eval = Task.eval{ println("Hello"); 22} eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80) scala> eval.runSyncMaybe Hello res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22) ------------------------------------------------------------------------------------------------- scala> val once = Task.evalOnce { println("Hello"); 22 } once: monix.eval.Task[Int] = Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d)) scala> once.foreach(println) Hello 22 scala> once.foreach(println) 22 res108: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Now@310d507 ------------------------------------------------------------------------------------------------- scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } } task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
  • 64.
    64 scala> val now= Task.now { println("Hello"); 22 } Hello now: monix.eval.Task[Int] = Task.Now(22) ------------------------------------------------------------------------------------------------- scala> val eval = Task.eval{ println("Hello"); 22} eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80) scala> eval.runSyncMaybe Hello res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22) ------------------------------------------------------------------------------------------------- scala> val once = Task.evalOnce { println("Hello"); 22 } once: monix.eval.Task[Int] = Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d)) scala> once.foreach(println) Hello 22 scala> once.foreach(println) 22 res108: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Now@310d507 ------------------------------------------------------------------------------------------------- scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } } task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
  • 65.
    65 scala> val now= Task.now { println("Hello"); 22 } Hello now: monix.eval.Task[Int] = Task.Now(22) ------------------------------------------------------------------------------------------------- scala> val eval = Task.eval{ println("Hello"); 22} eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80) scala> eval.runSyncMaybe Hello res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22) ------------------------------------------------------------------------------------------------- scala> val once = Task.evalOnce { println("Hello"); 22 } once: monix.eval.Task[Int] = Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d)) scala> once.foreach(println) Hello 22 scala> once.foreach(println) 22 res108: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Now@310d507 ------------------------------------------------------------------------------------------------- scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } } task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
  • 66.
    66 scala> val now= Task.now { println("Hello"); 22 } Hello now: monix.eval.Task[Int] = Task.Now(22) ------------------------------------------------------------------------------------------------- scala> val eval = Task.eval{ println("Hello"); 22} eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80) scala> eval.runSyncMaybe Hello res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22) ------------------------------------------------------------------------------------------------- scala> val once = Task.evalOnce { println("Hello"); 22 } once: monix.eval.Task[Int] = Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d)) scala> once.foreach(println) Hello 22 scala> once.foreach(println) 22 res108: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Now@310d507 ------------------------------------------------------------------------------------------------- scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } } task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
  • 67.
    67 scala> val now= Task.now { println("Hello"); 22 } Hello now: monix.eval.Task[Int] = Task.Now(22) ------------------------------------------------------------------------------------------------- scala> val eval = Task.eval{ println("Hello"); 22} eval: monix.eval.Task[Int] = Task.Eval($$Lambda$1750/778127042@2d007e80) scala> eval.runSyncMaybe Hello res4: Either[monix.execution.CancelableFuture[Int],Int] = Right(22) ------------------------------------------------------------------------------------------------- scala> val once = Task.evalOnce { println("Hello"); 22 } once: monix.eval.Task[Int] = Task.Eval(Coeval.Once($$Lambda$1912/1015141159@422dd52d)) scala> once.foreach(println) Hello 22 scala> once.foreach(println) 22 res108: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Now@310d507 ------------------------------------------------------------------------------------------------- scala> val task = Task.defer {Task.now { println("Effect"); "Hello!" } } task: monix.eval.Task[String] = Task.Suspend($$Lambda$1928/894408147@24935569)
  • 68.
    68 scala> val forked= Task.fork{Task.now(1)} forked: monix.eval.Task[Int] = Task.FlatMap(Task@1822739293, monix.eval.Task$$$Lambda$1614/131301275@3427f53a) scala> forked.runSyncMaybe res114: Either[monix.execution.CancelableFuture[Int],Int] = Left(monix.execution.CancelableFuture$Implementation@9b393c7) ------------------------------------------------------------------------------------------------- lazy val io = Scheduler.io(name="scalawaw-io") val source = Task(println(s"Running on thread: ${Thread.currentThread.getName}")) val forked = Task.fork(source, io) forked.foreach(println) Running on thread: scalawaw-io-233 ------------------------------------------------------------------------------------------------- scala> val danger = Task.delay { if(Random.nextInt % 2 == 0) throw new Exception("Boom") else 2 }.memoizeOnSuccess danger: monix.eval.Task[Int] = Task.Eval(<function0>) scala> danger.runOnComplete{x => println(x)} Failure(java.lang.Exception: Boom) scala> danger.runOnComplete{x => println(x)} Failure(java.lang.Exception: Boom) scala> danger.runOnComplete{x => println(x)} Success(2) scala> List.fill(1000)(danger.runSyncMaybe).forall(_.isRight) res48: Boolean = true
  • 69.
    69 scala> val forked= Task.fork{Task.now(1)} forked: monix.eval.Task[Int] = Task.FlatMap(Task@1822739293, monix.eval.Task$$$Lambda$1614/131301275@3427f53a) scala> forked.runSyncMaybe res114: Either[monix.execution.CancelableFuture[Int],Int] = Left(monix.execution.CancelableFuture$Implementation@9b393c7) ------------------------------------------------------------------------------------------------- lazy val io = Scheduler.io(name="scalawaw-io") val source = Task(println(s"Running on thread: ${Thread.currentThread.getName}")) val forked = Task.fork(source, io) forked.foreach(println) Running on thread: scalawaw-io-233 ------------------------------------------------------------------------------------------------- scala> val danger = Task.delay { if(Random.nextInt % 2 == 0) throw new Exception("Boom") else 2 }.memoizeOnSuccess danger: monix.eval.Task[Int] = Task.Eval(<function0>) scala> danger.runOnComplete{x => println(x)} Failure(java.lang.Exception: Boom) scala> danger.runOnComplete{x => println(x)} Failure(java.lang.Exception: Boom) scala> danger.runOnComplete{x => println(x)} Success(2) scala> List.fill(1000)(danger.runSyncMaybe).forall(_.isRight) res48: Boolean = true
  • 70.
    70 scala> val forked= Task.fork{Task.now(1)} forked: monix.eval.Task[Int] = Task.FlatMap(Task@1822739293, monix.eval.Task$$$Lambda$1614/131301275@3427f53a) scala> forked.runSyncMaybe res114: Either[monix.execution.CancelableFuture[Int],Int] = Left(monix.execution.CancelableFuture$Implementation@9b393c7) ------------------------------------------------------------------------------------------------- lazy val io = Scheduler.io(name="scalawaw-io") val source = Task(println(s"Running on thread: ${Thread.currentThread.getName}")) val forked = Task.fork(source, io) forked.foreach(println) Running on thread: scalawaw-io-233 ------------------------------------------------------------------------------------------------- scala> val danger = Task.delay { if(Random.nextInt % 2 == 0) throw new Exception("Boom") else 2 }.memoizeOnSuccess danger: monix.eval.Task[Int] = Task.Eval(<function0>) scala> danger.runOnComplete{x => println(x)} Failure(java.lang.Exception: Boom) scala> danger.runOnComplete{x => println(x)} Failure(java.lang.Exception: Boom) scala> danger.runOnComplete{x => println(x)} Success(2) scala> List.fill(1000)(danger.runSyncMaybe).forall(_.isRight) res48: Boolean = true
  • 71.
    71 scala> val forked= Task.fork{Task.now(1)} forked: monix.eval.Task[Int] = Task.FlatMap(Task@1822739293, monix.eval.Task$$$Lambda$1614/131301275@3427f53a) scala> forked.runSyncMaybe res114: Either[monix.execution.CancelableFuture[Int],Int] = Left(monix.execution.CancelableFuture$Implementation@9b393c7) ------------------------------------------------------------------------------------------------- lazy val io = Scheduler.io(name="scalawaw-io") val source = Task(println(s"Running on thread: ${Thread.currentThread.getName}")) val forked = Task.fork(source, io) forked.foreach(println) Running on thread: scalawaw-io-233 ------------------------------------------------------------------------------------------------- scala> val danger = Task.delay { if(Random.nextInt % 2 == 0) throw new Exception("Boom") else 2 }.memoizeOnSuccess danger: monix.eval.Task[Int] = Task.Eval(<function0>) scala> danger.runOnComplete{x => println(x)} Failure(java.lang.Exception: Boom) scala> danger.runOnComplete{x => println(x)} Failure(java.lang.Exception: Boom) scala> danger.runOnComplete{x => println(x)} Success(2) scala> List.fill(1000)(danger.runSyncMaybe).forall(_.isRight) res48: Boolean = true
  • 72.
    72 def runForrest()(implicit ec:ExecutionContext): Future[Unit] = Future(println("Run Forrest")) def runForrestL(): Task[Unit] = Task.deferFutureAction { implicit scheduler => runForrest()}
  • 73.
    73 def runForrest()(implicit ec:ExecutionContext): Future[Unit] = Future(println("Run Forrest")) def runForrestL(): Task[Unit] = Task.deferFutureAction { implicit scheduler => runForrest()} trait Database { def getClient(id:Long)(implicit ec: ExecutionContext): Future[Customer] }
  • 74.
    74 def runForrest()(implicit ec:ExecutionContext): Future[Unit] = Future(println("Run Forrest")) def runForrestL(): Task[Unit] = Task.deferFutureAction { implicit scheduler => runForrest()} trait Database { def getClient(id:Long)(implicit ec: ExecutionContext): Future[Customer] } trait DatabaseL { protected val db:Database def getClient(id:Long): Task[Customer] = Task.deferFutureAction{ implicit scheduler => db.getClient(id) } } Profit! No more implicit ExecutionContext passed around
  • 75.
    75 def task(taskName: String):Task[String] = Task { println(s"$taskName at ${LocalDateTime.now}"); taskName } val batch = for { t1 <- task("t1").delayExecution(10.seconds) t2 <- task("t2").delayExecution(5.seconds) t3 <- task("t3") } yield s"$t1 $t2 $t3" println(LocalDateTime.now) batch.foreach(println) // Exiting paste mode, now interpreting. 2017-06-27T14:07:17.116 task: (taskName: String)monix.eval.Task[String] batch: monix.eval.Task[String] = Task.FlatMap(Task@705632156, $$Lambda$3756/1733192919@f25496d) res7: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Implementation@1b19a48a t1 at 2017-06-27T14:07:27.120 t2 at 2017-06-27T14:07:32.122 t3 at 2017-06-27T14:07:32.124 t1 t2 t3
  • 76.
    76 def task(taskName: String):Task[String] = Task { println(s"$taskName at ${LocalDateTime.now}"); taskName } val batch = Task.zipMap3 ( task("t1").delayExecution(10.seconds), task("t2").delayExecution(5.seconds), task("t3")) {(t1,t2,t3) => s"$t1 $t2 $t3"} println(LocalDateTime.now) batch.foreach(println) // Exiting paste mode, now interpreting. 2017-06-27T14:12:27.466 t3 at 2017-06-27T14:12:27.479 batch: monix.eval.Task[String] = Task.Async(monix.eval.internal.TaskMapBoth$$$Lambda$3762/845438120@d41c7a7) res9: monix.execution.CancelableFuture[Unit] = monix.execution.CancelableFuture$Implementation@2b26ab67 t2 at 2017-06-27T14:12:32.484 t1 at 2017-06-27T14:12:37.484 t1 t2 t3
  • 77.
  • 78.
    78 Single Multiple Synchronous AIterable[A] Asynchronous Future[A] / Task[A] Observable[A] Observable is like Task but with multiple values available at some point in time
  • 79.
    79 //Like normal scalacollection val obs = Observable(1,2,3) val plusOneObs = obs.map(_ + 1)// lazy 😁 plusOneObs.foreach(println)// prints 2,3,4
  • 80.
    80 //Infinite asynchronous val infAsyncObs:Observable[Int] = Observable.repeatEval(Random.nextInt).flatMap(x => Observable.fromFuture(Future(x * x * x)) ).filter(_ % 2 == 0) val task: Task[List[Int]] = infAsyncObs.take(10).toListL task.runOnComplete(x => println(x)) Success(List(-1617719296, -570202176, -970884696, 1265664000, -812102280, 464640576, -1594939576, 1491391144, 2144446976, 742992448))
  • 81.
    81 //Time based val await= Observable.zip2( Observable.intervalAtFixedRate(3.seconds), Observable.repeatEval(LocalDateTime.now()) ) .map { case (_, t) => t }.take(3).foreach(println) Await.ready(await, 1.minute) 2017-06-27T15:33:20.855 2017-06-27T15:33:23.826 2017-06-27T15:33:26.826
  • 82.
    82 //Parallel processing val maxConcurrentThreads= new AtomicInteger(0) val allValuesOfmaxConcurrentThreads = new AtomicReference[List[Int]](Nil) val allJobs = Observable .repeatEval(Thread.currentThread().getId) .take(1000) .mapAsync(50)(threadId => Task { blocking { val see = maxConcurrentThreads.incrementAndGet() allValuesOfmaxConcurrentThreads .updateAndGet(t => see :: t) maxConcurrentThreads.decrementAndGet() threadId } }) .toListL .runAsync val threadIdsList: Seq[Long] = Await.result(allJobs, 5.minutes) println(s"Highest observed no. of threads ${allValuesOfmaxConcurrentThreads.get().max}") println(s"Threads used ${threadIdsList.distinct.length}") //Without blocking Highest observed nr of threads 3 Threads used 5 //With Blocking Highest observed nr of threads 4 Threads used 49
  • 83.
    83 def getClientDataFromS3(id: Long):Future[CSV] = Future { blocking { println(s"Getting data for $id") Thread.sleep(500) CSV(id.toString) } } def aggregate(input: CSV): Future[Int] = Future { println(s"Aggregating data for ${input.a}") 1 } val userList: List[Long] = (1L to 7L).toList val storeJobs: List[Future[Int]] = userList.map { user => getClientDataFromS3(user).flatMap(csvInput => aggregate(csvInput)) } val batchedJob: Future[List[Int]] = Future.sequence(storeJobs) Await.ready(batchedJob, 8.hours) Let’s revisit our broken code
  • 84.
    84 def getClientDataFromS3(id: Long):Task[CSV] = Task { blocking { println(s"Getting data for $id") Thread.sleep(500) CSV(id.toString) } } def aggregate(input: CSV): Task[Int] = Task { println(s"Aggregating data for ${input.a}") 1 } val userList: List[Long] = (1L to 7L).toList val storeJobs: List[Task[Int]] = userList.map { user => getClientDataFromS3(user).flatMap(csvInput => aggregate(csvInput)) } val batchedJob: Task[List[Int]] = Task.sequence(storeJobs) Await.ready(batchedJob.runAsync, 8.hours) Let’s revisit our broken code
  • 85.
    85 Future Getting data for1 Getting data for 3 Getting data for 2 Getting data for 4 Getting data for 5 Getting data for 6 Getting data for 7 Aggregating data for 3 Aggregating data for 5 Aggregating data for 6 Aggregating data for 2 Aggregating data for 4 Aggregating data for 1 Aggregating data for 7 Task Getting data for 1 Aggregating data for 1 Getting data for 2 Aggregating data for 2 Getting data for 3 Aggregating data for 3 Getting data for 4 Aggregating data for 4 Getting data for 5 Aggregating data for 5 Getting data for 6 Aggregating data for 6 Getting data for 7 Aggregating data for 7
  • 86.
    86 def getClientDataFromS3(id: Long):Task[CSV] = Task { blocking { println(s"Getting data for $id") Thread.sleep(500) CSV(id.toString) } } def aggregate(input: CSV): Task[Int] = Task { println(s"Aggregating data for ${input.a}") 1 } val userList: List[Long] = (1L to 7L).toList val storeJobs: List[Task[Int]] = Observable.fromIterable(userList).mapAsync(3){ user => getClientDataFromS3(user).flatMap(csvInput => aggregate(csvInput)) } val batchedJob: Task[List[Int]] = storeJobs.toListL Await.ready(batchedJob.runAsync, 8.hours) Let’s revisit our broken code
  • 87.
    87 Future Getting data for1 Getting data for 3 Getting data for 2 Getting data for 4 Getting data for 5 Getting data for 6 Getting data for 7 Aggregating data for 3 Aggregating data for 5 Aggregating data for 6 Aggregating data for 2 Aggregating data for 4 Aggregating data for 1 Aggregating data for 7 Task Getting data for 1 Aggregating data for 1 Getting data for 2 Aggregating data for 2 Getting data for 3 Aggregating data for 3 Getting data for 4 Aggregating data for 4 Getting data for 5 Aggregating data for 5 Getting data for 6 Aggregating data for 6 Getting data for 7 Aggregating data for 7 Observable Getting data for 2 Getting data for 3 Getting data for 1 Aggregating data for 3 Aggregating data for 1 Aggregating data for 2 Getting data for 4 Getting data for 5 Getting data for 6 Aggregating data for 4 Aggregating data for 6 Aggregating data for 5 Getting data for 7 Aggregating data for 7
  • 88.

Editor's Notes

  • #3Ā Danish global company 18 office Dev: England, Poland, Lithuania, Denmark, Belarus, Germany Platform - advertisers campgain, publishers inventory Big Data Kilos-TB, Fast Service 3M req/s, Machine Learning Hiring
  • #4Ā Divided 3 parts Clear out terminology Monix overcomes
  • #5Ā We’re not giving it a deep thought
  • #8Ā Simple main execution
  • #9Ā Work aside the main flow
  • #10Ā We’re not speaking about time here yet
  • #11Ā Threads within processes Important when talking about platforms Multithreading <: Asynchrony Single threading <: Asynchrony Both are specialization of Async. Underlying hardware platform differs.
  • #12Ā This is about algorithms, decomposability, and ordering Getting FB and Twitter data
  • #15Ā Overlapping
  • #16Ā It’s about time, you cannot have parallel programs on single core machine Physical requirment, not logical
  • #17Ā Again we can look into our machine hardware and talk about different levels of paralellism bit-level, instruction level, task-parallelism
  • #18Ā Async is the most general term Concentrate on what we want Define Async type, and see
  • #20Ā It lookg ugly, because it is ugly
  • #28Ā We would like to believe we can do this and pretend async calls are like sync ones. We’re used to ordering of things with ;
  • #31Ā Question to audience
  • #32Ā Scala runs on the JavaScript enginees 1:1 are hardest to work with, but offer significant advantages, you can emulate M:N platform on them
  • #36Ā Wait for the Product owner to come
  • #37Ā Async is hard and ugly
  • #44Ā 1 book to win! flatMap chains Futures, does not schedule them immediately
  • #45Ā Jumping threads, cache locality destroyed
  • #46Ā Batching Runnables, running on current thread
  • #49Ā This is only relevant for CPU bound work, when IO comes to play, everything changes a lot
  • #52Ā Threads are super expensive, why allow to create over 50x times the normal number of them?
  • #54Ā 5. Measurments show that AtomicInteger performs worst than ReentrantLock (fairness) under High Contention otherwise in medium contention
  • #56Ā Question nr two: Should be put inside a block cpu pure computation that is long running?
  • #62Ā Task is lazy so it’s a blueprint for computation, that needs to be invoked. Cancelable Future implements a Future, so it’s all good
  • #63Ā As you can see we’re in very familiar land Task.FlatMap - underneath there is trampolining, that means it’s stack safe
  • #64Ā eval is like function0 evalOnce is like lazy val defer like factory Nothing fancy
  • #65Ā eval is like function0 evalOnce is like lazy val defer like factory Nothing fancy
  • #66Ā eval is like function0 evalOnce is like lazy val defer like factory Nothing fancy
  • #67Ā eval is like function0 evalOnce is like lazy val defer like factory Nothing fancy
  • #68Ā eval is like function0 evalOnce is like lazy val defer like factory Nothing fancy
  • #76Ā Parallelism
  • #77Ā Parallelism
  • #84Ā flatMap chains Futures, does not schedule them immediately
  • #85Ā flatMap chains Futures, does not schedule them immediately