This page is no longer maintained — Please continue to the home page at www.scala-lang.org

why has the blocked thread detection been removed from actors scheduler?

1 reply
Erik Engbrecht
Joined: 2008-12-19,
User offline. Last seen 3 years 18 weeks ago.
Back in 2.7.7 the FJTaskScheduler2 included code to detect when all threads are blocked and spawn new ones: http://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_7_7_final/src/actors/scala/actors/FJTaskScheduler2.scala
The logic is no longer in it's replacement.http://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/actors/scala/actors/scheduler/ForkJoinScheduler.scala
I did some poking around in the ForkJoinPool classes and couldn't find anything similar, but that doesn't mean it's not there.  ;-)
So, if it's moved, where is it?  And if it's gone, why?  It seems that without it receive-based actors could quickly consume the entire pool, leading to deadlock and/or starvation.
Philipp Haller
Joined: 2009-01-13,
User offline. Last seen 42 years 45 weeks ago.
Re: why has the blocked thread detection been removed from acto

Hi Erik,

Erik Engbrecht wrote:
> Back in 2.7.7 the FJTaskScheduler2 included code to detect when all
> threads are blocked and spawn new ones:
> http://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_7_7_final/src/a...
>
> The logic is no longer in it's replacement.
> http://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/actors/scala/a...
>
> I did some poking around in the ForkJoinPool classes and couldn't find
> anything similar, but that doesn't mean it's not there. ;-)
>
> So, if it's moved, where is it? And if it's gone, why? It seems that
> without it receive-based actors could quickly consume the entire pool,
> leading to deadlock and/or starvation.

The new resizing logic in `ForkJoinScheduler` builds on the interface
for "managed blocking" of `ForkJoinPool`. Managed blocking works as
follows. The scheduler contains the following method:

override def managedBlock(blocker: scala.concurrent.ManagedBlocker) {
ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker {
def block = blocker.block()
def isReleasable() = blocker.isReleasable
}, true)
}

This method is called whenever an actor invokes a blocking
`receive`-like operation. The `ManagedBlocker` for `receive` is
implemented as follows:

private object blocker extends scala.concurrent.ManagedBlocker {
def block() = {
Actor.this.suspendActor() // blocks underlying thread
true
}
def isReleasable =
!Actor.this.isSuspended
}

Basically, a `ManagedBlocker` allows the thread pool to decide when it
is safe to invoke the blocking operation, perhaps after increasing the
pool size. Furthermore, using the `isReleasable` method the pool can
test whether it's no longer necessary to block, in which case the pool
size can be re-adjusted to the state before the `ManagedBlocker` was
submitted.
The second Boolean parameter of the `ForkJoinPool.managedBlock` method
indicates whether the current level of parallelism should be maintained
or whether we only want to prevent starvation. In the former case
(true), a spare thread is activated if necessary to ensure a parallelism
level of "core pool size".

So, in the case of `receive`, `receiveWithin` etc. this logic prevents
deadlock and maintains a given number of non-blocked threads (defaulting
to core pool size). However, it will not spawn more than core pool size
threads if it's not absolutely necessary to achieve this. On the other
hand, managed blocking allows shrinking the pool size when additional
threads are no longer necessary.

Also, `managedBlock` is only called in blocking library methods (and
this is where the behavior differs from the 2.7.7 scheduler); other
blocking operations are not protected using `ManagedBlocker`s. I am
thinking about exposing `managedBlock` in some way to the user, so that
upon calling some blocking operation, she can tell the library "please
wrap this in a managed blocker and submit it to the pool".

Cheers,
Philipp

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland