Future & ExecutionContext

1,330 views
Skip to first unread message

Bruno Bieth

unread,
Feb 4, 2014, 12:26:25 PM2/4/14
to scala...@googlegroups.com
I've been using scala.concurrent for a bit and I've been hitting some problems that I would like to share...

I've been reading those 2 pages
  * SIP: http://docs.scala-lang.org/sips/completed/futures-promises.html
  * Current doc: http://docs.scala-lang.org/overviews/core/futures.html

The SIP speaks about a 2nd SIP that would address ExecutionContext, but it apparently got never written. ExecutionContext are equally absent from the current doc.
This is really unfortunate in my opinion as ExecutionContexts are a crucial part of the future API.

From the current documentation:

  1. import scala.concurrent._
  2. import ExecutionContext.Implicits.global

  3. val session = socialNetwork.createSessionFor("user", credentials)
  4. val f: Future[List[Friend]] = future {
  5. session.getFriends()
  6. }

Assuming `session.getFriends()` is blocking, it seems to me that the global execution context, being backed by a ForkJoinPool, should not be used here.
Running this on a machine with one processor would lead to situations like:

    implicit val ec = ExecutionContext.fromExecutor( new ForkJoinPool(1))

    val f1 = Future {
      println("I'm blocking")
      Thread.sleep(99999)
    }

    val f2 = Future {
      println("But not me")
    }

    val f3 = Future {
      println("And me neither... too bad :(")
    }

    Await.ready(Future.sequence(Seq(f1, f2, f3)), 1.hour)



The scaladoc is also quite terse on the matter:


object ExecutionContext {
  /**
   * This is the explicit global ExecutionContext,
   * call this when you want to provide the global ExecutionContext explicitly
   */
  def global: ExecutionContextExecutor = Implicits.global

  object Implicits {
    /**
     * This is the implicit global ExecutionContext,
     * import this when you want to provide the global ExecutionContext implicitly
     */
    implicit lazy val global: ExecutionContextExecutor = impl.ExecutionContextImpl.fromExecutor(null: Executor)
  }



I guess to properly implement the example from the documentation, one should write something like that:

    val ioEc = ExecutionContext.fromExecutor( buildThreadPoolExecutorWithSensibleSettingsForIO )

    val session = socialNetwork.createSessionFor("user", credentials)
    val f: Future[List[Friend]] = Future {
      session.getFriends()
    }(ioEc)


But then if I want to map something to my future, and the mapping operation is not blocking, maybe I can use the global context:

   f.map( someNonBlockingOperation )(ExecutionContext.global)

Which makes me think, if `someNonBlockingOperation` is something really cheap, like wrapping into a tuple, isn't it overkill to send that to the FJ pool, with all the thread context switch overhead? Maybe we'd like to execute that in the future's thread, the IO thread (or the main thread if the future has completed).
That make me write stuff like:

  object CurrentThreadExecutor extends Executor{
    def execute(command: Runnable) {
      command.run()
    }
  }

  object ConcurrencyUtil {
      val currentThreadExecutionContext = ExecutionContext.fromExecutor(CurrentThreadExecutor)
  }

So that I can

  f.map( someCheapNonBlockingOperation )(ConcurrencyUtil.currentThreadExecutionContext)


Like Paul Chiusano in https://issues.scala-lang.org/browse/SI-6932 I'm wondering why map, flatMap, recover and the like take another ExecutionContext.
Viktor Klang says "that we consider that to be a disadvantage, i.e. the programmer has to make choices about execution rather than flow." But if you don't make choices about execution you'll end up using the wrong execution context for the task.
In any case, that kind of decision should be documented somewhere, along with, why ExecutionContext is passed implicitly?

Maybe i've missed something, but this whole concurrent package has been written as if there is one ExecutionContext that rule them all. Don't we need different ExecutionContexts for different tasks?

Then there's the bit about ExecutionContext.prepare. The scaladoc is not helping so much:

  /** Prepares for the execution of a task. Returns the prepared
   *  execution context. A valid implementation of `prepare` is one
   *  that simply returns `this`.
   */
  def prepare(): ExecutionContext = this








√iktor Ҡlang

unread,
Feb 5, 2014, 7:28:13 AM2/5/14
to Bruno Bieth, scala-user
Hi Bruno!

Great to see this email, we're currently curating the second part of that SIP which will address ExecutionContext in detail.
Please watch the scala-sips ML.


On Tue, Feb 4, 2014 at 6:26 PM, Bruno Bieth <bie...@gmail.com> wrote:
I've been using scala.concurrent for a bit and I've been hitting some problems that I would like to share...

I've been reading those 2 pages
  * SIP: http://docs.scala-lang.org/sips/completed/futures-promises.html
  * Current doc: http://docs.scala-lang.org/overviews/core/futures.html

The SIP speaks about a 2nd SIP that would address ExecutionContext, but it apparently got never written. ExecutionContext are equally absent from the current doc.
This is really unfortunate in my opinion as ExecutionContexts are a crucial part of the future API.

From the current documentation:

  1. import scala.concurrent._
  2. import ExecutionContext.Implicits.global

  3. val session = socialNetwork.createSessionFor("user", credentials)
  4. val f: Future[List[Friend]] = future {
  5. session.getFriends()
  6. }

Assuming `session.getFriends()` is blocking, it seems to me that the global execution context, being backed by a ForkJoinPool, should not be used here.

Why?
 

Running this on a machine with one processor would lead to situations like:

    implicit val ec = ExecutionContext.fromExecutor( new ForkJoinPool(1))

    val f1 = Future {
      println("I'm blocking")
      Thread.sleep(99999)
    }

    val f2 = Future {
      println("But not me")
    }

    val f3 = Future {
      println("And me neither... too bad :(")
    }

    Await.ready(Future.sequence(Seq(f1, f2, f3)), 1.hour)



The scaladoc is also quite terse on the matter:

use scala.concurrent.blocking { Thread.sleep(99999) }
Or even better, don't use Thread.sleep. Ever.
 

So that I can

  f.map( someCheapNonBlockingOperation )(ConcurrencyUtil.currentThreadExecutionContext)


Like Paul Chiusano in https://issues.scala-lang.org/browse/SI-6932 I'm wondering why map, flatMap, recover and the like take another ExecutionContext.

Because Futures are strict—they are not a lifted expression of operations. User code shall not ever run on the thread of the completer, as this introduces weird situations where it is impossible to figure out where things are running (non-determinisim due to the race between completion and attaching of action). For a longer discussion on this, see http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/
 

Viktor Klang says "that we consider that to be a disadvantage, i.e. the programmer has to make choices about execution rather than flow." But if you don't make choices about execution you'll end up using the wrong execution context for the task.

I think you misunderstand me. By always having to specify where things are run, you are always in control of execution.
 
In any case, that kind of decision should be documented somewhere, along with, why ExecutionContext is passed implicitly?

If it is implicit, you can choose whether you want to pass it explicitly or not, if it is explicit you have to pass it explicitly at all time.
 

Maybe i've missed something, but this whole concurrent package has been written as if there is one ExecutionContext that rule them all. Don't we need different ExecutionContexts for different tasks?

I don't share that conclusion: Akka Dispatchers are ExecutionContexts, there are multiple possibilities for enhancing ExecutionContexts, several of those already mentioned in the thread linked earlier, but I will repeat them here for easy access:

 

Then there's the bit about ExecutionContext.prepare. The scaladoc is not helping so much:

  /** Prepares for the execution of a task. Returns the prepared
   *  execution context. A valid implementation of `prepare` is one
   *  that simply returns `this`.
   */
  def prepare(): ExecutionContext = this




Yes, EC.prepare is going to get address in the follow-on SIP as well.
 






--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



--
Cheers,

———————
Viktor Klang
Chief Architect - Typesafe

Twitter: @viktorklang

Bruno Bieth

unread,
Feb 5, 2014, 12:17:29 PM2/5/14
to scala...@googlegroups.com, Bruno Bieth
Hi Viktor!


Great to see this email, we're currently curating the second part of that SIP which will address ExecutionContext in detail.
Please watch the scala-sips ML.

Nice!

Assuming `session.getFriends()` is blocking, it seems to me that the global execution context, being backed by a ForkJoinPool, should not be used here.
Why?
use scala.concurrent.blocking { Thread.sleep(99999) }
Or even better, don't use Thread.sleep. Ever.
 
Thread.sleep was used here to simulate a blocking call. Of course I never Thread.sleep(99999) in a real program :)
That code snippet was just there to illustrate that future f2 and f3 won't run before f1 finishes, because blocking in a ForkJoinPool without notifying it doesn't make it to grow its workers.

