Skip to content

proxystore.store.ref

Object ownership and borrowing with proxies.

Warning

These features are experimental and may change in future releases.

This module implements Rust-like ownership and borrowing rules for Python objects in shared memory using transparent object proxies. Thus, these proxy reference types are similar to the type returned by Store.proxy()---they will resolve to an object in the global store. However, these proxy references enforce additional rules.

  1. Each target object of type T in the global store has an associated OwnedProxy[T].
  2. There can only be one OwnedProxy[T] for any target in the global store.
  3. When an OwnedProxy[T] goes out of scope (e.g., gets garbage collected), the associated target is removed from the global store.
Tip

The docstrings often use T to refer to both the target object in the global store and the type of the target object.

An OwnedProxy[T] can be borrowed without relinquishing ownership. This requires two additional rules.

  1. At any given time, you can have either one mutable reference to the target, a RefMutProxy[T], or any number of immutable references, a RefProxy[T].
  2. References must always be valid. I.e., you cannot delete an OwnedProxy[T] while it has been borrowed via a RefProxy[T] or RefMutProxy[T].

All three reference types (OwnedProxy[T], RefProxy[T], and RefMutProxy[T]) behave like an instance of T, forwarding operations on themselves to a locally cached instance of T.

BaseRefProxyError

Bases: Exception

Base exception type for proxy references.

MutableBorrowError

Bases: BaseRefProxyError

Exception raised when violating borrowing rules.

ReferenceNotOwnedError

Bases: BaseRefProxyError

Exception raised when invoking an operation on a non-owned reference.

ReferenceInvalidError

Bases: BaseRefProxyError

Exception raised when a reference instance has been invalidated.

BaseRefProxy

BaseRefProxy(
    factory: FactoryType[T],
    *,
    cache_defaults: bool = False,
    target: T | None = None
)

Bases: Proxy[T]

Base reference proxy type.

This base type adds some features to Proxy that are shared by all the reference types:

  1. Valid flag. When invalidated, the proxy will raise a ReferenceInvalidError when accessing the wrapped target object.
  2. Disable copy() and deepcopy() support to prevent misuse of the API. Generally, borrow() or clone() should be used instead.
Source code in proxystore/store/ref.py
def __init__(
    self,
    factory: FactoryType[T],
    *,
    cache_defaults: bool = False,
    target: T | None = None,
) -> None:
    object.__setattr__(self, '__proxy_valid__', True)
    super().__init__(factory, cache_defaults=cache_defaults, target=target)

OwnedProxy

OwnedProxy(
    factory: FactoryType[T],
    *,
    cache_defaults: bool = False,
    target: T | None = None
)

Bases: BaseRefProxy[T]

Represents ownership over an object in a global store.

This class maintains reference counts of the number of immutable and mutable borrows of this proxy. The target object will be evicted from the store once this proxy goes out of scope (this is handled via __del__ and an atexit handler).

Parameters:

  • factory (FactoryType[T]) –

    StoreFactory used to resolve the target object from the store.

  • cache_defaults (bool, default: False ) –

    Precompute and cache the __proxy_default_class__ and __proxy_default_hash__ attributes of the proxy instance from target. Ignored if target is not provided.

  • target (T | None, default: None ) –

    Optionally preset the target object.

Source code in proxystore/store/ref.py
def __init__(
    self,
    factory: FactoryType[T],
    *,
    cache_defaults: bool = False,
    target: T | None = None,
) -> None:
    object.__setattr__(self, '__proxy_ref_count__', 0)
    object.__setattr__(self, '__proxy_ref_mut_count__', 0)
    object.__setattr__(
        self,
        '__proxy_finalizer__',
        atexit.register(_WeakRefFinalizer(self, '__del__')),
    )
    super().__init__(factory, cache_defaults=cache_defaults, target=target)

RefProxy

RefProxy(
    factory: FactoryType[T],
    *,
    cache_defaults: bool = False,
    owner: OwnedProxy[T] | None = None,
    target: T | None = None
)

Bases: BaseRefProxy[T]

Represents a borrowed reference to an object in the global store.

Parameters:

  • factory (FactoryType[T]) –

    StoreFactory used to resolve the target object from the store.

  • cache_defaults (bool, default: False ) –

    Precompute and cache the __proxy_default_class__ and __proxy_default_hash__ attributes of the proxy instance from target. Ignored if target is not provided.

  • owner (OwnedProxy[T] | None, default: None ) –

    Proxy which has ownership over the target object. This reference will keep the owner alive while this borrowed reference is alive. In the event this borrowed reference was initialized in a different address space from the proxy with ownership, then owner will be None.

  • target (T | None, default: None ) –

    Optionally preset the target object.

Source code in proxystore/store/ref.py
def __init__(
    self,
    factory: FactoryType[T],
    *,
    cache_defaults: bool = False,
    owner: OwnedProxy[T] | None = None,
    target: T | None = None,
) -> None:
    object.__setattr__(self, '__proxy_owner__', owner)
    object.__setattr__(
        self,
        '__proxy_finalizer__',
        atexit.register(_WeakRefFinalizer(self, '__del__')),
    )
    super().__init__(factory, cache_defaults=cache_defaults, target=target)

RefMutProxy

RefMutProxy(
    factory: FactoryType[T],
    *,
    cache_defaults: bool = False,
    owner: OwnedProxy[T] | None = None,
    target: T | None = None
)

Bases: BaseRefProxy[T]

Represents a borrowed mutable reference to an object in the global store.

Parameters:

  • factory (FactoryType[T]) –

    StoreFactory used to resolve the target object from the store.

  • cache_defaults (bool, default: False ) –

    Precompute and cache the __proxy_default_class__ and __proxy_default_hash__ attributes of the proxy instance from target. Ignored if target is not provided.

  • owner (OwnedProxy[T] | None, default: None ) –

    Proxy which has ownership over the target object. This reference will keep the owner alive while this borrowed reference is alive. In the event this borrowed reference was initialized in a different address space from the proxy with ownership, then owner will be None.

  • target (T | None, default: None ) –

    Optionally preset the target object.

Source code in proxystore/store/ref.py
def __init__(
    self,
    factory: FactoryType[T],
    *,
    cache_defaults: bool = False,
    owner: OwnedProxy[T] | None = None,
    target: T | None = None,
) -> None:
    object.__setattr__(self, '__proxy_owner__', owner)
    object.__setattr__(
        self,
        '__proxy_finalizer__',
        atexit.register(_WeakRefFinalizer(self, '__del__')),
    )
    super().__init__(factory, cache_defaults=cache_defaults, target=target)

borrow()

borrow(
    proxy: OwnedProxy[T], *, populate_target: bool = True
) -> RefProxy[T]

Borrow T by creating an immutable reference of T.

Note

This mutates proxy.

Parameters:

  • proxy (OwnedProxy[T]) –

    Proxy reference to borrow.

  • populate_target (bool, default: True ) –

    If the target of proxy has already been resolved, copy the reference to the target into the returned proxy such that the returned proxy is already resolved.

Raises:

Source code in proxystore/store/ref.py
def borrow(
    proxy: OwnedProxy[T],
    *,
    populate_target: bool = True,
) -> RefProxy[T]:
    """Borrow `T` by creating an immutable reference of `T`.

    Note:
        This mutates `proxy`.

    Args:
        proxy: Proxy reference to borrow.
        populate_target: If the target of `proxy` has already been resolved,
            copy the reference to the target into the returned proxy such that
            the returned proxy is already resolved.

    Raises:
        ReferenceNotOwnedError: if `proxy` is not an
            [`OwnedProxy`][proxystore.store.ref.OwnedProxy] instance.
        MutableBorrowError: if `proxy` has already been mutably borrowed.
    """
    if not isinstance(proxy, OwnedProxy):
        raise ReferenceNotOwnedError('Only owned references can be borrowed.')
    if object.__getattribute__(proxy, '__proxy_ref_mut_count__') > 0:
        raise MutableBorrowError('Proxy was already borrowed as mutable.')
    object.__setattr__(
        proxy,
        '__proxy_ref_count__',
        object.__getattribute__(proxy, '__proxy_ref_count__') + 1,
    )
    ref_proxy = RefProxy(proxy.__proxy_factory__, owner=proxy)
    if populate_target:
        _copy_attributes(proxy, ref_proxy)
    return ref_proxy

mut_borrow()

mut_borrow(
    proxy: OwnedProxy[T], *, populate_target: bool = True
) -> RefMutProxy[T]

Mutably borrow T by creating an mutable reference of T.

Note

This mutates proxy.

Parameters:

  • proxy (OwnedProxy[T]) –

    Proxy reference to borrow.

  • populate_target (bool, default: True ) –

    If the target of proxy has already been resolved, copy the reference to the target into the returned proxy such that the returned proxy is already resolved.

Raises:

Source code in proxystore/store/ref.py
def mut_borrow(
    proxy: OwnedProxy[T],
    *,
    populate_target: bool = True,
) -> RefMutProxy[T]:
    """Mutably borrow `T` by creating an mutable reference of `T`.

    Note:
        This mutates `proxy`.

    Args:
        proxy: Proxy reference to borrow.
        populate_target: If the target of `proxy` has already been resolved,
            copy the reference to the target into the returned proxy such that
            the returned proxy is already resolved.

    Raises:
        ReferenceNotOwnedError: if `proxy` is not an
            [`OwnedProxy`][proxystore.store.ref.OwnedProxy] instance.
        MutableBorrowError: if `proxy` has already been borrowed
            (mutable/immutable).
    """
    if not isinstance(proxy, OwnedProxy):
        raise ReferenceNotOwnedError('Only owned references can be borrowed.')
    if object.__getattribute__(proxy, '__proxy_ref_mut_count__') > 0:
        raise MutableBorrowError('Proxy was already borrowed as mutable.')
    if object.__getattribute__(proxy, '__proxy_ref_count__') > 0:
        raise MutableBorrowError('Proxy was already borrowed as immutable.')
    object.__setattr__(
        proxy,
        '__proxy_ref_mut_count__',
        object.__getattribute__(proxy, '__proxy_ref_mut_count__') + 1,
    )
    ref_proxy = RefMutProxy(proxy.__proxy_factory__, owner=proxy)
    if populate_target:
        _copy_attributes(proxy, ref_proxy)
    return ref_proxy

clone()

clone(proxy: OwnedProxy[T]) -> OwnedProxy[T]

Clone the target object.

Creates a new copy of T in the global store. If proxy is in the resolved state, the local version of T belonging to proxy will be deepcopied into the cloned proxy.

Raises:

Source code in proxystore/store/ref.py
def clone(proxy: OwnedProxy[T]) -> OwnedProxy[T]:
    """Clone the target object.

    Creates a new copy of `T` in the global store. If `proxy` is in
    the resolved state, the local version of `T` belonging to `proxy` will
    be deepcopied into the cloned proxy.

    Raises:
        ReferenceNotOwnedError: if `proxy` is not an
            [`OwnedProxy`][proxystore.store.ref.OwnedProxy] instance.
    """
    if not isinstance(proxy, OwnedProxy):
        raise ReferenceNotOwnedError('Only owned references can be cloned.')
    factory = proxy.__proxy_factory__
    store = factory.get_store()
    data = store.connector.get(factory.key)
    new_key = store.connector.put(data)
    new_factory: StoreFactory[Any, T] = StoreFactory(
        new_key,
        store_config=store.config(),
        evict=factory.evict,
        deserializer=factory.deserializer,
    )
    owned = OwnedProxy(new_factory)
    _copy_attributes(proxy, owned, deepcopy=True)
    return owned

into_owned()

into_owned(
    proxy: Proxy[T], *, populate_target: bool = True
) -> OwnedProxy[T]

Convert a basic proxy into an owned proxy.

Warning

It is the caller's responsibility to ensure that proxy has not been copied already.

Note

This will unset the evict flag on the proxy.

Parameters:

  • proxy (Proxy[T]) –

    Proxy reference to borrow.

  • populate_target (bool, default: True ) –

    If the target of proxy has already been resolved, copy the reference to the target into the returned proxy such that the returned proxy is already resolved.

