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
DEBUGlevel logging to determine where unintentional proxy resolution is occurring. TheStorewill log everyGETandEVICToperation 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=Trueflag when creating the proxy. Thepopulate_targetflag 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 inisinstancechecks 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
ContextLifetimeand 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
Storeshould be closed after any associated lifetimes because lifetimes use theStorefor 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=Trueis recommended to prevent another instance being created internally when a proxy is resolved. - The object associated with
keywill be evicted at the end of the program. - The object associated with
proxywill be evicted at the end of the program.
Additional tips:
- Closing the
Storeat the end of the program but before the atexit handler has executed can cause undefined behaviour. Let the handler perform all cleanup. - The
StaticLifetimecan 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
Tin 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
OwnedProxyas aRefProxy. submit()will callpool.submit()with the specifiedargsandkwargs. Here,sumwill be the function invoked on a single argumentborrowedwhich is a proxy of a list of integers.- The
argsandkwargswill 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
borrowedreference is marked out-of-scope and the reference count of borrows managed internally inproxyis 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.