So here come the blocking construct. With this you can notify the fork join pool of a blocking call. But I guess you wouldn't build an app that does a lot of IO on a FJP right?
I mean, if you don't know upfront how many blocking call you might end up with, you could create a lot of threads, up to the FJP limit (32767). That's a pretty high limit isn't it? I'd say way too high in many situations.

A word on the blocking construct. There's indeed a "Blocking" section in the current doc, but it's mainly about blocking on a future and no so much in a future. At least this distinction is not made clear.
As the documentation says almost nothing about what `blocking` does in effect, I looked up its code (scala 2.10.3) and it turns out that only the default ExecutionContext implementation implements it (by means of ExecutionContextImpl.DefaultThreadFactory).
If I use `ExecutionContext.fromExecutor(Executor)` the blocking construct will be made useless.
BTW I find weird the fact that the default implementation falls back on a ThreadPoolExecutor if building a FJP fails?! Also the TPE is then configured with a thread factory that builds ForkJoinWorkerThread with calls ForkJoinPool.managedBlock...

Overall I'd say that to me the biggest issue when I started using the scala.concurrent package was the lack of documentation:
* in the current documentation, the example should wrap session.getFriends() in a blocking call.
* the global ExecutionContext should be way more documented. First of all that it is backed by a FJP with all the consequences, such as failing to wrap blocking calls in a blocking block can lead to deadlocks, or using too many blocking calls will kill your resources

> Because Futures are strict—they are not a lifted expression of operations. User code shall not ever run on the thread of the completer, as this introduces weird situations where it is impossible to figure out where things are running (non-determinisim due to the race between completion and attaching of action). For a longer discussion on this, see http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/
 
Thanks for the links. The blog post was great!
Maybe it's a matter of taste, but to me if the documentation clearly states how `runNow` or `runInCurrentThread` behave then it's fine. If I understand, the problem is about locks: locking with `runNow` can make you deadlock.
But if all i'm doing is a simple non-blocking operation then I don't really care do I?

>  I think you misunderstand me. By always having to specify where things are run, you are always in control of execution.

Ok.

> If it is implicit, you can choose whether you want to pass it explicitly or not, if it is explicit you have to pass it explicitly at all time.

> I don't share that conclusion: Akka Dispatchers are ExecutionContexts, there are multiple possibilities for enhancing ExecutionContexts, several of those already mentioned in the thread linked earlier, but I will repeat them here for easy access:


I'll write about this in another message.

Thanks for your time, it's really nice to write all that down :)

Bruno
 

Bruno Bieth

unread,
Feb 6, 2014, 10:53:42 AM2/6/14
to scala...@googlegroups.com, Bruno Bieth

> If it is implicit, you can choose whether you want to pass it explicitly or not, if it is explicit you have to pass it explicitly at all time.

The problem is that it let the beginner think that you can do concurrent programming without knowing much about it. Very much like GWT with the promise that you won't have to learn javascript, VisualBasic that makes you think you can build programs without knowing programming, Access that let you design databases without having a clue about normalization, etc...
Basically all you have to do is import the global execution context and you're done. The higher up the better. As a matter of fact there's only one import of ExecutionContext.global in the whole doc. And as it is written : "for now it is sufficient to know that you can import the default execution context as shown above".

My take is that it is necessary to have ExecutionContext passed implicitly to make futures usable in for-comprehensions.
And by making futures for-comprehension compatible, you end up with methods like foreach. Why would you need foreach if you have onSuccess?
Foreach let you write stuff like:

    for( result <- Future { sys.error("damn") } ) {
      println("And here is our result: " + result)
    }

And your exception is swallowed.

You say you can decide to pass ExecutionContext explicitly if you wanted to, but you cannot do that in a for-comprehension, you have to rewrite your code with map, flatMap and foreach (which will probably be replaced by onSuccess).

To me using for-comprehension on a future is way too powerful and if I had to introduce futures in a team of developpers that are not acquainted with this API I would probably forbid its use with static analysis.
In a project where I want to have a fine control on the amount of threads created I've written a compiler plugin to spot uses of the global ExecutionContext. It issues warnings when used implicitly.
 
> I don't share that conclusion: Akka Dispatchers are ExecutionContexts, there are multiple possibilities for enhancing ExecutionContexts, several of those already mentioned in the thread linked earlier, but I will repeat them here for easy access:

Yeah but Akka Dispatchers don't come with the scala library and are not mentioned in the doc. It might be that having ExecutionContexts passed implicitly in Akka is the right decision though. In this case you might want to have a Future API for Akka and another one for the standard library (which is based on thread pools)?


 

Bruno
 

Bruno Bieth

unread,
Feb 7, 2014, 10:18:42 AM2/7/14
to scala...@googlegroups.com, Bruno Bieth

I just remembered this post (quite relevant to this discussion) http://beust.com/weblog/2011/08/15/scalas-parallel-collections/

Check out Doug Lea's comment : http://beust.com/weblog/2011/08/15/scalas-parallel-collections/#comment-12031

> FJ is designed for non-IO-based computations — it doesn’t understand blocking IO at all. However, people find that it can be made to work pretty well (much better than I had expected) with IO-based tasks. We’ll be adding a bit more support for doing even better sometime.
 
So even if FJ can deal with IO, this is not what it has been primarily designed for.

Johannes Rudolph

unread,
Feb 7, 2014, 10:59:56 AM2/7/14
to Bruno Bieth, scala-user
Do you have any suggestions? You seem to imply that there's some
potential other scheduling strategy that would help when blocking
calls are involved.

IMO there's no thread pool which will make blocking calls and the
problems they come with automatically vanish. If you need blocking
calls you will always have to do active management of the threads to
run blocking things on. ExecutionContext.global sets some default
values which may be different from ones you may have observed in other
contexts. If you don't do any active management you will sooner or
later always run into issues when it comes to blocking. There's no
unbounded threadpool as you will never have unlimited memory to
allocate an unlimited number of stacks.

--
Johannes

-----------------------------------------------
Johannes Rudolph
http://virtual-void.net

Bruno Bieth

unread,
Feb 7, 2014, 11:27:56 AM2/7/14
to scala...@googlegroups.com, Bruno Bieth, johannes...@googlemail.com


Yes, and that's my whole point. The doc starts right off the bat with an example of a blocking call wrapped into a future, giving you that impression that there is "a thread pool which will make blocking calls and the problems they come with automatically vanish".
So my suggestions are:
1. make this very clear in the doc (and they are other points were the doc is lacking such as `blocking`)
2. make ExecutionContext not implicit, thus forcing you to know what you are doing (and removing for-comprehension support, but I think that's implied by making ExecutionContext explicit)

One of java's main idea was that there's no problem in having verbose code if it helps readability. The code is more often read than written. The future API is not verbose enough in my opinion.

When using blocking IO you might want to have a dedicated thread pool for it with a maximum pool size way below 32767. There's something about it in Java Concurrency in Practice (Section 8.2).
 

Johannes Rudolph

unread,
Feb 7, 2014, 12:02:46 PM2/7/14
to Bruno Bieth, scala-user
On Fri, Feb 7, 2014 at 5:27 PM, Bruno Bieth <bie...@gmail.com> wrote:
> Yes, and that's my whole point. The doc starts right off the bat with an
> example of a blocking call wrapped into a future, giving you that impression
> that there is "a thread pool which will make blocking calls and the problems
> they come with automatically vanish".

Ah, that makes sense. This whole "wrap in a future"-business invites
you to use it to hide blocking away behind a nice Future interface.
So, I agree `future { session.getFriends() }` may not be the best
thing to teach newcomers (at least without accompanying it with a big
fat warning about what it means to block there).

But that's also completely orthogonal to the the API itself including
for-comprehensions which works perfectly well with default settings as
long you don't use any blocking.

√iktor Ҡlang

unread,
Feb 8, 2014, 8:59:24 AM2/8/14
to Bruno Bieth, scala-user
On Wed, Feb 5, 2014 at 6:17 PM, Bruno Bieth <bie...@gmail.com> wrote:
Hi Viktor!


Great to see this email, we're currently curating the second part of that SIP which will address ExecutionContext in detail.
Please watch the scala-sips ML.

Nice!

Assuming `session.getFriends()` is blocking, it seems to me that the global execution context, being backed by a ForkJoinPool, should not be used here.
Why?
use scala.concurrent.blocking { Thread.sleep(99999) }
Or even better, don't use Thread.sleep. Ever.
 
Thread.sleep was used here to simulate a blocking call. Of course I never Thread.sleep(99999) in a real program :)
That code snippet was just there to illustrate that future f2 and f3 won't run before f1 finishes, because blocking in a ForkJoinPool without notifying it doesn't make it to grow its workers.

So here come the blocking construct. With this you can notify the fork join pool of a blocking call. But I guess you wouldn't build an app that does a lot of IO on a FJP right?

That depends completely. I think we're still without a "perfect" thread pool implementation.
 
I mean, if you don't know upfront how many blocking call you might end up with, you could create a lot of threads, up to the FJP limit (32767). That's a pretty high limit isn't it? I'd say way too high in many situations.

Yes, it is unfortunate that the max threads isn't configurable in the pool. I'll talk to Doug about that.
 

A word on the blocking construct. There's indeed a "Blocking" section in the current doc, but it's mainly about blocking on a future and no so much in a future. At least this distinction is not made clear.

Good point, I'll try to add some extra documentation into 2.11 (there's still time to do a documentation PR so if you want to help out that'd be fantastic)
 
As the documentation says almost nothing about what `blocking` does in effect, I looked up its code (scala 2.10.3) and it turns out that only the default ExecutionContext implementation implements it (by means of ExecutionContextImpl.DefaultThreadFactory).
If I use `ExecutionContext.fromExecutor(Executor)` the blocking construct will be made useless.

It's not useless. not only can you install your own BlockContext but it serves as "documentation" so that if someone changes the EC implementation, your code can be made to run better.
 
BTW I find weird the fact that the default implementation falls back on a ThreadPoolExecutor if building a FJP fails?! Also the TPE is then configured with a thread factory that builds ForkJoinWorkerThread with calls ForkJoinPool.managedBlock...

I think you need to revisit that piece of code.

newThread(runnable)
and
newThread(FJP)

are different methods.
 

Overall I'd say that to me the biggest issue when I started using the scala.concurrent package was the lack of documentation:
* in the current documentation, the example should wrap session.getFriends() in a blocking call.
* the global ExecutionContext should be way more documented. First of all that it is backed by a FJP with all the consequences, such as failing to wrap blocking calls in a blocking block can lead to deadlocks, or using too many blocking calls will kill your resources

blocking can always lead to deadlocks. Hence me repeating the mantra "Do not block. Do not block. …"
 

> Because Futures are strict—they are not a lifted expression of operations. User code shall not ever run on the thread of the completer, as this introduces weird situations where it is impossible to figure out where things are running (non-determinisim due to the race between completion and attaching of action). For a longer discussion on this, see http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/
 
Thanks for the links. The blog post was great!
Maybe it's a matter of taste, but to me if the documentation clearly states how `runNow` or `runInCurrentThread` behave then it's fine. If I understand, the problem is about locks: locking with `runNow` can make you deadlock.
But if all i'm doing is a simple non-blocking operation then I don't really care do I?

If you control the code that will be executed fully, then you can always cut corners, but passing that EC into someone elses code, I wouldn't.
So, I'll improve on the documentation so that it is clearer in that regard.
 

>  I think you misunderstand me. By always having to specify where things are run, you are always in control of execution.

Ok.

> If it is implicit, you can choose whether you want to pass it explicitly or not, if it is explicit you have to pass it explicitly at all time.

> I don't share that conclusion: Akka Dispatchers are ExecutionContexts, there are multiple possibilities for enhancing ExecutionContexts, several of those already mentioned in the thread linked earlier, but I will repeat them here for easy access:


I'll write about this in another message.

Thanks for your time, it's really nice to write all that down :)

You're most welcome
 


Bruno
 

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

√iktor Ҡlang

unread,
Feb 8, 2014, 9:03:15 AM2/8/14
to Bruno Bieth, scala-user
On Thu, Feb 6, 2014 at 4:53 PM, Bruno Bieth <bie...@gmail.com> wrote:

> If it is implicit, you can choose whether you want to pass it explicitly or not, if it is explicit you have to pass it explicitly at all time.

The problem is that it let the beginner think that you can do concurrent programming without knowing much about it. Very much like GWT with the promise that you won't have to learn javascript, VisualBasic that makes you think you can build programs without knowing programming, Access that let you design databases without having a clue about normalization, etc...
Basically all you have to do is import the global execution context and you're done. The higher up the better. As a matter of fact there's only one import of ExecutionContext.global in the whole doc. And as it is written : "for now it is sufficient to know that you can import the default execution context as shown above".

My take is that it is necessary to have ExecutionContext passed implicitly to make futures usable in for-comprehensions.
And by making futures for-comprehension compatible, you end up with methods like foreach. Why would you need foreach if you have onSuccess?
Foreach let you write stuff like:

    for( result <- Future { sys.error("damn") } ) {
      println("And here is our result: " + result)
    }

And your exception is swallowed.

Of course. What else would happen?
 

You say you can decide to pass ExecutionContext explicitly if you wanted to, but you cannot do that in a for-comprehension, you have to rewrite your code with map, flatMap and foreach (which will probably be replaced by onSuccess).

Can you give a code example that demonstrates the problem?
 

To me using for-comprehension on a future is way too powerful and if I had to introduce futures in a team of developpers that are not acquainted with this API I would probably forbid its use with static analysis.

I tend to prefer to trust my colleagues, and use code reviews to give feedback.
 
In a project where I want to have a fine control on the amount of threads created I've written a compiler plugin to spot uses of the global ExecutionContext. It issues warnings when used implicitly.

You know that you can configure the global EC, right?
 
 
> I don't share that conclusion: Akka Dispatchers are ExecutionContexts, there are multiple possibilities for enhancing ExecutionContexts, several of those already mentioned in the thread linked earlier, but I will repeat them here for easy access:

Yeah but Akka Dispatchers don't come with the scala library and are not mentioned in the doc. It might be that having ExecutionContexts passed implicitly in Akka is the right decision though. In this case you might want to have a Future API for Akka and another one for the standard library (which is based on thread pools)?

No, it's about the possibility of isolation.
 


 

Bruno
 

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

√iktor Ҡlang

unread,
Feb 8, 2014, 9:04:22 AM2/8/14
to Bruno Bieth, scala-user
But it works pretty well.
 

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Bruno Bieth

unread,
Feb 11, 2014, 5:16:19 AM2/11/14
to scala...@googlegroups.com, Bruno Bieth


On Saturday, February 8, 2014 3:03:15 PM UTC+1, √iktor Klang wrote:



On Thu, Feb 6, 2014 at 4:53 PM, Bruno Bieth <bie...@gmail.com> wrote:

> If it is implicit, you can choose whether you want to pass it explicitly or not, if it is explicit you have to pass it explicitly at all time.

The problem is that it let the beginner think that you can do concurrent programming without knowing much about it. Very much like GWT with the promise that you won't have to learn javascript, VisualBasic that makes you think you can build programs without knowing programming, Access that let you design databases without having a clue about normalization, etc...
Basically all you have to do is import the global execution context and you're done. The higher up the better. As a matter of fact there's only one import of ExecutionContext.global in the whole doc. And as it is written : "for now it is sufficient to know that you can import the default execution context as shown above".

My take is that it is necessary to have ExecutionContext passed implicitly to make futures usable in for-comprehensions.
And by making futures for-comprehension compatible, you end up with methods like foreach. Why would you need foreach if you have onSuccess?
Foreach let you write stuff like:

    for( result <- Future { sys.error("damn") } ) {
      println("And here is our result: " + result)
    }

And your exception is swallowed.

Of course. What else would happen?

This is equivalent to

Future { sys.error("damn") } onSuccess { case result =>

  println("And here is our result:" + result)
}

Which is way more obvious (at least to me) that you are swallowing exceptions.
I'm not saying `foreach` is broken per-se, but that its name and worse its use in a for-comprehension is obscure.
 
 

You say you can decide to pass ExecutionContext explicitly if you wanted to, but you cannot do that in a for-comprehension, you have to rewrite your code with map, flatMap and foreach (which will probably be replaced by onSuccess).

Can you give a code example that demonstrates the problem?

val f = Future { ... }(ec1).map( ... )(ec2).map( ... )(ec3)
f.onSuccess( ... )(ec4)
f.onFailure( ... )(ec5)

I might have missed something about the language, but can you write this in for-comprehension style?

 

To me using for-comprehension on a future is way too powerful and if I had to introduce futures in a team of developpers that are not acquainted with this API I would probably forbid its use with static analysis.

I tend to prefer to trust my colleagues, and use code reviews to give feedback.

I hope you have enough time to code review every single line of every single colleague that is not aware of those gotchas ;)
That might work in some projects, but definitely not in others.
 
 
In a project where I want to have a fine control on the amount of threads created I've written a compiler plugin to spot uses of the global ExecutionContext. It issues warnings when used implicitly.

You know that you can configure the global EC, right?

What do you mean? The global EC is a val in an object.

Maybe you're refering to

val desiredParallelism = range(
      getInt("scala.concurrent.context.minThreads", "1"),
      getInt("scala.concurrent.context.numThreads", "x1"),
      getInt("scala.concurrent.context.maxThreads", "x1"))

