Object Lifetimes¶
Last updated 20 April 2024
The Store
, by default, leaves the responsibility of managing shared objects to the application.
For example, a object put into a Store
will persist there until the key is manually evicted.
Some Connectors
, and therefore Stores
, delete all of their objects when closed but this is not a specified requirement of the protocol.
ProxyStore, however, provides optional mechanisms for more automated management of shared objects.
Method | Supports keys? | Supports proxies? |
---|---|---|
Ephemeral Proxies | ✗ | ✓ |
Lifetimes | ✓ | ✓ |
Ownership | ✗ | ✓ |
Note: Currently, these methods are mutually exclusive with each other.
Ephemeral Proxies¶
Setting the evict=True
flag when creating a proxy of an object with
Store.proxy()
,
Store.proxy_batch()
,
Store.proxy_from_key()
, or
Store.locked_proxy()
marks the proxy as ephemeral (one-time use).
The factory will evict the object from the store when the proxy's factory is invoked for the first time to resolve the proxy.
This is useful when the a proxy will be created once and consumed once by an application.
A common side-effect of evict=True
is obscure ProxyResolveMissingKeyError
tracebacks.
This commonly happens when a proxy is unintentionally resolved by another component of the program.
For example, certain serializers may attempt to inspect the proxy to optimize serialization but resolve the proxy in the process, or datastructures like set()
access the __hash__
method of the proxy which will resolve the proxy.
These accidental resolves will automatically evict the target object so later resolves of the proxy will fail.
If you run into these errors, try:
- Enabling
DEBUG
level logging to determine where unintentional proxy resolution is occurring. TheStore
will log everyGET
andEVICT
operation on a key. - Avoid use of datastructures or functions which unnecessarily resolve proxies.
- If avoiding use of the datastructures or functions causing the problem is not possible, consider using the
populate_target=True
flag when creating the proxy. Thepopulate_target
flag will return a proxy that is already resolved so the factory, which would evict the target object, does not need to be called until the proxy is serialized and then deserialized and resolved on a different process. The flag will also cache the class type and hash value of the target such that the proxy can be used in datastructures which rely onhash()
or inisinstance
checks without needing to resolve the proxy.
Lifetimes¶
Shared objects in a Store
can be associated with a Lifetime
.
Lifetimes provide a management mechanism for keeping track of objects and cleaning them up when appropriate.
Contextual Lifetime¶
The ContextLifetime
provides a simple interface for managing shared objects.
Objects added to a Store
can be associated with the lifetime via the lifetime
parameter supported by most Store
methods.
Objects associated with the lifetime are evicted when the lifetime is closed/ended.
- The
ContextLifetime
and all its associated objects must be associated with the sameStore
. - A new key can be automatically associated with a lifetime.
- The target object of a proxy can be automatically associated with a lifetime.
- Ending a lifetime will cause all of its associated objects to be evicted.
- The
Store
should be closed after any associated lifetimes because lifetimes use theStore
for cleanup.
The ContextLifetime
can be used as a context manager.
Contextual Lifetime | |
---|---|
Leased Lifetime¶
The LeaseLifetime
provides time-based object lifetimes.
Each LeaseLifetime
has an associated expiration time after which any associated objects will be evicted.
The lease can be extended as needed with extend()
or ended early close()
.
- Create a new lifetime with a current lease of ten seconds.
- Extend the lease by another five seconds.
- Sleep for longer than our current lease.
- Lease has expired so the lifetime has ended and associated objects have been evicted.
Static Lifetime¶
A static lifetime indicates that the associated objects should live for the remainder of the lifetime of the running process which created the object.
The StaticLifetime
, a singleton class, will evict all associated objects at the end of the program via an atexit handler.
- The atexit handler will call
store.close()
at the end of the program. Settingregister=True
is recommended to prevent another instance being created internally when a proxy is resolved. - The object associated with
key
will be evicted at the end of the program. - The object associated with
proxy
will be evicted at the end of the program.
Additional tips:
- Closing the
Store
at the end of the program but before the atexit handler has executed can cause undefined behaviour. Let the handler perform all cleanup. - The
StaticLifetime
can be closed manually, but only once. This may be useful if the the associated stores need to be closed manually or outside of the atexit handler. (Close the lifetime before the stores.) - atexit does not guarantee that the handler will be called in some unexpected process shutdown cases. This can lead to a memory leak in the connector(s).
Ownership¶
An OwnedProxy
, created by Store.owned_proxy()
, provides an alternative to the default Proxy
which enforces Rust-like ownership and borrowing rules for objects in a Store
.
- Each target object of type
T
in the global store has an associatedOwnedProxy[T]
. - There can only be one
OwnedProxy[T]
for any target in the global store. - When an
OwnedProxy[T]
goes out of scope (e.g., gets garbage collected), the associated target is removed from the global store.
An OwnedProxy[T]
can be borrowed without relinquishing ownership.
This requires two additional rules.
- At any given time, you can have either one mutable reference to the target, a
RefMutProxy[T]
, or any number of immutable references, aRefProxy[T]
. - References must always be valid. I.e., you cannot delete an
OwnedProxy[T]
while it has been borrowed via aRefProxy[T]
orRefMutProxy[T]
.
Reference proxy types can be created and used using:
borrow()
,
mut_borrow()
,
clone()
,
into_owned()
, and
update()
.
The submit()
associates proxy references with the scope of a function executed by a function executor, such as a ProcessPoolExecutor
or FaaS system.
This wrapper function ensures that immutable or mutable borrows of a value passed to a function are appropriately removed once the function completes.
- Borrow an
OwnedProxy
as aRefProxy
. submit()
will callpool.submit()
with the specifiedargs
andkwargs
. Here,sum
will be the function invoked on a single argumentborrowed
which is a proxy of a list of integers.- The
args
andkwargs
will be scanned for any proxy reference types, and a callback will be added to the returned future that marks the input proxy references as out-of-scope once the future completes. - Once the future is completed, the
borrowed
reference is marked out-of-scope and the reference count of borrows managed internally inproxy
is decremented. - The
OwnedProxy
,proxy
, which owns the target value is safe to delete and get garbage collected because there are no remaining reference proxies which have borrowed the target value.