Change docker lock to a fair lock
diff --git a/core/dispatcher/src/main/scala/whisk/core/container/ContainerPool.scala b/core/dispatcher/src/main/scala/whisk/core/container/ContainerPool.scala
index eacd592..dfc9da7 100644
--- a/core/dispatcher/src/main/scala/whisk/core/container/ContainerPool.scala
+++ b/core/dispatcher/src/main/scala/whisk/core/container/ContainerPool.scala
@@ -18,6 +18,7 @@
 
 import java.nio.file.Files
 import java.nio.file.Paths
+import java.util.concurrent.locks.ReentrantLock
 import java.util.Timer
 import java.util.TimerTask
 import java.util.concurrent.ConcurrentLinkedQueue
@@ -407,15 +408,15 @@
         makeContainerName(action.fullyQualifiedName)
 
     /**
-     * dockerLock is used to serialize all docker operations except pull.
+     * dockerLock is a fair lock used to serialize all docker operations except pull.
      * However, a non-pull operation can run concurrently with a pull operation.
      */
-    val dockerLock = new Object()
+    val dockerLock = new ReentrantLock(true)
 
     /**
      * dockerPullLock is used to serialize all pull operations.
      */
-    val dockerPullLock = new Object()
+    val dockerPullLock = new ReentrantLock(true)
 
     /* A background thread that
      *   1. Kills leftover action containers on startup
@@ -462,23 +463,29 @@
      * All docker operations from the pool must pass through here (except for pull).
      */
     private def runDockerOp[T](dockerOp: => T)(implicit transid: TransactionId): T = {
-        val (elapsed, result) = TimingUtil.time {
-            dockerLock.synchronized {
-                dockerOp
-            }
-        }
-        if (elapsed > slowDockerThreshold) {
-            warn(this, s"Docker operation took $elapsed")
-        }
-        result
+        runDockerOpWithLock(dockerLock, dockerOp)
     }
 
     /**
-     * All pull operations from the pool must pass through here.
+     * All docker pull operations from the pool must pass through here.
      */
-    private def runDockerPull[T](dockerOp: => T): T = {
-        dockerPullLock.synchronized {
-            dockerOp
+    private def runDockerPull[T](dockerOp: => T)(implicit transid: TransactionId): T = {
+        runDockerOpWithLock(dockerPullLock, dockerOp)
+    }
+
+    /**
+     * All docker operations from the pool must pass through here (except for pull).
+     */
+    private def runDockerOpWithLock[T](lock: ReentrantLock, dockerOp: => T)(implicit transid: TransactionId): T = {
+        lock.lock()
+        try {
+            val (elapsed, result) = TimingUtil.time { dockerOp }
+            if (elapsed > slowDockerThreshold) {
+                warn(this, s"Docker operation took $elapsed")
+            }
+            result
+        } finally {
+            lock.unlock()
         }
     }
 
@@ -607,6 +614,7 @@
     private val defaultMaxIdle = 10
     private val defaultGCThreshold = 600.seconds
     private val slowDockerThreshold = 500.millis
+    private val slowDockerPullThreshold = 5.seconds
 
     val gcFrequency = 1000.milliseconds // this should not be leaked but a test needs this until GC count is implemented
     private var _maxIdle = defaultMaxIdle