But that's really not helping the blocking story.

BTW how does `blocking` work if the global execution context falls back to a ThreadPoolExecutor?

 
 
 
> I don't share that conclusion: Akka Dispatchers are ExecutionContexts, there are multiple possibilities for enhancing ExecutionContexts, several of those already mentioned in the thread linked earlier, but I will repeat them here for easy access:

Yeah but Akka Dispatchers don't come with the scala library and are not mentioned in the doc. It might be that having ExecutionContexts passed implicitly in Akka is the right decision though. In this case you might want to have a Future API for Akka and another one for the standard library (which is based on thread pools)?

No, it's about the possibility of isolation.

Sorry, I don't understand, could you expand a little?
 

Bruno Bieth

unread,
Feb 11, 2014, 5:31:19 AM2/11/14
to scala...@googlegroups.com, Bruno Bieth

Good point, I'll try to add some extra documentation into 2.11 (there's still time to do a documentation PR so if you want to help out that'd be fantastic)

Right, I'll try something.
 
 
As the documentation says almost nothing about what `blocking` does in effect, I looked up its code (scala 2.10.3) and it turns out that only the default ExecutionContext implementation implements it (by means of ExecutionContextImpl.DefaultThreadFactory).
If I use `ExecutionContext.fromExecutor(Executor)` the blocking construct will be made useless.

It's not useless. not only can you install your own BlockContext but it serves as "documentation" so that if someone changes the EC implementation, your code can be made to run better.

Ok, but this should be better documented.
 
I think you need to revisit that piece of code.

newThread(runnable)
and
newThread(FJP)

are different methods.

My mistake! Still I find this awkward and a recipe for subtle bugs. Why not let the FJP instantiation fail?
 
Thanks, Bruno

Bruno Bieth

unread,
Feb 11, 2014, 5:41:14 AM2/11/14
to scala...@googlegroups.com, Bruno Bieth, johannes...@googlemail.com
 
as long you don't use any blocking.

This is where it's going to bite you.

My bet is that for-comprehensions are not going to be used as much as expected with futures. In a couple of years we could do a poll for the fun ;)
 

Derek Williams

unread,
Feb 11, 2014, 6:05:00 AM2/11/14
to Bruno Bieth, scala-user
On Tue, Feb 11, 2014 at 10:16 AM, Bruno Bieth <bie...@gmail.com> wrote:
You say you can decide to pass ExecutionContext explicitly if you wanted to, but you cannot do that in a for-comprehension, you have to rewrite your code with map, flatMap and foreach (which will probably be replaced by onSuccess).

Can you give a code example that demonstrates the problem?

val f = Future { ... }(ec1).map( ... )(ec2).map( ... )(ec3)
f.onSuccess( ... )(ec4)
f.onFailure( ... )(ec5)

I might have missed something about the language, but can you write this in for-comprehension style?

The way I deal with this is to make sure that anything that requires running in it's own ExecutionContext is isolated, and use a for-comprehension to flatMap them together:

def getA: Future[A] = Future(...)(blockingEC1)
def doA(a: A): Future[B] = Future(....)(blockingEC1)
def doB(b: B): Future[C] = Future(....)(blockingEC2)

for {
  a <- getA
  b <- doA(a)
  c <- doB(b)
} yield c

Use the default ExecutionContext for non blocking work, and isolate blocking work. Don't do blocking within flatMap/map, let those be done by the default ExecutionContext.

--
Derek Williams

√iktor Ҡlang

unread,
Feb 11, 2014, 6:15:14 AM2/11/14
to Bruno Bieth, scala-user
On Tue, Feb 11, 2014 at 11:16 AM, Bruno Bieth <bie...@gmail.com> wrote:


On Saturday, February 8, 2014 3:03:15 PM UTC+1, √iktor Klang wrote:



On Thu, Feb 6, 2014 at 4:53 PM, Bruno Bieth <bie...@gmail.com> wrote:

> If it is implicit, you can choose whether you want to pass it explicitly or not, if it is explicit you have to pass it explicitly at all time.

The problem is that it let the beginner think that you can do concurrent programming without knowing much about it. Very much like GWT with the promise that you won't have to learn javascript, VisualBasic that makes you think you can build programs without knowing programming, Access that let you design databases without having a clue about normalization, etc...
Basically all you have to do is import the global execution context and you're done. The higher up the better. As a matter of fact there's only one import of ExecutionContext.global in the whole doc. And as it is written : "for now it is sufficient to know that you can import the default execution context as shown above".

My take is that it is necessary to have ExecutionContext passed implicitly to make futures usable in for-comprehensions.
And by making futures for-comprehension compatible, you end up with methods like foreach. Why would you need foreach if you have onSuccess?
Foreach let you write stuff like:

    for( result <- Future { sys.error("damn") } ) {
      println("And here is our result: " + result)
    }

And your exception is swallowed.

Of course. What else would happen?

This is equivalent to

Future { sys.error("damn") } onSuccess { case result =>

  println("And here is our result:" + result)
}

Which is way more obvious (at least to me) that you are swallowing exceptions.
I'm not saying `foreach` is broken per-se, but that its name and worse its use in a for-comprehension is obscure.

Yes, if you don't know how a for-comprehension works, then it will be hard to use correctly.
However, this is well documented for Future.foreach:

defforeach[U](f: (T) ⇒ U)(implicit executor: ExecutionContext)Unit

Asynchronously processes the value in the future once the value becomes available.

Will not be called if the future fails.

 
 
 

You say you can decide to pass ExecutionContext explicitly if you wanted to, but you cannot do that in a for-comprehension, you have to rewrite your code with map, flatMap and foreach (which will probably be replaced by onSuccess).

Can you give a code example that demonstrates the problem?

val f = Future { ... }(ec1).map( ... )(ec2).map( ... )(ec3)
f.onSuccess( ... )(ec4)
f.onFailure( ... )(ec5)

I might have missed something about the language, but can you write this in for-comprehension style?

You have the exact same situation with all collections and their CanBuildFrom instance.
 

 

To me using for-comprehension on a future is way too powerful and if I had to introduce futures in a team of developpers that are not acquainted with this API I would probably forbid its use with static analysis.

I tend to prefer to trust my colleagues, and use code reviews to give feedback.

I hope you have enough time to code review every single line of every single colleague that is not aware of those gotchas ;)
That might work in some projects, but definitely not in others. 
 
 
In a project where I want to have a fine control on the amount of threads created I've written a compiler plugin to spot uses of the global ExecutionContext. It issues warnings when used implicitly.

You know that you can configure the global EC, right?

What do you mean? The global EC is a val in an object. 

Maybe you're refering to

val desiredParallelism = range(
      getInt("scala.concurrent.context.minThreads", "1"),
      getInt("scala.concurrent.context.numThreads", "x1"),
      getInt("scala.concurrent.context.maxThreads", "x1"))

But that's really not helping the blocking story.

You can install your own BlockContexts.
 

BTW how does `blocking` work if the global execution context falls back to a ThreadPoolExecutor?

 

 
 
 
> I don't share that conclusion: Akka Dispatchers are ExecutionContexts, there are multiple possibilities for enhancing ExecutionContexts, several of those already mentioned in the thread linked earlier, but I will repeat them here for easy access:

Yeah but Akka Dispatchers don't come with the scala library and are not mentioned in the doc. It might be that having ExecutionContexts passed implicitly in Akka is the right decision though. In this case you might want to have a Future API for Akka and another one for the standard library (which is based on thread pools)?

No, it's about the possibility of isolation.

Sorry, I don't understand, could you expand a little?

By divorcing the execution mechanism from the thing that is executed one is in full control over _where_ and by _what_ something is executed, so that one can isolate the execution of things from other things, this is commonly referred to as "bulkheading".
 
 
 


 

Bruno
 

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.



--
Cheers,

———————
Viktor Klang
Chief Architect - Typesafe

Twitter: @viktorklang

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Bruno Bieth

unread,
Feb 11, 2014, 9:55:20 AM2/11/14
to scala...@googlegroups.com, Bruno Bieth

You have the exact same situation with all collections and their CanBuildFrom instance.

I have never had the need to use a different CanBuildFrom from what the compiler chose. This is the exact opposite situation where the types tell you precisely which instance of CanBuildFrom is needed.
I'm not going to use a BitSet CanBuildFrom when turning a List of String to a Vector of String.
Like you said, we're still without a "perfect" thread pool implementation and that's why this pattern should be avoided in this situation. I do use it however in my application with a finer grain construct:

  trait IoExecutionContext

  implicit class IoFuture[A](val future: Future[A]) extends AnyVal {
    /** map this future by executing `f` in an `IoExecutionContext` */
    def mapIo[B](f : A => B)(implicit ioEc: IoExecutionContext) : Future[B] = future.map(f)(ioEc)
  }

