Search results

There are no results.

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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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

std.drop.

Drop

impl Drop for Future