Skip to content

Instantly share code, notes, and snippets.

@Andrei-Pozolotin
Last active January 26, 2019 17:14
Show Gist options
  • Save Andrei-Pozolotin/3089dc50fe20f18fc2c5d29eb001e81c to your computer and use it in GitHub Desktop.
Save Andrei-Pozolotin/3089dc50fe20f18fc2c5d29eb001e81c to your computer and use it in GitHub Desktop.
https://github.com/scala/bug/issues/10587
the rationale is:
- `ForkJoinPool` and `ExecutionContext.global` behave differently
- `ForkJoinPool` threads terminate upon interrupt
- `ExecutionContext.global` threads stay alive upon interrupt
for `ForkJoinPool` see: main1.Main.scala
for `ExecutionContext.global` see: main2.Main.scala
run...
thread=Thread[ForkJoinPool-1-worker-1,5,main] isAlive=true isDaemon=true
Exception in thread "ForkJoinPool-1-worker-1" java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at main1.Main$$anon$1.run(Main.scala:14)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
thread=Thread[ForkJoinPool-1-worker-1,5,] isAlive=false isDaemon=true
package main1
import java.util.concurrent.ForkJoinPool
object Main {
def main(args: Array[String]): Unit = {
val forkJoinPool = new ForkJoinPool(3);
var thread: Thread = null
def report() = {
println(s"thread=$thread isAlive=${thread.isAlive()} isDaemon=${thread.isDaemon()}")
}
val task = new Runnable() {
def run() = {
println("run...")
thread = Thread.currentThread()
Thread.sleep(1000 * 1000)
}
}
forkJoinPool.execute(task)
Thread.sleep(1000)
report()
thread.interrupt();
Thread.sleep(1000)
report()
Thread.sleep(1000)
}
}
run...
thread=Thread[scala-execution-context-global-21,5,main] isAlive=true isDaemon=true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at main2.Main$$anon$1.run(Main.scala:15)
at main2.Main$.$anonfun$main$1(Main.scala:19)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:653)
at scala.util.Success.$anonfun$map$1(Try.scala:251)
at scala.util.Success.map(Try.scala:209)
at scala.concurrent.Future.$anonfun$map$1(Future.scala:287)
at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:29)
at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:29)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at scala.concurrent.impl.ExecutionContextImpl$AdaptedForkJoinTask.exec(ExecutionContextImpl.scala:140)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
thread=Thread[scala-execution-context-global-21,5,main] isAlive=true isDaemon=true
package main2
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
object Main {
implicit val ec = ExecutionContext.global
def main(args: Array[String]): Unit = {
var thread: Thread = null
def report() = {
println(s"thread=$thread isAlive=${thread.isAlive()} isDaemon=${thread.isDaemon()}")
}
val task = new Runnable() {
def run() = {
println("run...")
thread = Thread.currentThread()
Thread.sleep(1000 * 1000)
}
}
Future {
task.run()
}
Thread.sleep(1000)
report()
thread.interrupt();
Thread.sleep(1000)
report()
Thread.sleep(1000)
}
}
@jhalterman
Copy link

jhalterman commented Jan 26, 2019

Interestingly, running a similar test in Java, the ForkJoinPool thread is kept alive:

    ExecutorService executorService = new ForkJoinPool(3);
    AtomicReference<Thread> thread = new AtomicReference<>();
    Runnable report = () -> {
      Thread t = thread.get();
      System.out.println(
          "thread=" + thread.get() + " isAlive=" + t.isAlive() + " isDaemon=" + t.isDaemon() + " isInterrupted="
              + t.isInterrupted());
    };

    executorService.submit(() -> {
      System.out.println("run...");
      thread.set(Thread.currentThread());
      try {
        Thread.sleep(1000 * 1000);
      } catch (Exception e) {
        System.out.println("Caught exception " + e);
        throw e;
      }
      return null;
    });

    Thread.sleep(1000);
    report.run();
    thread.get().interrupt();
    Thread.sleep(1000);
    report.run();
    Thread.sleep(1000);

In this case I'm using Executor.submit(Callable) so that the checked exceptions from Thread.sleep do not need to be handled. Prints:

run...
thread=Thread[ForkJoinPool-1-worker-1,5,main] isAlive=true isDaemon=true isInterrupted=false
Caught exception java.lang.InterruptedException: sleep interrupted
thread=Thread[ForkJoinPool-1-worker-1,5,main] isAlive=true isDaemon=true isInterrupted=false

I'd be interested to know why the discrepancy.

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