Of course this is in the context of my application, where `Io` means something specific.
 


BTW how does `blocking` work if the global execution context falls back to a ThreadPoolExecutor?


Exactly, that's the DefaultBlockContext which does nothing. So imagine you've decorated your code with `blocking` wherever it's needed, you start your application and for some reason (although I can't see why?) the FJP cannot be instantiated. You get a warning on the console that it is falling back to a TPE and baam you end up deadlock everywhere because `blocking` isn't doing its job.
 

√iktor Ҡlang

unread,
Feb 11, 2014, 10:02:47 AM2/11/14
to Bruno Bieth, scala-user
On Tue, Feb 11, 2014 at 3:55 PM, Bruno Bieth <bie...@gmail.com> wrote:

You have the exact same situation with all collections and their CanBuildFrom instance.

I have never had the need to use a different CanBuildFrom from what the compiler chose. This is the exact opposite situation where the types tell you precisely which instance of CanBuildFrom is needed.
I'm not going to use a BitSet CanBuildFrom when turning a List of String to a Vector of String.

I use "breakOut" fairly commonly to avoid intermittent collections.
 
Like you said, we're still without a "perfect" thread pool implementation and that's why this pattern should be avoided in this situation. I do use it however in my application with a finer grain construct:

  trait IoExecutionContext

  implicit class IoFuture[A](val future: Future[A]) extends AnyVal {
    /** map this future by executing `f` in an `IoExecutionContext` */
    def mapIo[B](f : A => B)(implicit ioEc: IoExecutionContext) : Future[B] = future.map(f)(ioEc)
  }

Of course this is in the context of my application, where `Io` means something specific.
 


BTW how does `blocking` work if the global execution context falls back to a ThreadPoolExecutor?


Exactly, that's the DefaultBlockContext which does nothing. So imagine you've decorated your code with `blocking` wherever it's needed, you start your application and for some reason (although I can't see why?) the FJP cannot be instantiated. You get a warning on the console that it is falling back to a TPE and baam you end up deadlock everywhere because `blocking` isn't doing its job.

I think you're misunderstanding the intention of "blocking". It is to give a hint that there will be blocking ahead, so IF the pool can do something, it will attempt to do so. If you annotate your blocking code appropriately, at least you give the underlying execution mechanism a chance to deal with it.
 
 

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Bruno Bieth

unread,
Feb 11, 2014, 10:22:12 AM2/11/14
to scala...@googlegroups.com, Bruno Bieth

I think you're misunderstanding the intention of "blocking". It is to give a hint that there will be blocking ahead, so IF the pool can do something, it will attempt to do so. If you annotate your blocking code appropriately, at least you give the underlying execution mechanism a chance to deal with it.

