Creates a simple, noncancelable F[A]
instance that
executes an asynchronous process on evaluation.
Creates a simple, noncancelable F[A]
instance that
executes an asynchronous process on evaluation.
The given function is being injected with a side-effectful callback for signaling the final result of an asynchronous process.
is a function that should be called with a callback for signaling the result once it is ready
A generalized version of bracket which uses ExitCase to distinguish between different exit cases when releasing the acquired resource.
A generalized version of bracket which uses ExitCase to distinguish between different exit cases when releasing the acquired resource.
is an action that "acquires" some expensive resource, that needs to be used and then discarded
is the action that uses the newly allocated resource and that will provide the final result
is the action that's supposed to release the
allocated resource after use
is done, by observing
and acting on its exit condition
Creates a cancelable F[A]
instance that executes an
asynchronous process on evaluation.
Creates a cancelable F[A]
instance that executes an
asynchronous process on evaluation.
This builder accepts a registration function that is being injected with a side-effectful callback, to be called when the asynchronous process is complete with a final result.
The registration function is also supposed to return
an IO[Unit]
that captures the logic necessary for
canceling the asynchronous process, for as long as it
is still active.
Example:
import java.util.concurrent.ScheduledExecutorService import scala.concurrent.duration._ def sleep[F[_]](d: FiniteDuration) (implicit F: Concurrent[F], ec: ScheduledExecutorService): F[Unit] = { F.cancelable { cb => // Note the callback is pure, so we need to trigger evaluation val run = new Runnable { def run() = cb(Right(())) } // Schedules task to run after delay val future = ec.schedule(run, d.length, d.unit) // Cancellation logic, suspended in IO IO(future.cancel(true)) } }
Returns a new F
value that mirrors the source for normal
termination, but that triggers the given error on cancellation.
Returns a new F
value that mirrors the source for normal
termination, but that triggers the given error on cancellation.
This onCancelRaiseError
operator transforms any task into one
that on cancellation will terminate with the given error, thus
transforming potentially non-terminating tasks into ones that
yield a certain error.
import scala.concurrent.CancellationException val F = Concurrent[IO] val timer = Timer[IO] val error = new CancellationException("Boo!") val fa = F.onCancelRaiseError(timer.sleep(5.seconds, error)) fa.start.flatMap { fiber => fiber.cancel *> fiber.join }
Without "onCancelRaiseError" the sleep operation
yields a non-terminating task on cancellation. But by applying
"onCancelRaiseError", the yielded task above will terminate with
the indicated "CancellationException" reference, which we can
then also distinguish from other errors thrown in the F
context.
Depending on the implementation, tasks that are canceled can become non-terminating. This operation ensures that when cancellation happens, the resulting task is terminated with an error, such that logic can be scheduled to happen after cancellation:
import scala.concurrent.CancellationException val wasCanceled = new CancellationException() F.onCancelRaiseError(fa, wasCanceled).attempt.flatMap { case Right(a) => F.delay(println(s"Success: $a")) case Left(`wasCanceled`) => F.delay(println("Was canceled!")) case Left(error) => F.delay(println(s"Terminated in error: $error")) }
This technique is how a "bracket" operation can be implemented.
Besides sending the cancellation signal, this operation also cuts the connection between the producer and the consumer. Example:
val F = Concurrent[IO] val timer = Timer[IO] // This task is uninterruptible ;-) val tick = F.uncancelable( for { _ <- timer.sleep(5.seconds) _ <- IO(println("Tick!")) } yield ()) // Builds an value that triggers an exception on cancellation val loud = F.onCancelRaiseError(tick, new CancellationException)
In this example the loud
reference will be completed with a
"CancellationException", as indicated via "onCancelRaiseError".
The logic of the source won't get canceled, because we've
embedded it all in uncancelable. But its bind continuation is
not allowed to continue after that, its final result not being
allowed to be signaled.
Therefore this also transforms uncancelable values into ones
that can be canceled. The logic of the source, its run-loop
might not be interruptible, however cancel
on a value on which
onCancelRaiseError
was applied will cut the connection from
the producer, the consumer receiving the indicated error instead.
Run two tasks concurrently, creating a race between them and returns a pair containing both the winner's successful value and the loser represented as a still-unfinished fiber.
Run two tasks concurrently, creating a race between them and returns a pair containing both the winner's successful value and the loser represented as a still-unfinished fiber.
If the first task completes in error, then the result will complete in error, the other task being canceled.
On usage the user has the option of canceling the losing task, this being equivalent with plain race:
val ioA: IO[A] = ??? val ioB: IO[B] = ??? Concurrent[IO].racePair(ioA, ioB).flatMap { case Left((a, fiberB)) => fiberB.cancel.map(_ => a) case Right((fiberA, b)) => fiberA.cancel.map(_ => b) }
See race for a simpler version that cancels the loser immediately.
Start concurrent execution of the source suspended in
the F
context.
Start concurrent execution of the source suspended in
the F
context.
Returns a Fiber that can be used to either join or cancel the running computation, being similar in spirit (but not in implementation) to starting a thread.
Suspends the evaluation of an F
reference.
Suspends the evaluation of an F
reference.
Equivalent to FlatMap.flatten
for pure expressions,
the purpose of this function is to suspend side effects
in F
.
Returns a new F
that mirrors the source, but that is uninterruptible.
Returns a new F
that mirrors the source, but that is uninterruptible.
This means that the cancel signal has no effect on the result of join and that the cancelable token returned by ConcurrentEffect.runCancelable on evaluation will have no effect.
This operation is undoing the cancellation mechanism of cancelable, with this equivalence:
F.uncancelable(F.cancelable { cb => f(cb); io }) <-> F.async(f)
Sample:
val F = Concurrent[IO] val timer = Timer[IO] // Normally Timer#sleep yields cancelable tasks val tick = F.uncancelable(timer.sleep(10.seconds)) // This prints "Tick!" after 10 seconds, even if we are // canceling the Fiber after start: for { fiber <- F.start(tick) _ <- fiber.cancel _ <- fiber.join } yield { println("Tick!") }
Cancelable effects are great in race conditions, however sometimes
this operation is necessary to ensure that the bind continuation
of a task (the following flatMap
operations) are also evaluated
no matter what.
Operation meant for specifying tasks with safe resource acquisition and release in the face of errors and interruption.
Operation meant for specifying tasks with safe resource acquisition and release in the face of errors and interruption.
This operation provides the equivalent of try/catch/finally
statements in mainstream imperative languages for resource
acquisition and release.
is an action that "acquires" some expensive resource, that needs to be used and then discarded
is the action that uses the newly allocated resource and that will provide the final result
is the action that's supposed to release the
allocated resource after use
is done, irregardless of
its exit condition
Lifts any by-name parameter into the F
context.
Lifts any by-name parameter into the F
context.
Equivalent to Applicative.pure
for pure expressions,
the purpose of this function is to suspend side effects
in F
.
Inherited from LiftIO, defines a conversion from IO
in terms of the Concurrent
type class.
Inherited from LiftIO, defines a conversion from IO
in terms of the Concurrent
type class.
N.B. expressing this conversion in terms of Concurrent
and
its capabilities means that the resulting F
is cancelable in
case the source IO
is.
To access this implementation as a standalone function, you can use Concurrent.liftIO (on the object companion).
Returns a non-terminating F[_]
, that never completes
with a result, being equivalent to async(_ => ())
Returns a non-terminating F[_]
, that never completes
with a result, being equivalent to async(_ => ())
Run two tasks concurrently and return the first to finish, either in success or error.
Run two tasks concurrently and return the first to finish, either in success or error. The loser of the race is canceled.
The two tasks are potentially executed in parallel, the winner being the first that signals a result.
As an example see Concurrent.timeoutTo
Also see racePair for a version that does not cancel the loser automatically on successful results.
(Since version 1.0.0-RC2) Use *> or productR instead.
(Since version 1.0.0-RC2) Use productREval instead.
(Since version 1.0.0-RC2) Use <* or productL instead.
(Since version 1.0.0-RC2) Use productLEval instead.
Type class for Async data types that are cancelable and can be started concurrently.
Thus this type class allows abstracting over data types that:
Due to these restrictions, this type class also affords to describe a start operation that can start async processing, suspended in the context of
F[_]
and that can be canceled or joined.Without cancellation being baked in, we couldn't afford to do it. See below.
Cancelable builder
The signature exposed by the cancelable builder is this:
IO[Unit]
is used to represent a cancellation action which will send a signal to the producer, that may observe it and cancel the asynchronous process.On Cancellation
Simple asynchronous processes, like Scala's
Future
, can be described with this very basic and side-effectful type and you should recognize what is more or less the signature ofFuture#onComplete
or of Async.async (minus the error handling):But many times the abstractions built to deal with asynchronous tasks can also provide a way to cancel such processes, to be used in race conditions in order to cleanup resources early, so a very basic and side-effectful definition of asynchronous processes that can be canceled would be:
This is approximately the signature of JavaScript's
setTimeout
, which will return a "task ID" that can be used to cancel it. Or of Java'sScheduledExecutorService#schedule
, which will return a JavaScheduledFuture
that has a.cancel()
operation on it.Similarly, for
Concurrent
data types, we can provide cancellation logic, that can be triggered in race conditions to cancel the on-going processing, only thatConcurrent
's cancelable token is an action suspended in anIO[Unit]
. See IO.cancelable.Suppose you want to describe a "sleep" operation, like that described by Timer to mirror Java's
ScheduledExecutorService.schedule
or JavaScript'ssetTimeout
:This signature is in fact incomplete for data types that are not cancelable, because such equivalent operations always return some cancellation token that can be used to trigger a forceful interruption of the timer. This is not a normal "dispose" or "finally" clause in a try/catch block, because "cancel" in the context of an asynchronous process is concurrent with the task's own run-loop.
To understand what this means, consider that in the case of our
sleep
as described above, on cancellation we'd need a way to signal to the underlyingScheduledExecutorService
to forcefully remove the scheduledRunnable
from its internal queue of scheduled tasks, before its execution. Therefore, without a cancelable data type, a safe signature needs to return a cancellation token, so it would look like this:This function is returning a tuple, with one
F[Unit]
to wait for the completion of our sleep and a secondF[Unit]
to cancel the scheduled computation in case we need it. This is in fact the shape of Fiber's API. And this is exactly what the start operation returns.The difference between a Concurrent data type and one that is only Async is that you can go from any
F[A]
to aF[Fiber[F, A]]
, to participate in race conditions and that can be canceled should the need arise, in order to trigger an early release of allocated resources.Thus a Concurrent data type can safely participate in race conditions, whereas a data type that is only Async cannot do it without exposing and forcing the user to work with cancellation tokens. An Async data type cannot expose for example a
start
operation that is safe.