Raises:

Source code in proxystore/store/ref.py
def into_owned(
    proxy: Proxy[T],
    *,
    populate_target: bool = True,
) -> OwnedProxy[T]:
    """Convert a basic proxy into an owned proxy.

    Warning:
        It is the caller's responsibility to ensure that `proxy` has not been
        copied already.

    Note:
        This will unset the `evict` flag on the proxy.

    Args:
        proxy: Proxy reference to borrow.
        populate_target: If the target of `proxy` has already been resolved,
            copy the reference to the target into the returned proxy such that
            the returned proxy is already resolved.

    Raises:
        ValueError: if `proxy` is already a
            [`BaseRefProxy`][proxystore.store.ref.BaseRefProxy] instance.
        ProxyStoreFactoryError: If the proxy's factory is not an instance of
            [`StoreFactory`][proxystore.store.base.StoreFactory].
    """
    if type(proxy) in (OwnedProxy, RefProxy, RefMutProxy):
        # We don't use isinstance to prevent resolving the proxy.
        raise ValueError(
            'Only a base proxy can be converted into an owned proxy.',
        )
    factory = proxy.__proxy_factory__
    if not isinstance(factory, StoreFactory):
        raise ProxyStoreFactoryError(
            'The proxy must contain a factory with type '
            f'{StoreFactory.__name__}. {type(factory).__name__} '
            'is not supported.',
        )
    factory.evict = False
    owned_proxy = OwnedProxy(factory)
    if populate_target:
        _copy_attributes(proxy, owned_proxy)
    return owned_proxy

update()

update(
    proxy: OwnedProxy[T] | RefMutProxy[T],
    *,
    serializer: SerializerT | None = None
) -> None

Update the global copy of the target.

Note

If the proxy has not been resolved, there is nothing to update and this function is a no-op.

Warning

This will not invalidate already cached copies of the global target.

Parameters:

  • proxy (OwnedProxy[T] | RefMutProxy[T]) –

    Proxy containing a modified local copy of the target to use as the new global value.

  • serializer (SerializerT | None, default: None ) –

    Optionally override the default serializer for the store instance when pushing the local copy to the store.

Raises:

Source code in proxystore/store/ref.py
def update(
    proxy: OwnedProxy[T] | RefMutProxy[T],
    *,
    serializer: SerializerT | None = None,
) -> None:
    """Update the global copy of the target.

    Note:
        If the proxy has not been resolved, there is nothing to update and
        this function is a no-op.

    Warning:
        This will not invalidate already cached copies of the global target.

    Args:
        proxy: Proxy containing a modified local copy of the target to use
            as the new global value.
        serializer: Optionally override the default serializer for the
                store instance when pushing the local copy to the store.

    Raises:
        MutableBorrowError: if `proxy` has been mutably borrowed.
        NotImplementedError: if the `connector` of the `store` used to create
            the proxy does not implement the
            [`DeferrableConnector`][proxystore.connectors.protocols.DeferrableConnector]
            protocol.
        ReferenceNotOwnedError: if `proxy` is not an
            [`OwnedProxy`][proxystore.store.ref.OwnedProxy] or
            [`RefMutProxy`][proxystore.store.ref.RefMutProxy] instance.
    """
    if not isinstance(proxy, (OwnedProxy, RefMutProxy)):
        raise ReferenceNotOwnedError('Reference is an immutable borrow.')
    if isinstance(proxy, OwnedProxy) and (
        object.__getattribute__(proxy, '__proxy_ref_mut_count__') > 0
        or object.__getattribute__(proxy, '__proxy_ref_count__') > 0
    ):
        raise MutableBorrowError(
            'OwnedProxy has been borrowed. Cannot mutate.',
        )
    if not is_resolved(proxy):
        return

    store = proxy.__proxy_factory__.get_store()
    try:
        store._set(
            proxy.__proxy_factory__.key,
            proxy.__proxy_wrapped__,
            serializer=serializer,
        )
    except NotImplementedError as e:  # pragma: no cover
        raise NotImplementedError(
            'Mutating the global copy of the value requires a connector '
            'type that supports set().',
        ) from e