├── .gitignore ├── README.textile ├── project ├── build.properties └── build │ └── AtomicMapProject.scala └── src ├── main └── scala │ └── atomicmap │ └── .gitignore └── test └── scala └── atomicmap └── AtomicMapSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | project/boot/* 2 | project/build/target/* 3 | target/* 4 | lib_managed/* 5 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Boundary's AtomicMap Challenge 2 | 3 | In the course of developing our database we have come across surprising multithreaded behavior in Scala's ConcurrentMap trait. Java and Scala provide a ConcurrentMap "interface":http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ConcurrentMap.html and "trait":http://www.scala-lang.org/api/current/scala/collection/mutable/ConcurrentMap.html, respectively. Both trait and interface define a number of atomic, or transactional, operations that help developers deal with concurrent modification of the map. The difference between the Scala trait and the Java interface is that the Scala trait inherits a number of operations that are not atomic. 4 | 5 | In particular, Scala's ConcurrentMap trait does not provide an atomic implementation of getOrElseUpdate. getOrElseUpdate lazily evaluates its second argument, allowing the user to forgo evaluation if the key already exists in the map. In a multithreaded environment, however, a highly contended map key is likely to cause multiple evaluations of the value. This is problematic behavior if the evaluation is resource intensive or has some side effects which should only occur once per key. 6 | 7 | Your challenge is to provide an atomic implementation of getOrElseUpdate. It must guarantee that the value for any particular key will only get evaluated once, regardless of how many threads are contending for the same key. We have provided a "github repo":https://github.com/boundary/atomicmap_challenge with a spec for the correct behavior of a ConcurrentMap. Fork this repo, provide a ConcurrentMap implementation that passes the spec, and send a repo link to challenge@boundary.com with the subject line "AtomicMap Challenge". We will be publishing a breakdown and benchmark of the best implementations along with our own. Happy hacking! -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | #Project properties 2 | #Mon Apr 18 09:01:22 PDT 2011 3 | project.organization=com.boundary 4 | project.name=atomicmap 5 | sbt.version=0.7.5.RC0 6 | project.version=0.1 7 | build.scala.versions=2.8.1 8 | project.initialize=false 9 | -------------------------------------------------------------------------------- /project/build/AtomicMapProject.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.StringUtilities._ 3 | 4 | class AtomicMapProject(info : ProjectInfo) extends DefaultProject(info) { 5 | 6 | val specs = "org.scala-tools.testing" %% "specs" % "1.6.7" % "test" 7 | } -------------------------------------------------------------------------------- /src/main/scala/atomicmap/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundary/atomicmap_challenge/d765a99b64e6ac99569ff82fd02d76201006d40c/src/main/scala/atomicmap/.gitignore -------------------------------------------------------------------------------- /src/test/scala/atomicmap/AtomicMapSpec.scala: -------------------------------------------------------------------------------- 1 | package atomicmap 2 | 3 | import org.specs._ 4 | import scala.collection.mutable.ConcurrentMap 5 | import java.util.concurrent.atomic._ 6 | 7 | /** 8 | * Use this as a base for your test. You need merely override createMap[A,B] to create 9 | * a ConcurrentMap[A,B] with the correct behavior. 10 | */ 11 | abstract class AtomicMapSpec extends Specification { 12 | def createMap[A,B] : ConcurrentMap[A,B] 13 | 14 | "AtomicMap" should { 15 | val map = createMap[String,Int] 16 | 17 | "be a full concurrentmap implementation" in { 18 | "getOrElseUpdate" in { 19 | map.getOrElseUpdate("blah", 1) must ==(1) 20 | map.getOrElseUpdate("blah", 2) must ==(1) 21 | } 22 | 23 | "replace(k,v)" in { 24 | map.replace("blah", 1) must beNone 25 | map.put("blah", 1) 26 | map.replace("blah", 2) must beSome(1) 27 | } 28 | 29 | "replace(k, o, n)" in { 30 | map.replace("blah", 2, 1) must ==(false) 31 | map.put("blah", 1) 32 | map.replace("blah", 1, 2) must ==(true) 33 | map("blah") must ==(2) 34 | } 35 | 36 | "remove" in { 37 | map.put("blah", 1) 38 | map.remove("blah", 2) must ==(false) 39 | map.remove("blah", 1) must ==(true) 40 | map.get("blah") must beNone 41 | } 42 | 43 | "putIfAbsent" in { 44 | map.putIfAbsent("blah", 1) must beNone 45 | map.putIfAbsent("blah", 2) must beSome(1) 46 | map.putIfAbsent("derp", 3) must beNone 47 | } 48 | 49 | "-=" in { 50 | map.put("herp", 1) 51 | map -= "herp" 52 | map.get("herp") must beNone 53 | } 54 | 55 | "+=" in { 56 | map += (("herp", 1)) 57 | map.get("herp") must beSome(1) 58 | } 59 | 60 | "iterator" in { 61 | map.put("herp", 1) 62 | map.put("derp", 2) 63 | 64 | val otherMap = createMap[String,Int] 65 | 66 | for ((key,value) <- map) { 67 | otherMap.put(key,value) 68 | } 69 | 70 | otherMap.get("herp") must beSome(1) 71 | otherMap.get("derp") must beSome(2) 72 | } 73 | 74 | "get" in { 75 | map.put("herp", 5) 76 | map.get("herp") must beSome(5) 77 | } 78 | } 79 | 80 | "evaluate op only once" in { 81 | val counter = new AtomicInteger(0) 82 | 83 | val threads = for (i <- (0 to 5)) yield { 84 | new Thread { 85 | override def run { 86 | map.getOrElseUpdate("blah", {Thread.sleep(100); counter.incrementAndGet}) 87 | } 88 | } 89 | } 90 | threads.foreach(_.start) 91 | threads.foreach(_.join) 92 | map("blah") must ==(1) 93 | counter.get must ==(1) 94 | } 95 | } 96 | } --------------------------------------------------------------------------------