Skip to content

Proxy Futures

Last updated 4 October 2026

This guide walks through the use of the Store.future() interface and associated ProxyFuture.

Note

Some familiarity with ProxyStore is assumed. Check out the Get Started guide and Concepts page to learn more about ProxyStore's core concepts.

The ProxyFuture interface enables a data producer to preemptively send a proxy to a data consumer before the target data has been created. The consumer of the target data proxy will block when the proxy is first used and resolved until the producer has created the target data.

Here is a trivial example using a Store and LocalConnector. The future.proxy() method is used to create a Proxy which will resolve to the result of the future.

example.py
from proxystore.connectors.local import LocalConnector
from proxystore.store import Store
from proxystore.store.future import ProxyFuture

with Store(LocalConnector()) as store:
    future: ProxyFuture[str] = store.future()
    proxy = future.proxy()

    future.set_result('value')
    assert future.result() == 'value'
    assert proxy == 'value'

Info

Not all Connector implementations are compatible with the Store.future() interface. The Connector instance used to initialize the Store must also implement the DeferrableConnector protocol. A NotImplementedError will be raised when calling Store.future() if the connector is not an instance of DeferrableConnector. Many of the out-of-the-box implementations implement the DeferrableConnector protocol such as the EndpointConnector, FileConnector, and RedisConnector.

The power of ProxyFuture comes when the data producer and consumer are executing independently in time and space (i.e., execution occurs in different processes, potentially on different systems, and in an undefined order). The ProxyFuture enables the producer and consumer to share a data dependency, while allowing the consumer to eagerly start execution before the data dependencies are fully satisfied.

Consider the following example where we have a client which invokes two functions, foo() and bar() on remote processes. foo() will produce an object needed by bar(), but we want to start executing foo() and bar() at the same time. (We could even start bar() before foo()!)

client.py
from proxystore.connectors.redis import RedisConnector
from proxystore.store import Store
from proxystore.store.future import ProxyFuture


class MyData: ...


def foo(future: ProxyFuture[MyData]) -> None:
    data: MyData = compute(...)
    future.set_result(data)


def bar(data: MyData) -> None:
    # Computation not involving data can execute freely.
    compute(...)
    # Computation using data will block until foo
    # sets the result of the future.
    compute(data)


with Store(RedisConnector(...)) as store:
    future: ProxyFuture[MyData] = store.future()

    # The invoke_remote function will execute the function with
    # the provided arguments on an arbitrary remote process.
    foo_result_future = invoke_remote(foo, future)
    bar_result_future = invoke_remote(bar, future.proxy())

    # Wait on the functions to finish executing.
    foo_result_future.result()
    bar_result_future.result()

In this example, foo() and bar() started executing at the same time. This allows bar() to eagerly execute code which does not depend on the data produced by foo(). bar() will only block once the data is needed by the computation.

Timeouts and Exceptions

By default, ProxyFuture.result() and resolving the proxy of a future wait forever for the result to be set. The PollingPolicy controls how often the store is polled for the result and the default timeout. A timeout can also be passed to result() which raises a TimeoutError if the result is not set in time.

1
2
3
4
from proxystore.store.future import PollingPolicy

future = store.future(polling=PollingPolicy(interval=0.1, timeout=60))
result = future.result(timeout=10)

If the producer fails, it can set an exception on the future with set_exception() rather than leaving the consumer waiting. The exception is raised by result(), and resolving the proxy of the future raises a ProxyResolveError caused by the exception. The exception is stored as the result of the future using the serializer of the store, so any result which is an exception is raised in the same way, including one set with set_result().

1
2
3
4
5
6
7
def foo(future: ProxyFuture[MyData]) -> None:
    try:
        data: MyData = compute(...)
    except Exception as e:
        future.set_exception(e)
    else:
        future.set_result(data)