Skip to content

Instantly share code, notes, and snippets.

@starkcoffee
Last active June 15, 2016 22:43
Show Gist options
  • Save starkcoffee/9b7bee938628a2bff783bed1f0634b26 to your computer and use it in GitHub Desktop.
Save starkcoffee/9b7bee938628a2bff783bed1f0634b26 to your computer and use it in GitHub Desktop.
Understanding how futures are executed
/*
I wanted to write a specs2 unit test that proves a function works concurrently. I tried writing a unit test
to prove that Futures run concurrently (not always sequentially), just for fun. I ran the test below,
expecting count to equal 1, but it always executed the futures in order, even though C1 takes the longest
time. I realised, I need to go back to school and learn how futures are executed.
*/
"prove futures run in parallel" in new Context {
var count = 0
Await.result(Future.join(Seq(
Future { Thread.sleep(2000); println("c1"); count = 1 },
Future { Thread.sleep(1000); println("c2"); count = 2 },
Future { Thread.sleep(0); println("c3"); count = 3 }
)))
count ==== 1
}
/*
It was hard to know where to start: What is executing the futures? Is it Await.result? Is it the specs2
framework? Some other lauering implicit executor somewhere? What also made it tricky is that I could
only find documentation on how Scala Futures are executed, not Twitter Futures :/
In the end a colleague told me to "You need to build your futures in the context of an ExecutionContext
that runs them in parallel. If you replace `Future {` with `FuturePool.unboundedPool {` in your code snippet
they should be executed in parallel". Thanks Kristof!
And lo and behold, that works!
*/
"prove futures run in parallel for realsies" in new Context {
var count = 0
Await.result(Future.join(Seq(
FuturePool.unboundedPool { Thread.sleep(2000); println("c1"); count = 1 },
FuturePool.unboundedPool { Thread.sleep(1000); println("c2"); count = 2 },
FuturePool.unboundedPool { Thread.sleep(0); println("c3"); count = 3 }
)))
count ==== 1
}
/*
Remaining mysteries: how many pools am I using now? What is the default execution context?
*/
@peterbourgon
Copy link

Your futures are mutating shared state without explicit synchronization. Unless I overlook some atomic guarantees of Scala's memory model, you have a data race :) If you're just using this as an example to understand futures, then no problem, but beware this is dangerous code in prod :)

@starkcoffee
Copy link
Author

starkcoffee commented Jun 15, 2016

@peterbourgon thanks! Yeh this is just a test. That means it is possible for a count to get lost right? If we use atomic, all counts are guaranteed to be counted. I could write a test to prove that too.

@peterbourgon
Copy link

I'm not an expert on the Scala/JVM memory model, but it's probably just undefined behavior. A count could get lost, counts could be applied out of order, the program could just crash, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment