SML
SML

Reputation: 117

Empty while loop in Future causes Future to never return in Scala

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

Answers (2)

Dima
Dima

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

Tim
Tim

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

Related Questions