std.sync.Future
type pub Future[T]
A proxy value to resolve into the result of some asynchronous operation.
The value of a Future
is set by its corresponding Promise
.
A Future[T]
is resolved into its T
using one of the following methods:
Future.get
Future.try_get
Future.get_until
Static methods
new
Show source codeHide source code
fn pub static new -> (uni Future[uni T], uni Promise[uni T]) {
let fut: FutureState[uni T] = FutureState(
waiter: NO_WAITER as Pointer[UInt8],
locked: UNLOCKED as UInt8,
status: Status.Connected,
value: Option.None,
)
# The `Future` and `Promise` need shared access of the underlying data. This
# technically violates Inko's single-ownership rules, so to allow that we
# cast the state reference to an address, then cast that back where
# necessary.
#
# This is of course highly unsafe, but it's how this particular sausage is
# made.
let fut = fut as UInt64
(recover Future(fut), recover Promise(fut))
}
fn pub static new -> (uni Future[uni T], uni Promise[uni T])
Returns a new Future
along with its corresponding Promise
.
The Future
and Promise
are returned as unique references, allowing them
to be moved between processes.
Examples
import std.sync (Future)
match Future.new {
case (future, promise) -> {
promise.set(42)
future.get # => 42
}
}
Instance methods
get
Show source codeHide source code
fn pub move get -> uni T {
loop {
let fut = lock
match fut.value := Option.None {
case Some(val) -> {
fut.unlock
# Ensure the shared state isn't dropped.
_INKO.moved(fut)
return val
}
case _ -> {
fut.waiter = _INKO.process
# This atomically changes the process status, unlocks the future lock
# and yields back to the scheduler.
inko_process_wait_for_value(
_INKO.process,
mut fut.locked,
LOCKED as UInt8,
UNLOCKED as UInt8,
)
# Ensure the shared state isn't dropped.
_INKO.moved(fut)
}
}
}
}
fn pub move get -> uni T
Returns the value of the Future
, blocking the calling process until a
value is available.
This method consumes the Future
, ensuring a value can only be resolved
once.
Deadlocks
If a Promise
is dropped before a call to Future.get
or while the
Future
waits for a value to be written, the calling process of
Future.get
will deadlock. This method makes no attempt at detecting such
cases as doing so is notoriously difficult.
To avoid a deadlock, make sure to always write a value to a Promise
before discarding it, or use Future.get_until
to wait using a deadline.
Examples
import std.sync (Future)
match Future.new {
case (future, promise) -> {
promise.set(42)
future.get # => 42
}
}
get_until
Show source codeHide source code
fn pub move get_until[D: ToInstant](deadline: ref D) -> Option[uni T] {
let nanos = deadline.to_instant.to_int as UInt64
loop {
let fut = lock
match fut.value := Option.None {
case Some(val) -> {
fut.unlock
# Ensure the shared state isn't dropped.
_INKO.moved(fut)
return Option.Some(val)
}
case _ -> {
fut.waiter = _INKO.process
# This atomically changes the process status, unlocks the future lock
# and yields back to the scheduler.
let timed_out = inko_process_wait_for_value_until(
_INKO.state,
_INKO.process,
mut fut.locked,
LOCKED as UInt8,
UNLOCKED as UInt8,
nanos,
)
# Ensure the shared state isn't dropped.
_INKO.moved(fut)
if timed_out { break }
}
}
}
# It's possible for a write to happen _just_ after we time out. We don't
# want to silently discard the value in that case. In addition, it's
# possible for a value to be written after returning from this method, which
# would result in the value also being lost.
#
# To prevent this from happening we disconnect the future immediately and
# perform a final check to see if a value is present. This ensures that
# beyond this point any values written using `Promise.set` are returned to
# the caller, instead of just being dropped.
let fut = lock
match fut.status {
case Connected -> fut.status = Status.NoFuture
case _ -> {}
}
let val = fut.value := Option.None
fut.unlock
# Ensure the shared state isn't dropped.
_INKO.moved(fut)
val
}
fn pub move get_until[D: ToInstant](deadline: ref D) -> Option[uni T]
Returns the value of the future, blocking the calling process until a value is available or the given deadline is exceeded.
If a value is resolved within the deadline, an Option.Some
containing the
value is returned. If the timeout expired, an Option.None
is returned.
In both cases self
is consumed. This is because trying to wait for a
result is inherently racy, and may result in unexpected results. For
example, if a value were to be written using Promise.set
just after we
return from this method, we wouldn't observe it unless the operation is
retried. If we don't do so, the value would be dropped.
However, it's more often than not clear how often the operation should be
retried, as the time waited might not necessarily be the same or longer as
the time it takes before Promise.set
is called.
Always consuming self
instead forces the caller to create a new Promise
and Future
pair if a retry is desired, and ensures that if
Promise.set
is called after returning from this method the value passed
to Promise.set
is returned to its caller.
Deadlocks
Unlike Future.get
, this method can't deadlock a calling process forever
due to the use of a deadline. However, if the Promise
is dropped before or
during a call to Future.get_until
, the calling process will be suspended
until the deadline expires.
Examples
import std.sync (Future)
import std.time (Duration)
match Future.new {
case (future, promise) -> {
promise.set(42)
future.get_until(Duration.from_secs(1)) # => Option.Some(42)
}
}
try_get
Show source codeHide source code
fn pub move try_get -> Result[uni T, Future[T]] {
let fut = lock
let val = fut.value := Option.None
fut.unlock
# Ensure the shared state isn't dropped.
_INKO.moved(fut)
match val {
case Some(v) -> Result.Ok(v)
case _ -> Result.Error(self)
}
}
fn pub move try_get -> Result[uni T, Future[T]]
Returns the value of the future if one is present, without blocking the calling process.
If a value is present, a Result.Ok
is returned containing the value. If no
value is present, a Result.Error
is returned containing a new Future
to
use for resolving the value.
Deadlocks
This method never deadlocks.
Examples
import std.sync (Future)
match Future.new {
case (future, promise) -> {
promise.set(42)
future.try_get # => Result.Ok(42)
}
}
Implemented traits
Drop
impl Drop for Future