Reputation: 117
My question is probably vague (could not think of how to describe it well) but hopefully this example will make things more clear:
class IntTestFake extends FunSpec with ScalaFutures {
describe("This"){
it("Fails for some reason"){
var a = "Chicken"
val b = "Steak"
def timeout() = Future{
while(a != b){}
}
Future{
Thread.sleep(3000)
a = b
}
whenReady(timeout(), Timeout(20 seconds), Interval(50 milliseconds))(result => result)
}
it("Passes...why?!?"){
var a = "Chicken"
val b = "Steak"
def timeout() = Future{
while(a != b){
println("this works...")
}
}
Future{
Thread.sleep(3000)
a = b
}
whenReady(timeout(), Timeout(20 seconds), Interval(50 milliseconds))(result => result)
}
}
}
In the first test (Fails for some reason
) the while loop has an empty body. In the second test (Passes...why?!?
) the while loop body has a println statement in it. My original thought was garbage collection was doing something funky but with that whenReady
statement I am expecting something to return so I would expect GC to leave it alone until then. Apologies if this has already been asked I could not find an example.
Upvotes: 1
Views: 324
Reputation: 40500
a
needs to be @volatile
, without it writes from other threads are not guaranteed to be visible to the current thread, until it hits a "memory barrier" (a special point in the code, where all caches are flashed - in a conceptual sense as pointed out in the comment, not necessarily mapping directly to how exactly hardware off a particular cpu handles that). This is why the second case works - there's plenty of memory barriers inside a println
call.
So, changing var a ...
to @volatile var a ...
will make it work ... but, seriously, don't use vars
. At least, not until you have learned enough scala to be able to recognize the cases where you have to have them.
Upvotes: 0
Reputation: 27356
The problem is that the code is reading a var
from two threads without warning the compiler that it is going to do this, and this leads to unpredictable behaviour. The compiler does not know that the value of a
is going to change under its feet, so it is perfectly allowed to cache that value in a register or some other bit of memory. If it does, that while
loop is going to spin forever.
It happens that your first test fails and the second succeeds, but this is a result of the particular compiler and scheduler that you are using, and could be different on a different system.
The solution is to avoid using a shared variable and use a proper synchronisation mechanism. In this case, a Promise
would probably do the trick.
Upvotes: 3