scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

document that ECs must be async to be general purpose #8377

Closed scabug closed 10 years ago

scabug commented 10 years ago

The following example uses an ExecutionContext with direct, synchronous execution.

  def futureTest: Int = {
    import scala.concurrent.duration._
    implicit val immediateContext = new scala.concurrent.ExecutionContext {
      override def execute(runnable: Runnable) { runnable.run() }
      override def reportFailure(t: Throwable) { t.printStackTrace() }
    }
    val normal = scala.concurrent.ExecutionContext.fromExecutor(java.util.concurrent.Executors.newCachedThreadPool())
    val initial = scala.concurrent.Future({ Thread.sleep(1000); 1 })(normal)
    val res = initial.flatMap(x1 => scala.concurrent.Future.successful(2)).flatMap(x2 => {
      val res2 = scala.concurrent.Future.successful(3).flatMap(x3 => scala.concurrent.Future.successful(4))
      scala.concurrent.Await.ready(res2, 300 seconds)   // this throws the IllegalArgumentException
    })
    scala.concurrent.Await.result(res, 300 seconds)
  }
  futureTest

It fails with

java.lang.IllegalArgumentException: requirement failed at scala.Predef$.require(Predef.scala:221) at scala.concurrent.Future$InternalCallbackExecutor$Batch.run(Future.scala:628) at scala.concurrent.Future$InternalCallbackExecutor$.scala$concurrent$Future$InternalCallbackExecutor$$unbatchedExecute(Future.scala:691) at scala.concurrent.Future$InternalCallbackExecutor$Batch.blockOn(Future.scala:669) at scala.concurrent.Await$.ready(package.scala:86) at $anonfun$3.apply(:17) at $anonfun$3.apply(:15) at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:251) at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:249) at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32) at $anon$1.execute(:10) at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:40) at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:248) at scala.concurrent.Promise$class.complete(Promise.scala:55) at scala.concurrent.impl.Promise$DefaultPromise.complete(Promise.scala:153) at scala.concurrent.Future$$anonfun$flatMap$1$$anonfun$apply$3.apply(Future.scala:254) at scala.concurrent.Future$$anonfun$flatMap$1$$anonfun$apply$3.apply(Future.scala:254) at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32) at scala.concurrent.Future$InternalCallbackExecutor$Batch$$anonfun$run$1.processBatch$1(Future.scala:640) at scala.concurrent.Future$InternalCallbackExecutor$Batch$$anonfun$run$1.apply$mcV$sp(Future.scala:655) at scala.concurrent.Future$InternalCallbackExecutor$Batch$$anonfun$run$1.apply(Future.scala:632) at scala.concurrent.Future$InternalCallbackExecutor$Batch$$anonfun$run$1.apply(Future.scala:632) at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:72) at scala.concurrent.Future$InternalCallbackExecutor$Batch.run(Future.scala:631) at scala.concurrent.Future$InternalCallbackExecutor$.scala$concurrent$Future$InternalCallbackExecutor$$unbatchedExecute(Future.scala:691) at scala.concurrent.Future$InternalCallbackExecutor$.execute(Future.scala:682) at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:40) at scala.concurrent.impl.Promise$KeptPromise.onComplete(Promise.scala:333) at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:254) at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:249) at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32) at $anon$1.execute(:10) at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:40) at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:248) at scala.concurrent.Promise$class.complete(Promise.scala:55) at scala.concurrent.impl.Promise$DefaultPromise.complete(Promise.scala:153) at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:23) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744)

The stack trace is from 2.10.3, but it also fails in 2.11.0-RC1. The stack strace looks very similar to that of #8069. Batch#blockOn looks suspicious, setting _tasksLocal to Nil and then calling run which requires it to be null. Also the "WARNING" comment from BatchingExecutor seems to apply to InternalCallbackExecutor.

scabug commented 10 years ago

Imported From: https://issues.scala-lang.org/browse/SI-8377?orig=1 Reporter: Niklas Nebel (niklasn) Affected Versions: 2.10.3, 2.11.0-RC1

scabug commented 10 years ago

@retronym said: Recent mailing list thread discussing why an immediate (ie current thread) execution context can be problematic: https://groups.google.com/forum/#!msg/scala-user/lVFde4UyBvc/lml-BkIGQuYJ

scabug commented 10 years ago

@retronym said: /cc @viktorklang

scabug commented 10 years ago

Viktor Klang (viktorklang) said: implicit val immediateContext = new scala.concurrent.ExecutionContext { override def execute(runnable: Runnable) { runnable.run() } override def reportFailure(t: Throwable) { t.printStackTrace() } }

This is not a valid execution context. We will update the documentation to explain that ECs must be async to be general purpose—i.e. you can only use the construct above to execute your own code.

scabug commented 10 years ago

Viktor Klang (viktorklang) said: See this discussion for more background: https://groups.google.com/forum/#!searchin/scala-user/Future$20$26$20ExecutionContext/scala-user/LKiVD2PGvew/XDT-UZrAjJcJ

scabug commented 10 years ago

Viktor Klang (viktorklang) said: https://github.com/scala/scala/pull/3613

scabug commented 10 years ago

@nilskp said: Any chance this can make it into 2.10.4?

scabug commented 10 years ago

@retronym said: 2.10.4 final has already been cut. We'd accept a pull request that cherry-picks the doc fix to the 2.10.x branch. That would be included in 2.10.5.