views:

434

answers:

2

I've written a Monte Carlo player for the board game Nine Men's Morris. Everything is basically immutable. The program involves lots of futures (hundreds) and a lot of modifying immutable Maps. Sometimes I get a crash with the following exception:

java.lang.NullPointerException
    at scala.collection.mutable.HashTable$class.elemHashCode(HashTable.scala:154)
    at scala.collection.immutable.HashMap.elemHashCode(HashMap.scala:41)
    at scala.collection.mutable.HashTable$class.findEntry(HashTable.scala:66)
    at scala.collection.immutable.HashMap.findEntry(HashMap.scala:41)
    at scala.collection.immutable.HashMap.undo$1(HashMap.scala:132)
    at scala.collection.immutable.HashMap.undo$1(HashMap.scala:130)
    at scala.collection.immutable.HashMap.makeCopy(HashMap.scala:154)
    at scala.collection.immutable.HashMap.makeCopyIfUpdated(HashMap.scala:161)
    at scala.collection.immutable.HashMap.update(HashMap.scala:66)
    at scala.collection.immutable.Map$class.$plus(Map.scala:66)
    at scala.collection.immutable.HashMap.$plus(HashMap.scala:41)
    at morris.players.MapBasedMorrisBoard.applyMove(MapBasedMorrisBoard.scala:30)
    at morris.players.MonteCarloPlayer$$anonfun$main$1$$anonfun$apply$1.apply(MonteCarloPlayer.scala:77)
    at morris.players.MonteCarloPlayer$$anonfun$main$1$$anonfun$apply$1.apply(MonteCarloPlayer.scala:77)
    at scala.actors.Futures$$anonfun$2$$anonfun$apply$1.apply(Future.scala:45)
    at scala.actors.Futures$$anonfun$2$$anonfun$apply$1.apply(Future.scala:44)
    at scala.actors.Reaction.run(Reaction.scala:78)
    at scala.actors.FJTask$Wrap.run(Unknown Source)
    at scala.actors.FJTaskRunner.scanWhileIdling(Unknown Source)
    at scala.actors.FJTaskRunner.run(Unknown Source)

I'm only using immutable Maps, so I wonder whether this is caused by a bug in my own code or maybe a bug in the scala library. When looking at the trace you can see, that there are calls to mutable HashTable further down the stack. Maybe this is causing problems with concurrency?

The code inside my program, where the exception occurs is just adding another collection to an immutable Map:

myMap ++ (someInteger -> aValue)

Edit: The same program without concurrency runs flawlessly.

A: 

It can certainly cause problems with concurrency. Mix Map with SynchronizedMap, at the very least.

But note that doesn't give you any transactional guarantees. It just ensures that the Map won't break under your feet.

Daniel
Doing this:new scala.collection.immutable.HashMap[Int,MorrisColor.Value]() with SynchronizedMap[Int,MorrisColor.Value]yields an error: (error overriding method excl in ...) any idea?
ziggystar
And the SynchronizedMap trait is in package mutable, while I am using Maps from immutable. You're sure this is something sane to do? Do I really have to mix in SynchronizedMap into an immutable type?
ziggystar
@ziggystar - no, I don't believe that you should have to do this at all. Immutable map does use synchronization internally, oddly enough, but "clients" should not need to care about it
oxbow_lakes
Ziggy, you said "I'm only using mutable Maps", so I assumed you were using mutable maps. :-) No, for immutable you shouldn't have to do anything -- but there might be a bug indeed. What is the Scala version you are using?
Daniel
@Daniel Sorry, that was a typo. I'm only using immutable Map as can be seen in the stack-trace. This bug is happening with 2.7.7
ziggystar
Well, you might want to try running the program on a Scala 2.8 -- the RC4 for Beta1 has just been made available. Scala 2.8 has tons of fixes over Scala 2.7.
Daniel
+2  A: 

I've filed a bug report for the Scala-library. As it turns out this is a known problem. The implementation of HashMap (which is used for as the standard Map type in Scala) is not suited to be used in concurrent programs since behind the scenes, it uses mutable types. This can also be observed in the stack-trace. The Scala people are hoping to replace the implementation in 2.8.

As a work-around it is suggested to use TreeHashMap, which is truly immutable. I've done this and can confirm that it works.

Link to original bug report

ziggystar