devshorts devshorts - 2 months ago 37
Scala Question

Deadlock with synchronized and scala futures

I can't seem to figure out why the following code deadlocks:

"Locks around blocking futures" should "be re-entrant" in {
val lock = new Object()

def processInFuture = {
lock.synchronized {
// simulate async call made blocking
Await.result(Future {
blocking {
logger.info("Sleeping")
Thread.sleep(100)
logger.info("Waking")
}
}, Duration.Inf)
}
}

// fire off 10 async events and wait on each one
(0 until 10).
map(_ => Future { processInFuture }).
foreach(future => Await.result(future, Duration.Inf))
}


I can't see why making an async into sync in a critical section causes the whole fork-join pool to bork.

If I make the future execute on a separate pool from the fork join pool then it works. I don't understand why the fork join pool thread doesn't block the other threads and then finish first? Is it because the pool is somehow blocked?

I realize its always best to make everything async if its async, but some scenarios don't allow for that (for example cache population with a guava cache)

Answer

The maximum number of threads in the default pool is equal to a number of cores you have. Say, 8. Here is what happens.

The first thread enters the critical section and starts sleeping (occupying another thread). Six more threads are started and pile up at the entrance of synchronized block. (There are still three more calls to start, but there are no threads available at the moment, so they are waiting).

The sleeping thread wakes up, satisfies that future, and the first thread exits synchronized block and finishes. The two threads are returned to the pool, so two more futures are immediately started.

One of the threads waiting for the lock wakes up and enters critical section.

Now, there are no threads available in the pool: 7 threads are waiting for the lock, and one is inside the critical section, for the total of 8.

The active thread tries to submit the Future, that will sleep, but gets blocked, because no more threads are available in the pool.

Now, 7 threads a waiting for the lock to be released, and the one holding the lock is waiting for another thread to become available.

--> Deadlock.

Update Forgot to mention this: the way to fix it is to put blocking around the processInFuture call. That will let the pool realize it needs to go beyond the limit on threads.