I'm taking the Reactive programming course on Coursera and when doing one of the assignments I came across something weird. Anyways I added a few methods to the Future Companion object via this extension
implicit class FutureCompanionOps[T](val f: Future.type) extends AnyVal {
/** Returns a future that is always completed with `value`.
*/
def always[T](value: T): Future[T] = Future(value)
/** Returns a future that is never completed.
*
* This future may be useful when testing if timeout logic works correctly.
*/
def never[T]: Future[T] = Promise().future
/** Given a list of futures `fs`, returns the future holding the list of values of all the futures from `fs`.
* The returned future is completed only once all of the futures in `fs` have been completed.
* The values in the list are in the same order as corresponding futures `fs`.
* If any of the futures `fs` fails, the resulting future also fails.
*/
def all[T](fs: List[Future[T]]): Future[List[T]] = {
val resPr = Promise[List[T]]()
def function( in: List[Future[T]], fxs:Future[List[T]] ): Future[List[T]] =
{
if(in.isEmpty) fxs
else
function( in.tail, for { i <- in.head ; xs <- fxs } yield { i :: xs } )
}
function( fs, resPr.success(Nil).future )
}
}
i then wrote this on a Scala WorkSheet in Eclipse
object TestSheet {
val tempPr = Promise[Boolean]()
val anotherFuLs = List( Future.always(true), Future.always(false), tempPr.future )
//> anotherFuLs : List[scala.concurrent.Future[Boolean]] = List(scala.concurren
//| t.impl.Promise$DefaultPromise@a19b1de, scala.concurrent.impl.Promise$Default
//| Promise@1cec6b00, scala.concurrent.impl.Promise$DefaultPromise@625dcec6)
val crapFut = Future.all(anotherFuLs) //> crapFut : scala.concurrent.Future[List[Boolean]] = scala.concurrent.impl.Pr
//| omise$DefaultPromise@6564dbd5
crapFut.isCompleted //> res3: Boolean = false
tempPr.success(false) //> res4: nodescala.TestSheet.tempPr.type = scala.concurrent.impl.Promise$Defaul
//| tPromise@625dcec6
crapFut.isCompleted //> res5: Boolean = true
crapFut onComplete {
case Success(ls) => println( ls )
case Failure(e) => println( "Failed with Exception " + e )
}
}
no matter what I can't get the Scala Work Sheet to print out the values of the resulting List. However when I write a unit test and run scala test, I have no problem comparing the final resulting list. Is this a bug in scala worksheet when working with asynchronous stuff ?
This is the unit test
test("A composed future with all should complete when all futures complete") {
val tempPr = Promise[Boolean]()
val lsFu = List( Future.always(true), Future.always(false), tempPr.future );
val fuL = Future.all( lsFu )
fuL onComplete { case Success(ls) => println( "This got done" ); assert( ls === List( true, false, true ), "I should get back the expected List" )
case Failure(ex) => assert( false, "Failed with Exception " + ex ) }
assert( fuL.isCompleted === false, "The resulting Future should not be complete when the depending futures are still pending" )
tempPr.success(true)
}
It looks like the problem is that the main thread that runs your worksheet code is ending before the onComplete
handler gets run.
Scala's default ExecutionContext
is essentially a thread pool full of daemon threads. "Daemon" in this context means that even if that thread is busy doing something, it won't prevent the JVM from shutting down when all non-daemon threads finish. In your case, the main thread is probably the only non-daemon thread in the program.
Calling onComplete
on a Future will make it so that the implicitly-provided ExecutionContext will execute your handler when the Future completes. This means the handler runs on a daemon thread. Since onComplete
is the last thing you do in your main method, the JVM is simply finishing before the ExecutionContext gets around to running the handler.
Normally, this isn't a big deal. In a scenario like a web server, your JVM will be up and running for a long time. For your use case, I'd recommend blocking for the Future to complete, by using one of the methods in scala.concurrent.Await
. That way you can run your completion logic as a part of the main thread, in the main method.