This article discusses the benefits of using
promises::future_promise() over a combination of
future::future() + promises::promise() to
better take advantage of computing resources available to your main R
session. To demonstrate these benefits, we’ll walk-through a use-case
with the plumber package. (See here to learn more about
plumber and the
previous article to learn more about future.)
future()+promise()In an ideal situation, the number of available future
workers (future::nbrOfFreeWorkers()) is always
more than the number of future::future()
jobs. However, if a future job is attempted when
the number of free workers is 0, then future
will block the current R session until one becomes available.
For a concrete example, let’s imagine a scenario, where seven
plumber requests are received at the same time with only
two future workers available. Also, let’s assume the
plumber route(s) serving the first 6 requests use
future::future() and take ~10s to compute
slow_calc():
Let’s also assume the plumber route serving the last
request does not use any form of future or
promises and takes almost no time to compute.
The figure below depicts the overall timeline of execution of these 7
requests under the conditions we’ve outlined above. Note that the y-axis
is ordered from first request coming in (/slow/1) to the
last request (/fast/7).
Note how R has to wait 20s before processing the 7th request (shown
in green). This is a big improvement over not using
future+promises at all (in that case, R would
have to wait 60s before processing). However, since there are only two
future workers available R still has to wait longer than
necessary to process that last request because the main R session must
wait for a future worker to become available. The video
below animates this behavior:
future_promise()The advantage of using future_promise() over
future::future() is that even if there aren’t
future workers available, the future is
scheduled to be done when workers become available via
promises. In other words, future_promise()
ensures the main R thread isn’t blocked when a future job
is requested and can’t immediately perform the work (i.e., the number of
jobs exceeds the number of workers).
Continuing with the example above, we can swap out the calls to
future::future() with future_promise().
With this change to future_promise(), note how the
/fast/7 route now does not have to wait on
future work to finish processing. Therefore,
plumber can complete the last requests almost
immediately:
The vertical gray bars in the figure above represent timepoints where
the main R session is actually busy. Outside of these gray areas, the R
session is free to do other things, for example, executing other
promises or, more generally, non-future work.
The video below animates this behavior: