On page 125 you demo cancellation future.
What is the difference between the following two code snippets:
object PromisesAndTimers extends App {
implicit class FutureOps[T](val self: Future[T]) {
def or(that: Future[T]): Future[T] = {
val p = Promise[T]
self onComplete { case x => println(x); p tryComplete x }
that onComplete { case y => println(y); p tryComplete y }
p.future
}
}
{ //[1]
println("Start...")
val f = timeout(1000).map(_ => "[1] timeout!") or Future {
Thread.sleep(999)
"[1] work completed!"
}
f foreach { case text => log(text) }
}
{ //[2]
println("Start...")
// `f0` gets completed with `()` after 1sec
val f0 = timeout(1000)
// `f1` gets completed with "timeout" after
// the mapping over the completed `f0`
val f1 = f0.map(_ => "[2] timeout!")
// `f2` gets completed with "work completed" after 999ms
val f2 = Future {
Thread.sleep(999)
"[2] work completed!"
}
(f1 or f2) foreach { case text => log(text) }
}
Thread.sleep(2000)
}
I increased the last sleep to 2000 so that I can see that eventually all threads are completed --- added printlns to or combinator.
So my questions:
Are comments in [2] correct?
Yes.
I ran PromiseAndTimer several times directly after one another, and each time [1] prints “timeout” and [2] prints “work completed”. Why, in case of [1], is 999 not enough — although this is your original example?
My guess is that this is a combination of your particular processor type, OS and the JVM version. I get pretty non-deterministic results:
> runMain org.learningconcurrency.ch4.PromisesAndTimers
[info] Compiling 1 Scala source to
C:\cygwin\home\axel22\workspaces\scala\learning-examples\target\scala-2.11\classes...
[info] Running org.learningconcurrency.ch4.PromisesAndTimers
ForkJoinPool-1-worker-5: [1] timeout!
Start...
ForkJoinPool-1-worker-7: [2] work completed!
[success] Total time: 5 s, completed May 21, 2015 10:51:26 PM
> runMain org.learningconcurrency.ch4.PromisesAndTimers
[info] Running org.learningconcurrency.ch4.PromisesAndTimers
ForkJoinPool-1-worker-15: [1] work completed!
Start...
ForkJoinPool-1-worker-7: [2] timeout!
[success] Total time: 4 s, completed May 21, 2015 10:51:31 PM
> runMain org.learningconcurrency.ch4.PromisesAndTimers
[info] Running org.learningconcurrency.ch4.PromisesAndTimers
ForkJoinPool-1-worker-9: [1] work completed!
Start...
ForkJoinPool-1-worker-15: [2] timeout!
[success] Total time: 4 s, completed May 21, 2015 10:51:36 PM
> runMain org.learningconcurrency.ch4.PromisesAndTimers
[info] Running org.learningconcurrency.ch4.PromisesAndTimers
ForkJoinPool-1-worker-7: [1] work completed!
Start...
ForkJoinPool-1-worker-15: [2] timeout!
[success] Total time: 4 s, completed May 21, 2015 10:51:41 PM
> runMain org.learningconcurrency.ch4.PromisesAndTimers
[info] Running org.learningconcurrency.ch4.PromisesAndTimers
ForkJoinPool-1-worker-5: [1] timeout!
Start...
ForkJoinPool-1-worker-15: [2] timeout!
[success] Total time: 4 s, completed May 21, 2015 10:51:46 PM
> runMain org.learningconcurrency.ch4.PromisesAndTimers
[info] Running org.learningconcurrency.ch4.PromisesAndTimers
ForkJoinPool-1-worker-7: [1] work completed!
Start...
ForkJoinPool-1-worker-15: [2] timeout!
[success] Total time: 4 s, completed May 21, 2015 10:51:50 PM
> runMain org.learningconcurrency.ch4.PromisesAndTimers
[info] Running org.learningconcurrency.ch4.PromisesAndTimers
ForkJoinPool-1-worker-5: [1] work completed!
Start...
ForkJoinPool-1-worker-7: [2] timeout!
[success] Total time: 4 s, completed May 21, 2015 10:51:54 PM
> runMain org.learningconcurrency.ch4.PromisesAndTimers
[info] Running org.learningconcurrency.ch4.PromisesAndTimers
ForkJoinPool-1-worker-15: [1] work completed!
Start...
ForkJoinPool-1-worker-5: [2] work completed!
[success] Total time: 4 s, completed May 21, 2015 10:51:59 PM
> runMain org.learningconcurrency.ch4.PromisesAndTimers
[info] Running org.learningconcurrency.ch4.PromisesAndTimers
ForkJoinPool-1-worker-15: [1] work completed!
Start...
ForkJoinPool-1-worker-7: [2] timeout!
[success] Total time: 4 s, completed May 21, 2015 10:52:03 PM
Code I used:
object PromisesAndTimers extends App {
import java.util._
import scala.concurrent._
import ExecutionContext.Implicits.global
import PromisesAndCustomOperations._
private val timer = new Timer(true)
def timeout(millis: Long): Future[Unit] = {
val p = Promise[Unit]
timer.schedule(new TimerTask {
def run() = p.success(())
}, millis)
p.future
}
val f = timeout(1000).map(_ => "[1] timeout!") or Future {
Thread.sleep(999)
"[1] work completed!"
}
f foreach {
case text => log(text)
}
Thread.sleep(2000)
{ //[2]
println("Start...")
// `f0` gets completed with `()` after 1sec
val f0 = timeout(1000)
// `f1` gets completed with "timeout" after
// the mapping over the completed `f0`
val f1 = f0.map(_ => "[2] timeout!")
// `f2` gets completed with "work completed" after 999ms
val f2 = Future {
Thread.sleep(999)
"[2] work completed!"
}
(f1 or f2) foreach { case text => log(text) }
}
Thread.sleep(2000)
}
Decreasing to 900 yields “work completed” in the foreach, though.
What is the difference between [1] and [2]?
There should be no difference as far as I can tell. This might be a particular artifact of how JIT optimizes code, or maybe the artifact of how OS schedules threads. The fact that I cannot reproduce it is in favour.
Just to be sure, I inspected the bytecode -- you can see below that in both cases, the sequence of operations is:
- timeout
- map
- Future.apply
- or
public final void
delayedEndpoint$org$learningconcurrency$ch4$PromisesAndTimers$1();
flags: ACC_PUBLIC, ACC_FINAL
LineNumberTable:
line 126: 0
line 136: 12
line 141: 69
line 145: 91
line 149: 97
line 152: 105
line 156: 113
line 159: 133
line 164: 153
line 168: 182
LocalVariableTable:
Start Length Slot Name Signature
0 189 0 this
Lorg/learningconcurrency/ch4/PromisesAndTimers$;
113 69 1 f0 Lscala/concurrent/Future;
133 49 2 f1 Lscala/concurrent/Future;
153 29 3 f2 Lscala/concurrent/Future;
Code:
stack=5, locals=4, args_size=1
0: aload_0
1: new #83 // class java/util/Timer
4: dup
5: iconst_1
6: invokespecial #102 // Method
java/util/Timer."<init>":(Z)V
9: putfield #63 // Field
timer:Ljava/util/Timer;
12: aload_0
13: getstatic #107 // Field
org/learningconcurrency/ch4/PromisesAndCustomOperations$.MODULE$:Lorg/learningconcurrency/ch4/PromisesAndCustomOperations$;
16: aload_0
17: ldc2_w #108 // long 1000l
20: invokevirtual #111 // Method
timeout:(J)Lscala/concurrent/Future;
23: new #113 // class
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$9
26: dup
27: invokespecial #114 // Method
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$9."<init>":()V
30: getstatic #119 // Field
scala/concurrent/ExecutionContext$Implicits$.MODULE$:Lscala/concurrent/ExecutionContext$Implicits$;
33: invokevirtual #123 // Method
scala/concurrent/ExecutionContext$Implicits$.global:()Lscala/concurrent/ExecutionContextExecutor;
36: invokeinterface #129, 3 // InterfaceMethod
scala/concurrent/Future.map:(Lscala/Function1;Lscala/concurrent/ExecutionContext;)Lscala/concurrent/Future;
41: invokevirtual #133 // Method
org/learningconcurrency/ch4/PromisesAndCustomOperations$.FutureOps:(Lscala/concurrent/Future;)Lorg/learningconcurrency/ch4/PromisesAndCustomOperations$FutureOps;
44: getstatic #138 // Field
scala/concurrent/Future$.MODULE$:Lscala/concurrent/Future$;
47: new #140 // class
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$10
50: dup
51: invokespecial #141 // Method
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$10."<init>":()V
54: getstatic #119 // Field
scala/concurrent/ExecutionContext$Implicits$.MODULE$:Lscala/concurrent/ExecutionContext$Implicits$;
57: invokevirtual #123 // Method
scala/concurrent/ExecutionContext$Implicits$.global:()Lscala/concurrent/ExecutionContextExecutor;
60: invokevirtual #144 // Method
scala/concurrent/Future$.apply:(Lscala/Function0;Lscala/concurrent/ExecutionContext;)Lscala/concurrent/Future;
63: invokevirtual #150 // Method
org/learningconcurrency/ch4/PromisesAndCustomOperations$FutureOps.or:(Lscala/concurrent/Future;)Lscala/concurrent/Future;
66: putfield #98 // Field
f:Lscala/concurrent/Future;
69: aload_0
70: invokevirtual #152 // Method
f:()Lscala/concurrent/Future;
73: new #154 // class
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$11
76: dup
77: invokespecial #155 // Method
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$11."<init>":()V
80: getstatic #119 // Field
scala/concurrent/ExecutionContext$Implicits$.MODULE$:Lscala/concurrent/ExecutionContext$Implicits$;
83: invokevirtual #123 // Method
scala/concurrent/ExecutionContext$Implicits$.global:()Lscala/concurrent/ExecutionContextExecutor;
86: invokeinterface #159, 3 // InterfaceMethod
scala/concurrent/Future.foreach:(Lscala/Function1;Lscala/concurrent/ExecutionContext;)V
91: ldc2_w #160 // long 2000l
94: invokestatic #166 // Method
java/lang/Thread.sleep:(J)V
97: getstatic #171 // Field
scala/Predef$.MODULE$:Lscala/Predef$;
100: ldc #173 // String Start...
102: invokevirtual #177 // Method
scala/Predef$.println:(Ljava/lang/Object;)V
105: aload_0
106: ldc2_w #108 // long 1000l
109: invokevirtual #111 // Method
timeout:(J)Lscala/concurrent/Future;
112: astore_1
113: aload_1
114: new #179 // class
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$12
117: dup
118: invokespecial #180 // Method
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$12."<init>":()V
121: getstatic #119 // Field
scala/concurrent/ExecutionContext$Implicits$.MODULE$:Lscala/concurrent/ExecutionContext$Implicits$;
124: invokevirtual #123 // Method
scala/concurrent/ExecutionContext$Implicits$.global:()Lscala/concurrent/ExecutionContextExecutor;
127: invokeinterface #129, 3 // InterfaceMethod
scala/concurrent/Future.map:(Lscala/Function1;Lscala/concurrent/ExecutionContext;)Lscala/concurrent/Future;
132: astore_2
133: getstatic #138 // Field
scala/concurrent/Future$.MODULE$:Lscala/concurrent/Future$;
136: new #182 // class
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$13
139: dup
140: invokespecial #183 // Method
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$13."<init>":()V
143: getstatic #119 // Field
scala/concurrent/ExecutionContext$Implicits$.MODULE$:Lscala/concurrent/ExecutionContext$Implicits$;
146: invokevirtual #123 // Method
scala/concurrent/ExecutionContext$Implicits$.global:()Lscala/concurrent/ExecutionContextExecutor;
149: invokevirtual #144 // Method
scala/concurrent/Future$.apply:(Lscala/Function0;Lscala/concurrent/ExecutionContext;)Lscala/concurrent/Future;
152: astore_3
153: getstatic #107 // Field
org/learningconcurrency/ch4/PromisesAndCustomOperations$.MODULE$:Lorg/learningconcurrency/ch4/PromisesAndCustomOperations$;
156: aload_2
157: invokevirtual #133 // Method
org/learningconcurrency/ch4/PromisesAndCustomOperations$.FutureOps:(Lscala/concurrent/Future;)Lorg/learningconcurrency/ch4/PromisesAndCustomOperations$FutureOps;
160: aload_3
161: invokevirtual #150 // Method
org/learningconcurrency/ch4/PromisesAndCustomOperations$FutureOps.or:(Lscala/concurrent/Future;)Lscala/concurrent/Future;
164: new #185 // class
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$14
167: dup
168: invokespecial #186 // Method
org/learningconcurrency/ch4/PromisesAndTimers$$anonfun$14."<init>":()V
171: getstatic #119 // Field
scala/concurrent/ExecutionContext$Implicits$.MODULE$:Lscala/concurrent/ExecutionContext$Implicits$;
174: invokevirtual #123 // Method
scala/concurrent/ExecutionContext$Implicits$.global:()Lscala/concurrent/ExecutionContextExecutor;
177: invokeinterface #159, 3 // InterfaceMethod
scala/concurrent/Future.foreach:(Lscala/Function1;Lscala/concurrent/ExecutionContext;)V
182: ldc2_w #160 // long 2000l
185: invokestatic #166 // Method
java/lang/Thread.sleep:(J)V
188: return
So, there should really be no difference. Try inverting the order of [1] and [2] to see if you get an opposite effect.
FYI, in case of [2] I separated the futures to make clear that the or combinator actually is operating on f1 and f2 and to make clear that actually three futures are created (f0, f1, and f2) --- neglecting the one created by the or combinator`.
Sure.
The difference might be more pronounced if the or
operator were to take a by-name parameter. In this case f2
would be evaluated only after or
is invoked in [1], but earlier in [2]. Which is, in this case, not a huge time difference, but might influence the execution schedule somewhat.