Hmm, then I'm not sure about what should be written in the documentation. Maybe along those lines:

  • import ExecutionContext.Implicits.global

    • val f: Future[List[Friend]] = future {
    • blocking {
    • session.getFriends()
    • }
    • }


    Note that we're using the global execution context here but you can be surprised that using `blocking` might not work as expected if the FJP cannot be instantiated and turns to be a TPE. This will show up as a warning in your logs, so be sure to check your logs if unexpected deadlock occurs.
     

    √iktor Ҡlang

    unread,
    Feb 11, 2014, 10:28:42 AM2/11/14
    to Bruno Bieth, scala-user
    On Tue, Feb 11, 2014 at 4:22 PM, Bruno Bieth <bie...@gmail.com> wrote:

    I think you're misunderstanding the intention of "blocking". It is to give a hint that there will be blocking ahead, so IF the pool can do something, it will attempt to do so. If you annotate your blocking code appropriately, at least you give the underlying execution mechanism a chance to deal with it.

    Hmm, then I'm not sure about what should be written in the documentation. Maybe along those lines:

    • import ExecutionContext.Implicits.global

    • val f: Future[List[Friend]] = future {
    • blocking {
    • session.getFriends()
    • }
    • }


    Note that we're using the global execution context here but you can be surprised that using `blocking` might not work as expected if the FJP cannot be instantiated and turns to be a TPE. This will show up as a warning in your logs, so be sure to check your logs if unexpected deadlock occurs.
     

    Yes, as we noted previously, documentation can definitely be improved, please feel encouraged to help out :)

    As for blocking itself, it describes the intent:

    defblocking[T](body: ⇒ T)T

    Used to designate a piece of code which potentially blocks, allowing the current BlockContext to adjust the runtime's behavior. Properly marking blocking code may improve performance or avoid deadlocks.

    Blocking on an Awaitable should be done using Await.result instead of blocking.

     
    (emphasis mine)

     
     

    --
    You received this message because you are subscribed to the Google Groups "scala-user" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

    For more options, visit https://groups.google.com/groups/opt_out.



    --
    Cheers,

    ———————
    Viktor Klang
    Chief Architect - Typesafe

    Twitter: @viktorklang

    --
    You received this message because you are subscribed to the Google Groups "scala-user" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.

    Bruno Bieth

    unread,
    Feb 11, 2014, 12:32:11 PM2/11/14
    to scala...@googlegroups.com, Bruno Bieth
    That's my point, you have to find circumvoluted ways to accomodate the for-comprehension. By using flatMap instead of map you've created two more futures and spawned 2 more threads.

    √iktor Ҡlang

    unread,
    Feb 11, 2014, 12:41:00 PM2/11/14
    to Bruno Bieth, scala-user
    On Tue, Feb 11, 2014 at 6:32 PM, Bruno Bieth <bie...@gmail.com> wrote:
    That's my point, you have to find circumvoluted ways to accomodate the for-comprehension. By using flatMap instead of map you've created two more futures and spawned 2 more threads.

    The DefaultPromise is about as cheap as you can make things, so allocating one is trivial.
    As for the "spawned 2 more threads." I don't understand what you mean.
     

    On Tuesday, February 11, 2014 12:05:00 PM UTC+1, Derek Williams wrote:
    On Tue, Feb 11, 2014 at 10:16 AM, Bruno Bieth <bie...@gmail.com> wrote:
    You say you can decide to pass ExecutionContext explicitly if you wanted to, but you cannot do that in a for-comprehension, you have to rewrite your code with map, flatMap and foreach (which will probably be replaced by onSuccess).

    Can you give a code example that demonstrates the problem?

    val f = Future { ... }(ec1).map( ... )(ec2).map( ... )(ec3)
    f.onSuccess( ... )(ec4)
    f.onFailure( ... )(ec5)

    I might have missed something about the language, but can you write this in for-comprehension style?

    The way I deal with this is to make sure that anything that requires running in it's own ExecutionContext is isolated, and use a for-comprehension to flatMap them together:

    def getA: Future[A] = Future(...)(blockingEC1)
    def doA(a: A): Future[B] = Future(....)(blockingEC1)
    def doB(b: B): Future[C] = Future(....)(blockingEC2)

    for {
      a <- getA
      b <- doA(a)
      c <- doB(b)
    } yield c

    Use the default ExecutionContext for non blocking work, and isolate blocking work. Don't do blocking within flatMap/map, let those be done by the default ExecutionContext.

    --
    Derek Williams

    --
    You received this message because you are subscribed to the Google Groups "scala-user" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.

    Bruno Bieth

    unread,
    Feb 11, 2014, 1:19:43 PM2/11/14
    to scala...@googlegroups.com, Bruno Bieth


    On Tuesday, February 11, 2014 6:41:00 PM UTC+1, √iktor Klang wrote:



    On Tue, Feb 11, 2014 at 6:32 PM, Bruno Bieth <bie...@gmail.com> wrote:
    That's my point, you have to find circumvoluted ways to accomodate the for-comprehension. By using flatMap instead of map you've created two more futures and spawned 2 more threads.

    The DefaultPromise is about as cheap as you can make things, so allocating one is trivial.
    As for the "spawned 2 more threads." I don't understand what you mean.

    def doA(a: A): Future[B] = Future( reallyDoA )(blockingEC1)
    def doB(b: B): Future[C] = Future( reallyDoB )(blockingEC2)

    getA().flatMap(doA(a)) // one thread for flat map
             .flatMap(doB(b)) // one thread for flat map = 2 more threads

    getA().map( reallyDoA )(blockingEC1).map( reallyDoB )(blockingEC2) // no overhead

    The flapMap operation, even though cheap, has to run through an executor. If you're using the global EC it means submitting a task to the FJP (= context switch overhead + many objects created on the way).



     

    √iktor Ҡlang

    unread,
    Feb 11, 2014, 2:19:35 PM2/11/14
    to Bruno Bieth, scala-user
    On Tue, Feb 11, 2014 at 7:19 PM, Bruno Bieth <bie...@gmail.com> wrote:


    On Tuesday, February 11, 2014 6:41:00 PM UTC+1, √iktor Klang wrote:



    On Tue, Feb 11, 2014 at 6:32 PM, Bruno Bieth <bie...@gmail.com> wrote:
    That's my point, you have to find circumvoluted ways to accomodate the for-comprehension. By using flatMap instead of map you've created two more futures and spawned 2 more threads.

    The DefaultPromise is about as cheap as you can make things, so allocating one is trivial.
    As for the "spawned 2 more threads." I don't understand what you mean.

    def doA(a: A): Future[B] = Future( reallyDoA )(blockingEC1)
    def doB(b: B): Future[C] = Future( reallyDoB )(blockingEC2)

    getA().flatMap(doA(a)) // one thread for flat map
             .flatMap(doB(b)) // one thread for flat map = 2 more threads

    getA().map( reallyDoA )(blockingEC1).map( reallyDoB )(blockingEC2) // no overhead

    The flapMap operation, even though cheap, has to run through an executor.

    Yes, that's the entire point. If you don't want to do things async, just do it directly.
     
    If you're using the global EC it means submitting a task to the FJP (= context switch overhead + many objects created on the way).

    No absolutely not. It depends on the implementation and configuration of the global EC—which is exactly how it should be, as it means that it can be optimized separately from your code. Just as you noted with FJP vs. TPE for the Global.

    Just to reiterate: The entire idea of EC is to abstract away _how_ things are executed.

    Bruno Bieth

    unread,
    Feb 12, 2014, 4:31:17 AM2/12/14
    to scala...@googlegroups.com, Bruno Bieth


    On Tuesday, February 11, 2014 8:19:35 PM UTC+1, √iktor Klang wrote:



    On Tue, Feb 11, 2014 at 7:19 PM, Bruno Bieth <bie...@gmail.com> wrote:


    On Tuesday, February 11, 2014 6:41:00 PM UTC+1, √iktor Klang wrote:



    On Tue, Feb 11, 2014 at 6:32 PM, Bruno Bieth <bie...@gmail.com> wrote:
    That's my point, you have to find circumvoluted ways to accomodate the for-comprehension. By using flatMap instead of map you've created two more futures and spawned 2 more threads.

    The DefaultPromise is about as cheap as you can make things, so allocating one is trivial.
    As for the "spawned 2 more threads." I don't understand what you mean.

    def doA(a: A): Future[B] = Future( reallyDoA )(blockingEC1)
    def doB(b: B): Future[C] = Future( reallyDoB )(blockingEC2)

    getA().flatMap(doA(a)) // one thread for flat map
             .flatMap(doB(b)) // one thread for flat map = 2 more threads

    getA().map( reallyDoA )(blockingEC1).map( reallyDoB )(blockingEC2) // no overhead

    The flapMap operation, even though cheap, has to run through an executor.

    Yes, that's the entire point. If you don't want to do things async, just do it directly.

    I think you missed the point.


      getA().map( reallyDoA )(blockingEC1).map( reallyDoB )(blockingEC2) // no overhead

    Is async. It just uses 2 threads less.
     
     
    If you're using the global EC it means submitting a task to the FJP (= context switch overhead + many objects created on the way).

    No absolutely not. It depends on the implementation and configuration of the global EC—which is exactly how it should be, as it means that it can be optimized separately from your code. Just as you noted with FJP vs. TPE for the Global.

    Just to reiterate: The entire idea of EC is to abstract away _how_ things are executed.

    As we already discussed, the global EC implementation is written in stone and its configuration almost inexistent (i'm referring to the "scala.concurrent.context.maxThreads" VM argument).
    To me the entire idea of the global EC is to let the programmers use the Future library standalone (i.e without Akka). Look at Derek William answer, which explicitly recommends using the global EC for the flatMap operation.

    Anyway, back to the original question, I'm going to rewrite the for-comprehension and inline the doX functions:

      getA().flatMap(Future(reallyDoA)(blockingEC1))(globalEC).flatMap(Future(reallyDoB)(blockingEC2))(globalEC)

    This wouldn't be so bad if globalEC was used everywhere (instead of blockingEC1 and blockingEC2), as the globalEC would be able to execute the flatMap wiring in the same thread as reallyDoA and reallyDoB, but as we are using blockingEC1 and blockingEC2 we can be sure that there will be a context switch just for that flatMap wiring.

    Even if we replace globalEC by something else, it seems there will always be an overhead compared to the `map` version above. We exclude the `currentThreadExecutionContext` as we wouldn't want it to be imported implicitly.
     

    Derek Williams

    unread,
    Feb 12, 2014, 4:42:20 AM2/12/14
    to Bruno Bieth, scala-user
    Are you having a performance problem? If you have an example app that is running slower than you'd expect that could help. We use the pattern I previously posted in production (using Akka's default ExecutionContext, not the Scala global one) and we don't have problems caused by the extra flatMaps (compared to the blocking calls, the cost of the context switch is negligible).
    Derek Williams

    Bruno Bieth

    unread,
    Feb 12, 2014, 5:50:59 AM2/12/14
    to scala...@googlegroups.com, Bruno Bieth


    On Wednesday, February 12, 2014 10:42:20 AM UTC+1, Derek Williams wrote:
    Are you having a performance problem? If you have an example app that is running slower than you'd expect that could help. We use the pattern I previously posted in production (using Akka's default ExecutionContext, not the Scala global one) and we don't have problems caused by the extra flatMaps (compared to the blocking calls, the cost of the context switch is negligible).

    I'm sure it is not a major bottleneck of course. It is just 2 ways to write the same thing, but using for-comprehension is less efficient and I fail to see the benefits. You're just paying for nothing.
    In many aspects of an application you can make sub-optimal decisions that won't be a bottleneck in themselves. If you're building a dummy website for 3 users then fine. If you're building a massive system for millions, then those small sub-optimal decisions will begin to cost you.

    √iktor Ҡlang

    unread,
    Feb 12, 2014, 7:36:01 AM2/12/14
    to Bruno Bieth, scala-user
    On Wed, Feb 12, 2014 at 10:31 AM, Bruno Bieth <bie...@gmail.com> wrote:


    On Tuesday, February 11, 2014 8:19:35 PM UTC+1, √iktor Klang wrote:



    On Tue, Feb 11, 2014 at 7:19 PM, Bruno Bieth <bie...@gmail.com> wrote:


    On Tuesday, February 11, 2014 6:41:00 PM UTC+1, √iktor Klang wrote:



    On Tue, Feb 11, 2014 at 6:32 PM, Bruno Bieth <bie...@gmail.com> wrote:
    That's my point, you have to find circumvoluted ways to accomodate the for-comprehension. By using flatMap instead of map you've created two more futures and spawned 2 more threads.

    The DefaultPromise is about as cheap as you can make things, so allocating one is trivial.
    As for the "spawned 2 more threads." I don't understand what you mean.

    def doA(a: A): Future[B] = Future( reallyDoA )(blockingEC1)
    def doB(b: B): Future[C] = Future( reallyDoB )(blockingEC2)

    getA().flatMap(doA(a)) // one thread for flat map
             .flatMap(doB(b)) // one thread for flat map = 2 more threads

    getA().map( reallyDoA )(blockingEC1).map( reallyDoB )(blockingEC2) // no overhead

    The flapMap operation, even though cheap, has to run through an executor.

    Yes, that's the entire point. If you don't want to do things async, just do it directly.

    I think you missed the point.


    In the code above, there is exactly _nothing_ that dictate how many java.lang.Threads will be used–That is _completely_ at the discretion of the _implementations_ of the ECs—the ECs might just delegate to the same underlying Executor for all we know
    If you _decide_ to pass in ECs that spawn threads that is under _your_ control and responsibility. It is however not hardcoded into the semantics of Futures.

    That is my point.



      getA().map( reallyDoA )(blockingEC1).map( reallyDoB )(blockingEC2) // no overhead

    Is async. It just uses 2 threads less.

    Again–you, the programmer, _choose_ to use those ECs!
     
     
     
    If you're using the global EC it means submitting a task to the FJP (= context switch overhead + many objects created on the way).

    No absolutely not. It depends on the implementation and configuration of the global EC—which is exactly how it should be, as it means that it can be optimized separately from your code. Just as you noted with FJP vs. TPE for the Global.

    Just to reiterate: The entire idea of EC is to abstract away _how_ things are executed.

    As we already discussed, the global EC implementation is written in stone

    No, in fact, it will most likely change with Scala 2.12 as we can detect Java8's FJPs commonPool and delegate to that.
    We can also make the default global use a BatchingExecutor. There's also possibilities for improving FJP.
     
    and its configuration almost inexistent (i'm referring to the "scala.concurrent.context.maxThreads" VM argument).

    Right now you can configure the number of Threads it will have, I wouldn't call that "almost nonexistent" but I guess it depends on your definitions.
     
    To me the entire idea of the global EC is to let the programmers use the Future library standalone (i.e without Akka). Look at Derek William answer, which explicitly recommends using the global EC for the flatMap operation.

    The intentions of ExecutionContext.global are the same as the intent of FJP.commonPool (which was added to FJP _after_ the SIP was finalized)

    You, the programmer, are perfectly able and capable of creating your own ECs using EC.fromExecutor and EC.fromExecutorService and use those instead of EC.global,
    the _biggest_ difference is that something you create you are also responsible for destroying. EC.global is there as a _service_, it uses daemonic threads so that the program will gracefully exit when the main thread exits.

     

    Anyway, back to the original question, I'm going to rewrite the for-comprehension and inline the doX functions:

      getA().flatMap(Future(reallyDoA)(blockingEC1))(globalEC).flatMap(Future(reallyDoB)(blockingEC2))(globalEC)

    This wouldn't be so bad if globalEC was used everywhere (instead of blockingEC1 and blockingEC2), as the globalEC would be able to execute the flatMap wiring in the same thread as reallyDoA and reallyDoB, but as we are using blockingEC1 and blockingEC2 we can be sure that there will be a context switch just for that flatMap wiring.

    Still, my point is that it is something that you opt into. You _choose_ what EC you want to use. You _choose_ that Context Switch.
     

    Even if we replace globalEC by something else, it seems there will always be an overhead compared to the `map` version above. We exclude the `currentThreadExecutionContext` as we wouldn't want it to be imported implicitly.

    Disregarding ECs there will _always_ be a higher overhead for flatMap compared to map, because it always creates an extra Promise.
    My objection here is: Ho big of a problem is it (performance impact) in reality? And, how much can be mitigated by just having smarter ECs, like ones backed by BatchingExecutor etc.

    Nils Kilden-Pedersen

    unread,
    Feb 12, 2014, 10:25:42 AM2/12/14
    to √iktor Ҡlang, Bruno Bieth, scala-user
    On Wed, Feb 12, 2014 at 6:36 AM, √iktor Ҡlang <viktor...@gmail.com> wrote:



    On Wed, Feb 12, 2014 at 10:31 AM, Bruno Bieth <bie...@gmail.com> wrote:


    On Tuesday, February 11, 2014 8:19:35 PM UTC+1, √iktor Klang wrote:



    On Tue, Feb 11, 2014 at 7:19 PM, Bruno Bieth <bie...@gmail.com> wrote:


    On Tuesday, February 11, 2014 6:41:00 PM UTC+1, √iktor Klang wrote:



    On Tue, Feb 11, 2014 at 6:32 PM, Bruno Bieth <bie...@gmail.com> wrote:
    That's my point, you have to find circumvoluted ways to accomodate the for-comprehension. By using flatMap instead of map you've created two more futures and spawned 2 more threads.

    The DefaultPromise is about as cheap as you can make things, so allocating one is trivial.
    As for the "spawned 2 more threads." I don't understand what you mean.

    def doA(a: A): Future[B] = Future( reallyDoA )(blockingEC1)
    def doB(b: B): Future[C] = Future( reallyDoB )(blockingEC2)

    getA().flatMap(doA(a)) // one thread for flat map
             .flatMap(doB(b)) // one thread for flat map = 2 more threads

    getA().map( reallyDoA )(blockingEC1).map( reallyDoB )(blockingEC2) // no overhead

    The flapMap operation, even though cheap, has to run through an executor.

    Yes, that's the entire point. If you don't want to do things async, just do it directly.

    I think you missed the point.


    In the code above, there is exactly _nothing_ that dictate how many java.lang.Threads will be used–That is _completely_ at the discretion of the _implementations_ of the ECs—the ECs might just delegate to the same underlying Executor for all we know
    If you _decide_ to pass in ECs that spawn threads that is under _your_ control and responsibility. It is however not hardcoded into the semantics of Futures.

    That is my point.



      getA().map( reallyDoA )(blockingEC1).map( reallyDoB )(blockingEC2) // no overhead

    Is async. It just uses 2 threads less.

    Again–you, the programmer, _choose_ to use those ECs!

    Maybe I'm missing something, but I believe Bruno's point is not how many threads are used, but rather how many times it must go through a queue. 

    And you've made it clear that an EC that runs on the calling thread violates the EC contract, so there's no other implementation than something delegate to another thread, which is disproportionally expensive for the amount of work being done (in this example, a simple map function).

    Alexandru Nedelcu

    unread,
    Feb 12, 2014, 10:51:17 AM2/12/14
    to Bruno Bieth, scala-user
    On Wed, Feb 12, 2014 at 11:31 AM, Bruno Bieth <bie...@gmail.com> wrote:
    As we already discussed, the global EC implementation is written in stone and its configuration almost inexistent (i'm referring to the "scala.concurrent.context.maxThreads" VM argument).
    To me the entire idea of the global EC is to let the programmers use the Future library standalone (i.e without Akka). Look at Derek William answer, which explicitly recommends using the global EC for the flatMap operation.

    Hey Bruno, 

    Personally I'm not using the global EC as an "import", because imports can't be overridden. Quick quiz - do Play2 apps use the same execution context, or do they configure their own?

    Since in projects I always have some form of dependency injection (at present, either Cake or SubCut), I'm injecting it. I even pass it around explicitly, depending on use-case. On reusable stuff that returns Futures, I require it as an extra implicit parameter on the functions in questions, if that makes sense.

    I view the globally configured execution context as a default meant for convenience when experimenting and I do use it by default, unless there's a better default available, but I design my code such that it's not hardwired to it.


    --
    Alexandru Nedelcu
    www.bionicspirit.com

    PGP Public Key:
    https://bionicspirit.com/key.aexpk

    √iktor Ҡlang

    unread,
    Feb 12, 2014, 11:22:17 AM2/12/14
    to Alexandru Nedelcu, Bruno Bieth, scala-user
    Exactly, that's how it's supposed to be done.

    Clearly there is some documentation to be written, and fortunately anyone can submit PRs to suggest improvements.
     


    --
    Alexandru Nedelcu
    www.bionicspirit.com

    PGP Public Key:
    https://bionicspirit.com/key.aexpk

    --
    You received this message because you are subscribed to the Google Groups "scala-user" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.

    Bruno Bieth

    unread,
    Feb 12, 2014, 11:26:27 AM2/12/14
    to scala...@googlegroups.com, √iktor Ҡlang, Bruno Bieth

    And you've made it clear that an EC that runs on the calling thread violates the EC contract, so there's no other implementation than something delegate to another thread, which is disproportionally expensive for the amount of work being done (in this example, a simple map function).

    A simple flatMap function to be precise (the discussion was about how for-comprehension force you into flatMap if you want your ExecutionContext to be passed explicitly), but that's exactly what I meant.

    Viktor is arguing at a higher abstraction level, whereas I'm down to the current state of affair. Anyway, this conversation has reached a state of limbo so I'm moving onto the doc. I'll submit a PR that will hopefully sumup my criticism more clearly :)

    √iktor Ҡlang

    unread,
    Feb 12, 2014, 11:28:06 AM2/12/14
    to Nils Kilden-Pedersen, Bruno Bieth, scala-user
    That makes more sense, but he insisted on threads so I'm not sure we can assume that.
     

    And you've made it clear that an EC that runs on the calling thread violates the EC contract,

    You're oversimplifying, the contract is that it cannot run synchronously on the current thread to be a generic ExecutionContext.
    If the current thread is within the given EC and that EC decides to enqueue the runnable to run _after_ the current task finishes, then that is completely fine, see: https://github.com/scala/scala/blob/master/src/library/scala/concurrent/BatchingExecutor.scala

    Also, if you know that the thing that uses the EC is safe to run synchronously then you can of course use a synchronous version for that, but naturally if things break it's your own fault. (It's your code after all)
     
    so there's no other implementation than something delegate to another thread, which is disproportionally expensive for the amount of work being done (in this example, a simple map function).

    See above.

    Haoyi Li

    unread,
    Feb 12, 2014, 12:22:28 PM2/12/14
    to √iktor Ҡlang, Nils Kilden-Pedersen, Bruno Bieth, scala-user
    Also, if you know that the thing that uses the EC is safe to run synchronously then you can of course use a synchronous version for that, but naturally if things break it's your own fault. (It's your code after all)

    This is exactly the point I was trying to make in another thread, which I think is what Bruno was referring to, which I thought you disagreed with. I got exactly the same feel from that thread as bruno has here (i.e. that you think synchronous ECs are bad). 

    Either we're seriously mis-communicating or I'm super confused @_@

    √iktor Ҡlang

    unread,
    Feb 12, 2014, 12:49:04 PM2/12/14
    to Haoyi Li, scala-user, Nils Kilden-Pedersen, Bruno Bieth

    Synchronous ECs are bad. (You are free to do bad things, if things break, you have only yourself to blame.)

    For more background, see Havocs blogpost that I have referred to earlier.

    Confusion cleared?

    Haoyi Li

    unread,
    Feb 12, 2014, 1:12:17 PM2/12/14
    to √iktor Ҡlang, scala-user, Nils Kilden-Pedersen, Bruno Bieth
    Yep, that makes sense. Thanks!

    charlie robert

    unread,
    Feb 12, 2014, 9:07:40 PM2/12/14
    to √iktor Ҡlang, Haoyi Li, scala-user, Nils Kilden-Pedersen, Bruno Bieth
    I am mostly following the discussion, but I am unsure how to construct a synchronous EC.  Could someone show the code that builds one?

    thanks,
    Rob
    - charlie




    charlie robert

    unread,
    Feb 12, 2014, 9:20:46 PM2/12/14
    to Charlie Robert, √iktor Ҡlang, Haoyi Li, scala-user, Nils Kilden-Pedersen, Bruno Bieth

    On Wed, Feb 12, 2014 at 8:28 AM, √iktor Ҡlang <viktor...@gmail.com> wrote:

    You're oversimplifying, the contract is that it cannot run synchronously on the current thread to be a generic ExecutionContext.
    If the current thread is within the given EC and that EC decides to enqueue the runnable to run _after_ the current task finishes, then that is completely fine, see: https://github.com/scala/scala/blob/master/src/library/scala/concurrent/BatchingExecutor.scala

    Also, if you know that the thing that uses the EC is safe to run synchronously then you can of course use a synchronous version for that, but naturally if things break it's your own fault. (It's your code after all)
     

    Actually, I don’t want a synchronous EC, I want to use this EC, but with a way to block tasks and still keep liveness.  I also was unsure how I could create an EC with one of these executors.  It looks private to me.

    Thank you,
    Rob

    √iktor Ҡlang

    unread,
    Feb 12, 2014, 9:42:58 PM2/12/14
    to charlie robert, Haoyi Li, scala-user, Nils Kilden-Pedersen, Bruno Bieth
    What definition of "block" are you using in the sentence above?
     

    Thank you,
    Rob

    charlie robert

    unread,
    Feb 12, 2014, 10:02:06 PM2/12/14
    to √iktor Ҡlang, Haoyi Li, scala-user, Nils Kilden-Pedersen, Bruno Bieth
    Whatever I can get.  Ultimately if a java wait or entry into a synchronized block of code would be sweet, to allow any code to run.  I figure its not possible to do this without changing the threading in the JVM, but still how could it be done, I am quite curious.  Getting a scala Await would be nice, where the future goes off in batched mode, and the Await call doesn’t block the Executor.

    val future = Future[Double] { longRunningMethod() }
    Await.result(future, Duration.fromNanos(1))

    cheers,
    Rob

     

    Thank you,
    Rob



    --
    Cheers,

    ———————
    Viktor Klang
    Chief Architect - Typesafe

    Twitter: @viktorklang

    --
    You received this message because you are subscribed to the Google Groups "scala-user" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.

    - charlie




    Brian Schlining

    unread,
    Feb 12, 2014, 10:07:22 PM2/12/14
    to charlie robert, Haoyi Li, √iktor Ҡlang, scala-user, Bruno Bieth, Nils Kilden-Pedersen


    Actually, I don’t want a synchronous EC, I want to use this EC, but with a way to block tasks and still keep liveness.  I also was unsure how I could create an EC with one of these executors.  It looks private to me.



    If you want to be able to block (i.e stop) execution you can construct an Executor based around one of Java’s queues, like ArrayDeque or ArrayBlockingQueue. Take a look at the SerialExecutor in the javadocs at http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html, it should be trivial to add a method to turn on and off execution. You can create an ExecutionContext from a java Executor using ‘ExecutionContext.fromExecutor’.

    Cheers

    -- 
    Brian Schlining

    charlie robert

    unread,
    Feb 12, 2014, 11:41:35 PM2/12/14
    to Brian Schlining, Haoyi Li, √iktor Ҡlang, scala-user, Bruno Bieth, Nils Kilden-Pedersen
    On Feb 12, 2014, at 8:07 PM, Brian Schlining <bschl...@gmail.com> wrote:



    Actually, I don’t want a synchronous EC, I want to use this EC, but with a way to block tasks and still keep liveness.  I also was unsure how I could create an EC with one of these executors.  It looks private to me.



    If you want to be able to block (i.e stop) execution you can construct an Executor based around one of Java’s queues, like ArrayDeque or ArrayBlockingQueue. Take a look at the SerialExecutor in the javadocs at http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html, it should be trivial to add a method to turn on and off execution.

    I want the executor to exhibit liveness when a wait() is called in the run() method.  I heard fibers may be a possibility, but i do not know if that is accessible on the JVM - I am doubting it.

    You can create an ExecutionContext from a java Executor using ‘ExecutionContext.fromExecutor’.

    I was curious whether I could instantiate the private Executor class.

    cheers,
    Rob

    Alexandru Nedelcu

    unread,
    Feb 13, 2014, 1:51:22 AM2/13/14
    to charlie robert, √iktor Ҡlang, Haoyi Li, scala-user, Nils Kilden-Pedersen, Bruno Bieth

    On Thu, Feb 13, 2014 at 5:02 AM, charlie robert <charlie...@icloud.com> wrote:

    Whatever I can get.  Ultimately if a java wait or entry into a synchronized block of code would be sweet, to allow any code to run.  I figure its not possible to do this without changing the threading in the JVM, but still how could it be done, I am quite curious.  Getting a scala Await would be nice, where the future goes off in batched mode, and the Await call doesn’t block the Executor.

    Fibers are in general a language-level feature. Scala has something similar called Scala Async, based on macros and targeted for release in Scala 2.11, but you can use it as a library in Scala 2.10. It’s very similar to C#’s async support. I’ve been using it lately and haven’t had problems with it.

    See the SIP here: http://docs.scala-lang.org/sips/pending/async.html
    The repo is here: https://github.com/scala/async

    You can then run code that looks like this (including type annotations for you to see what’s going on):

    def slowCalcFunc: Future[Int] = ???
    
    val result: Future[Int] = async {
      val num1: Int = await(slowCalcFunc)
      val num2: Int = await(slowCalcFunc)
      num1 + num2
    }
    

    So await() is basically a function that takes a Future[T] and returns a T, very similar to what Await.result does, except that it does so in the context of an async block. The compiler, using the macros support in Scala 2.10, will then rewrite that code into a bunch of flatMap and map statements (don’t really know what it does actually, maybe it uses onComplete directly - but that’s of no consequence), the code above being equivalent to this:

    for (num1 <- slowCalcFunc; num2 <- slowCalcFunc) yield num1 + num2
    

    Basically Scala Async allows you to write code with asynchronous calls without having to deal with callbacks, maps and flatMaps. It’s pretty cool in practice.

    Now, if you were thinking of somehow transforming things like Thread.sleep, Object.wait and so on, such that third-party code that blocks can behave in an asynchronous manner, well that doesn’t make sense and it would be pretty awful even if possible.

    Cheers,

    Bruno Bieth

    unread,
    Feb 17, 2014, 1:26:21 PM2/17/14
    to scala...@googlegroups.com, √iktor Ҡlang, Bruno Bieth
    Here is the PR: https://github.com/scala/scala.github.com/pull/302
    And I've also raised an issue concerning the global ExecutionContext fallback: https://issues.scala-lang.org/browse/SI-8278

    √iktor Ҡlang

    unread,
    Feb 17, 2014, 1:29:14 PM2/17/14
    to Bruno Bieth, scala-user
    Thanks, I'll comment on the PR!

    √iktor Ҡlang

    unread,
    Feb 17, 2014, 2:59:18 PM2/17/14
    to Bruno Bieth, scala-user
    Great contribution, I've commented on what I found to be possible to improve.

    √iktor Ҡlang

    unread,
    Feb 17, 2014, 6:11:50 PM2/17/14
    to Bruno Bieth, scala-user
    To be honest, I think this documentation PR would be better targeted as ScalaDoc for ExecutionContext, Future etc.

    Bruno Bieth

    unread,
    Feb 18, 2014, 2:21:13 AM2/18/14
    to scala...@googlegroups.com, Bruno Bieth
    Thanks for reviewing.
    I believe the two doc aren't necessarily mutually exclusive, but I agree that some bits could be moved over.
    I'll submit a PR in the scala repo.
    Reply all
    Reply to author
    Forward
    0 new messages