don't block - how to mess up akka and spray
DESCRIPTION
Presented at Feb 2014 Iowa Scala Enthusiasts meetup: http://www.meetup.com/ia-scala/events/158419152/ Git repo contains runnable code samples not in slides: https://github.com/zcox/iascala-dont-block Actors and Futures make writing concurrent, asynchronous, non-blocking, reactive applications much simpler than dealing with threads, locks and synchronization directly. However, it’s also really easy to inadvertently block in the wrong place and bring your entire system to a grinding halt. We’ll demonstrate some of the common, simple ways to destroy Akka and Spray applications, examine the internals of these libraries to explore what’s happening, and discuss some best practices they provide for avoiding these problems.TRANSCRIPT
Don't BlockHow to Mess Up Akka and Spray
Zach Cox - @zcox - [email protected] - Feb 2014
Don t block1:52 PM - 4 Aug 2013
Horse ebooks @Horse_ebooks
Follow
467 RETWEETS 320 FAVORITES
PurposeScala provides great concurrency toolsVery easy to mess upDemonstrate how blocking can prevent processingProvide solutions
Things We Will BlockThreads (Java)Futures (Scala)Actors (Akka)Spray
SpoilerAll about blocking threads
What is Blocking?Code that takes a long time to runNetwork I/O
HTTP requestsDatabase access
File I/OReally heavy computationNothing after the blocking function can run on this thread untilit is done
Java: Runnable, Executortrait Runnable { def run(): Unit}
trait Executor { def execute(command: Runnable): Unit}
ThreadPoolExecutorclass ThreadPoolExecutor extends Executor { val pool: Set[Thread] = Set.empty val tasks: BlockingQueue[Runnable] = ???
def execute(command: Runnable): Unit = { //run command on new thread, or queue it, or reject it, depending on settings... }}
How to block ThreadPoolExecutorExecute tasks that block in run() methodAll threads in pool get blockedTasks are queued before execution (until queue is full)How it is supposed to work...Need to be aware of the blocking though
ForkJoinPoolExecutorService implementationscala.concurrent.forkjoinjava.util.concurrent (Java7)Sub-dividing tasks, work queue, worker thread pool, work stealing, ...https://www.google.com/search?q=java+fork+join
How to block ForkJoinPoolSame as ThreadPoolExecutor
Solutions for Blocked Executorval executor = Executors.newFixedThreadPool(moreThreads)
val executor1 = Executors.newFixedThreadPool(pool1Size)val executor2 = Executors.newFixedThreadPool(pool2Size)
Java: Callable, Future,ExecutorService
trait Callable[V] { def call(): V}
trait Future[V] { def isDone(): Boolean def get(): V}
trait ExecutorService extends Executor { def submit(task: Runnable): Future[_] def submit[T](task: Callable[T]): Future[T]}
Future[T]Monad that eventually contains either:
A value of type T (success)A Throwable (failure)
Future[T] is the read-side; Promise[T] is the write-sideValue is computed and placed into promise/future on some otherthread (usually)//TODO compelling example of Future...
ExecutionContextRuns code that asynchronously completes futuresScala version of Executor/ExecutorService
Implementations usually wrap oneExecutor => ExecutionContextExecutorService => ExecutionContext
Future.apply runs body function using ExecutionContextWraps the body function in a RunnableExecutes that Runnable on an ExecutionContextThat Runnable completes a Promise
ExecutionContext.globalTries to use ForkJoinPoolFalls back to ThreadPoolExecutor
How to block Future/ExecutionContextExecutionContext just wraps an Executor/ExecutorServiceExecutionContext.global usually wraps either aForkJoinPool or a ThreadPoolExecutorWe already know tldr function passed to Future.apply blocks the underlying thread,exhaust the pool
how to block those
Solutions for blockedFuture/ExecutionContext
java ... \ -Dscala.concurrent.context.minThreads=8 \ -Dscala.concurrent.context.numThreads=16 \ -Dscala.concurrent.context.maxThreads=24 \ ...
implicit val c = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(123))
implicit val defaultContext = ExecutionContext.globalval databaseContext = ExecutionContext.fromExecutor(null)
Future("default processing")Future("database operations")(databaseContext)
Actors and Dispatchersactor ! msgmsg placed in actor''s Mailbox queueMailbox is a RunnableMailbox executed on dispatcher''s ExecutorServiceBy default, all actors use the same default dispatcher
How to Block ActorsRemember ? Do that.Block in Actor.receiveEnough blocked actors will exhaust the dispatcher''s thread pool
how to block Executor
Solutions for Blocked Actorsblocking2-dispatcher { type = Dispatcher executor = "fork-join-executor"}
val blocking = system.actorOf(Props[BlockingActor] .withRouter(FromConfig()) .withDispatcher("blocking2-dispatcher"), "blocking2")
my-dispatcher { type = Dispatcher executor = "fork-join-executor" fork-join-executor { parallelism-factor = 10.0 parallelism-max = 100 }}
Fun FactMessageDispatcher is an ExecutionContext
val jdbcContext = system.dispatchers.lookup("jdbc-dispatcher")Future(useTheDatabase)(jdbcContext)
Sprayspray-io/akka-io: Java NIO + Actorsspray-can: HTTP server & client built on spray-iospray-routing: HTTP request/response DSL
How to Block SprayBuilt on actorsWe know SimpleRoutingApp uses a single actor to route all requests (!!!)That actor synchronously calls runRoute - easily blocked!That actor uses default Akka dispatcher - easily blocked!
how to block those
Example code
Solutions for Blocking SprayDo not call blocking functions directly in routes
Instead detach to Future or ActorSpray can complete a response using a Future
Use separate dispatchersGive Spray its own dispatcher(s)Give your blocking code its own dispatcher(s)
Java NIO and spray-clientBlocking I/O: One thread per socketNon-blocking I/O: One thread, many socketsNo network I/O (web service client, database, etc) libraries use it!Except spray-client...Start writing !Other protocols (TCP, SMTP, XMPP, various DBs, etc) can use
Scala web service clients using spray-can
akka-io
Banno is HiringScala Developers!