Resources¶
Resources represent reusable, stateful data or capabilities that are expensive to create for every operator invocation. A resource declaration describes how to load and tear down that data; Jayrun owns the loaded instance at runtime and may share it across contexts.
Resources belong to the data model because operators receive their loaded values as jayrun.Data. Unlike artifacts, resource data does not flow along graph edges. It is addressed through jayrun.ResourceField declarations and managed by runtime lifetime rules.
This page separates the resource declaration, the operator field, the graph-local field definition, and the loaded runtime data. See Operators and Executions for field ownership and Graph Construction for binding resources into a graph.
Resource lifetime¶
Resource lifetime has two distinct layers:
Layer |
Owner |
Lifetime |
|---|---|---|
Resource declaration |
Application |
Reusable Python object, independent of engine execution |
Loaded resource data |
Engine runtime |
From successful setup until safe eviction or engine shutdown |
Loading is demand-driven. When an operator becomes eligible, Jayrun resolves its bound resource fields. If a matching resource is already available, setup is skipped. Otherwise, one setup execution creates the loaded data while matching consumers wait.
After the operator releases the resource, the loaded data normally remains cached. It can be acquired by later operators or contexts without running setup again.
BaseResource¶
Every resource subclasses jayrun.BaseResource, declares configuration fields in __init__(), and implements setup() and teardown():
from jayrun import BaseResource, ConfigField, Data
class ModelResource(BaseResource):
def __init__(
self,
*,
name: str | None = None,
description: str | None = None,
) -> None:
super().__init__(name=name, description=description)
self.model_path = ConfigField(value_type=str, required=True)
def setup(self) -> Data:
model = load_model(self.model_path.value)
return Data(value=model)
def teardown(self, data: Data) -> None:
close_model(data.value)
The constructed object is an immutable declaration. Jayrun invokes the unbound setup method on a runtime proxy containing config values and operational interfaces.
Important
Arbitrary constructor attributes are not copied to the setup proxy and do not participate in cache identity. Declare runtime-varying inputs with jayrun.ConfigField, or use class-level constants and module-level helper functions.
Resource declarations may define class-level requirements using standard package requirement strings. These requirements join the graph specification after resource binding.
ResourceField¶
Operators declare resource dependencies with jayrun.ResourceField:
from jayrun import ResourceField
self.model = ResourceField(
required=True,
parallel_safe=False,
)
Bind the field while constructing the graph:
model_resource = ModelResource(name="model")
graph.bind_resources({inference.model: model_resource})
At execution time, the field contains the loaded jayrun.Data:
def execute(self) -> object:
return self.model.value(self.input_data.value)
A required field must be bound before graph confirmation. An optional field may remain unbound; in that case it is not attached to the operator execution proxy.
ResourceDefinition¶
When an operator’s resource field is registered in a graph, Jayrun creates an immutable jayrun.core.graph.definition.ResourceDefinition. The definition describes the field dependency, not the BaseResource later bound to it.
Attribute |
Meaning |
|---|---|
|
Integer identifier local to the graph |
|
Display representation of the operator that owns the field |
|
Operator attribute under which the field was declared |
|
Position of the owning operator |
|
Copied field metadata |
|
Copied acquisition capability |
Resource definitions are available before binding:
required = graph.inspect.resources.required
optional = graph.inspect.resources.optional
all_resources = graph.inspect.resources.all
Applications do not construct resource definitions. They declare ResourceField objects, and the graph creates definitions when it registers those fields. See Graph inspection for the inspection surface.
jayrun.GraphDefinition.bind_resources() accepts a source ResourceField, its graph-local ResourceDefinition, or its graph-local integer ID:
definition = next(
item
for item in graph.inspect.resources.all
if item.attribute_name == "model"
)
graph.bind_resources({inference.model: model_resource})
# Equivalent reference forms for the same graph field:
# graph.bind_resources({definition: model_resource})
# graph.bind_resources({definition.resource_id: model_resource})
Use the field in normal Python construction, a definition in inspection-driven tooling, and the ID in serialized graph-local input. Unknown IDs, foreign definitions, and duplicate aliases for the same field are rejected.
See Resource binding for confirmation behavior and optional fields.
Resource setup¶
setup() must return exactly one jayrun.Data instance. Raw objects and tuples are not accepted as resource setup results.
def setup(self) -> Data:
client = create_client(self.endpoint.value)
return Data(value=client)
Define setup with def for thread-executor work or async def for event-loop work:
async def setup(self) -> Data:
client = await create_client(self.endpoint.value)
return Data(value=client)
Setup receives the four operational interfaces. It may store records, log metrics, inspect its context, or request placement capacity. Repetition is operator-only and is not available to resource setup.
Jayrun loads a resource only when the consuming operator can otherwise run. If the operator is skipped because a required artifact value is absent, its resource setup is skipped as well.
Resource teardown¶
teardown() receives the exact jayrun.Data returned by successful setup:
def teardown(self, data: Data) -> None:
data.value.close()
Teardown may also return an awaitable:
async def teardown(self, data: Data) -> None:
await data.value.aclose()
Resource config fields are available during teardown with the same resolved values used by setup. Operational interfaces are intentionally unavailable: teardown is runtime cleanup, not an execution scope exposed to user control.
Teardown runs when an idle cached resource is evicted or during engine shutdown. A teardown failure is recorded as a runtime cleanup failure. During eviction, the resource remains registered as ready if teardown fails; during shutdown, Jayrun continues attempting cleanup of other resources.
teardown() may contain only pass when the payload owns no explicit external or native resource. After successful teardown, Jayrun removes its cached reference to the Data; normal Python reference counting or garbage collection can then reclaim the payload once no other references remain. This does not guarantee immediate destruction.
Use explicit teardown for database connections, files, sockets, client sessions, thread pools, subprocesses, device allocations, or any object with a documented close(), shutdown(), or equivalent lifecycle method.
Setup and teardown data¶
The setup result is the resource’s runtime value and lifetime carrier:
return Data(
value=loaded_resource,
placement=placement,
)
The value becomes available through every bound operator field. The placement identifies where the data lives and keeps accelerator capacity leased while the resource remains cached.
The same Data is later passed to teardown. Do not return a container whose payload has already been invalidated, and do not move placement-backed data without returning a new Data that describes its actual location.
Parallel-safe resources¶
ResourceField.parallel_safe controls concurrent acquisition of the same cached instance.
Trueallows several active operator executions to use it concurrently.Falseallows only one active operator execution at a time.
self.client = ResourceField(parallel_safe=True)
self.model = ResourceField(parallel_safe=False)
parallel_safe=True is a declaration of capability, not automatic synchronization. The resource payload and every library it calls must actually support concurrent use.
Parallel-safety mode participates in the cache key. Bindings with different modes do not share the same cached entry.
Resource acquisition and release¶
Acquisition is automatic:
The execution checks whether each resource key is ready.
Jayrun acquires every distinct required key before invoking the operator.
Loaded
Datais attached to all fields using that key.Jayrun releases the acquisitions after the invocation result is applied or the session is drained.
If several fields on one operator resolve to the same key, Jayrun acquires the cached resource once and attaches the same Data to each field.
A non-parallel-safe resource that is already in use temporarily blocks another consumer. The consumer remains undispatched until the resource becomes acquirable; user code does not poll or release it manually.
Pinning and eviction¶
Pinning is an internal ownership mechanism that protects a loaded resource between preparation and operator acquisition. A resource cannot be evicted while pinned or actively used.
After all pins and active acquisitions are released, a resource is ready and evictable. Accelerator placement pressure may cause Jayrun to select idle placement-backed resources for teardown. Eviction favors resources by least recent access and creation time while minimizing the number of cache entries and excess capacity released.
There is no public pin, unpin, acquire, release, or evict API. Exposing those operations would let user code invalidate resources still required by another context.
Failure during resource initialization¶
Setup failures follow the effective jayrun.settings.RetryPolicy. While setup is retrying, the cache registration remains in the loading state and matching consumers continue to wait.
If retry succeeds, the returned Data becomes the shared cached value. If retries are exhausted, Jayrun cancels the loading registration and fails the context according to the configured failure policy.
Warning
Teardown is available only after setup successfully returns and the Data is registered. If setup acquires external state and then raises, setup must clean up that partial state itself before propagating the exception.
A failed setup does not publish partial resource data. Another later context may attempt a fresh registration after the failed one is removed.
CPU model-resource example¶
This resource loads one CPU model and serializes its use:
from jayrun import BaseResource, ConfigField, Data
class CpuModelResource(BaseResource):
def __init__(
self,
*,
name: str | None = None,
description: str | None = None,
) -> None:
super().__init__(name=name, description=description)
self.model_path = ConfigField(value_type=str, required=True)
def setup(self) -> Data:
model = load_model(self.model_path.value)
model.eval()
return Data(value=model)
def teardown(self, data: Data) -> None:
pass
No placement request is needed: Data uses CPU placement by default. The empty teardown is appropriate only when the model owns no handle that requires explicit closure; Jayrun releases its cache reference after teardown.
Bind it through a non-parallel-safe field when the model implementation is not safe for simultaneous calls:
self.model = ResourceField(parallel_safe=False)
Database-resource example¶
A database connection requires explicit teardown even though its Python wrapper is garbage-collectable:
import sqlite3
from jayrun import BaseResource, ConfigField, Data
class DatabaseResource(BaseResource):
def __init__(
self,
*,
name: str | None = None,
description: str | None = None,
) -> None:
super().__init__(name=name, description=description)
self.path = ConfigField(value_type=str, required=True)
def setup(self) -> Data:
connection = sqlite3.connect(
self.path.value,
check_same_thread=False,
)
return Data(value=connection)
def teardown(self, data: Data) -> None:
data.value.close()
Bind this resource to a serialized field unless the selected database client explicitly supports concurrent operations through one connection:
self.database = ResourceField(parallel_safe=False)
For production database concurrency, a thread-safe connection pool is usually the resource payload; its teardown should close the pool.
GPU model-resource example¶
Placement-bearing resource data keeps GPU capacity reserved while the model remains cached:
class CudaModelResource(BaseResource):
def __init__(
self,
*,
name: str | None = None,
description: str | None = None,
) -> None:
super().__init__(name=name, description=description)
self.model_path = ConfigField(value_type=str, required=True)
self.memory_gb = ConfigField(value_type=float, required=True)
def setup(self) -> Data:
placement = self.placement.cuda(memory_gb=self.memory_gb.value)
device = f"cuda:{placement.device_id}"
model = load_model(self.model_path.value).to(device)
model.eval()
return Data(value=model, placement=placement)
def teardown(self, data: Data) -> None:
pass
The declared reservation is capacity accounting; the model library still performs the actual device transfer. An explicit transfer back to CPU is not required for ordinary teardown. After teardown() returns, Jayrun removes its cached Data reference. If application code holds no other reference to the model, the model and its tensor storage become eligible for reclamation, and the placement carried by Data is released through its normal lifetime mechanism.
An empty teardown is therefore appropriate when the model owns no external handle that requires deterministic closure. A tensor library may retain released device blocks in its allocator cache for reuse by the same process; that is library-managed caching, not a live Jayrun resource. Use a library-specific cleanup operation only when the library requires one or the application has a concrete need to release that cached capacity. External references retained by application code also remain the application’s responsibility.
See Placement and Capacity for allocator behavior and multi-device usage.
API reference¶
- class jayrun.BaseResource(*, name=None, description=None, **kwargs)¶
Abstract immutable declaration for one runtime-managed resource.
- jayrun.BaseResource.requirements: tuple[str, ...]¶
Class-level package requirements contributed by the resource.
- jayrun.BaseResource.config_fields: tuple[jayrun.ConfigField, ...]¶
Resource configuration fields in declaration order.
- jayrun.BaseResource.display_name: str¶
Explicit name, or the resource subclass name when no name was supplied.
- jayrun.BaseResource.setup() jayrun.Data¶
Load and return exactly one runtime resource value.
Subclasses may implement a synchronous method or an asynchronous coroutine method.
- jayrun.BaseResource.teardown(data) None¶
Release a previously loaded resource value.
- Parameters:
data (jayrun.Data) – Exact data container returned by successful setup.
- class jayrun.ResourceField(*, name=None, description=None, required=True, parallel_safe=True)¶
Declare a runtime-managed resource dependency on an operator.
- Parameters:
- Raises:
TypeError – If field metadata or
parallel_safehas an invalid type.
- jayrun.ResourceField.parallel_safe: bool¶
Whether the bound cached resource may be acquired concurrently.
- class jayrun.core.graph.definition.ResourceDefinition(*, resource_id, parallel_safe, owner, required, layout_position, attribute_name, name, description)¶
Immutable graph-local description of one registered resource field.
- Parameters:
resource_id (int) – Integer identifier local to the owning graph.
parallel_safe (bool) – Whether the selected cached resource may be acquired concurrently through this field.
owner (str) – Display representation of the owning operator.
required (bool) – Whether the field must be bound before confirmation.
layout_position (tuple) – Graph layout position of the owner.
attribute_name (str) – Attribute under which the field was declared.
Next, read Operators and Executions to use artifact, configuration, and resource fields in reusable computation.