Skip to content

SimFuture

Futures for cross-entity coordination. Generators yield futures to park until resolved.

SimFuture — yield on events, not just delays.

SimFuture enables generators to pause until an external condition is met, rather than only pausing for fixed time delays. When a generator yields a SimFuture, the process parks until another entity calls future.resolve(value).

This unlocks natural request-response modeling, resource acquisition, lock waiting, timeout races (any_of), and quorum waits (all_of).

Example::

class Client(Entity):
    def handle_event(self, event):
        future = SimFuture()
        # Send request with the future so the server can resolve it
        yield (
            0.0,
            [
                Event(
                    time=self.now,
                    event_type="Request",
                    target=self.server,
                    context={"reply_future": future},
                )
            ],
        )
        response = yield future  # Park until server resolves
        # response is the value passed to future.resolve(value)


class Server(Entity):
    def handle_event(self, event):
        yield 0.1  # Processing time
        event.context["reply_future"].resolve({"status": "ok"})

SimFuture

SimFuture()

A future that generators can yield to park until resolved.

When a generator yields a SimFuture, the ProcessContinuation parks instead of scheduling a time-based resume. The generator stays suspended until some other entity calls resolve(value), at which point a new continuation is scheduled to resume the generator with the resolved value via gen.send(value).

Each SimFuture can only be yielded by one generator. For broadcast patterns, create separate futures per consumer.

Attributes:

Name Type Description
is_resolved bool

True if the future has been resolved.

value Any

The resolved value (raises RuntimeError if not yet resolved).

is_resolved property

is_resolved: bool

Whether this future has been resolved.

value property

value: Any

The resolved value.

Raises:

Type Description
RuntimeError

If the future hasn't been resolved yet.

resolve

resolve(value: Any = None) -> None

Resolve the future with a value, resuming the parked generator.

The parked generator will be resumed at the current simulation time with the value sent via gen.send(value). If no generator is parked yet, the value is stored and the generator will resume immediately when it yields this future.

Resolving an already-resolved future is a no-op.

Parameters:

Name Type Description Default
value Any

The value to send into the generator.

None

any_of

any_of(*futures: SimFuture) -> SimFuture

Return a future that resolves when ANY input future resolves.

The composite future resolves with a tuple (index, value) where index is the position of the first future to resolve and value is its resolved value.

This enables timeout races and first-to-complete patterns::

timeout = SimFuture()
# Schedule a timeout event that resolves timeout future
yield (
    0.0,
    [
        Event.once(
            time=self.now + 5.0,
            event_type="Timeout",
            fn=lambda e: timeout.resolve("timeout"),
        )
    ],
)

response = SimFuture()
# Send request with response future
yield (
    0.0,
    [
        Event(
            time=self.now,
            event_type="Request",
            target=server,
            context={"reply_future": response},
        )
    ],
)

idx, value = yield any_of(timeout, response)
if idx == 0:
    print("Timed out!")
else:
    print(f"Got response: {value}")

Parameters:

Name Type Description Default
*futures SimFuture

Two or more SimFuture instances to race.

()

Returns:

Type Description
SimFuture

A new SimFuture that resolves when any input resolves.

all_of

all_of(*futures: SimFuture) -> SimFuture

Return a future that resolves when ALL input futures resolve.

The composite future resolves with a list of values in the same order as the input futures.

This enables quorum waits and barrier patterns::

ack1, ack2, ack3 = SimFuture(), SimFuture(), SimFuture()
# Send requests to three replicas
yield (
    0.0,
    [
        Event(time=self.now, event_type="Write", target=replica1, context={"ack": ack1}),
        Event(time=self.now, event_type="Write", target=replica2, context={"ack": ack2}),
        Event(time=self.now, event_type="Write", target=replica3, context={"ack": ack3}),
    ],
)
results = yield all_of(ack1, ack2, ack3)
# results = [value1, value2, value3]

Parameters:

Name Type Description Default
*futures SimFuture

Two or more SimFuture instances to wait on.

()

Returns:

Type Description
SimFuture

A new SimFuture that resolves when all inputs resolve.