Status
Implemented by PR #850; pending merge.
Scope
Phase 1 supports resources registered directly on framework_graph by code compiled against the concrete resource type. It provides two resource categories:
- An unlimited-access resource is a graph-owned, non-data process facility with no
token_type. It imposes no resource-capacity limit and may therefore be used concurrently by any number of algorithm invocations, subject to the facility's own thread-safety contract.
- A single-token resource has a
token_type, but no tokens(), and permits one algorithm invocation at a time.
A declaration with both token_type and tokens() is recognized as a multi-token resource form, but Phase 1 rejects it at compile time. It must not be silently treated as a single-token resource.
For bounded resources, the access value is a concurrency token held for the complete execution of the algorithm body. For unlimited resources, the access value grants access only and does not reserve capacity.
Unlimited-Access Resources
An unlimited-access resource is a graph-owned, process-lifetime facility that algorithms may use concurrently. It is not a data product: it has no data-layer association, producer provenance, output representation, or persistence semantics.
Use an unlimited-access resource for a non-data capability such as a thread-safe service client, library context, logging or metrics endpoint, device-runtime interface, or implementation cache. The resource author does not declare token_type or tokens().
class conditions_client {
public:
explicit conditions_client(service_endpoint endpoint);
calibration_record fetch(channel_id channel, timestamp time) const;
};
g.add_resource<conditions_client>(endpoint);
A transform requesting the resource receives a pointer to the graph-owned facility. By default, the pointer is T const*, so the facility must support concurrent read operations:
calibration_record obtain_conditions(event const& input,
conditions_client const* client)
{
return client->fetch(input.channel(), input.timestamp());
}
m.transform("obtain_conditions", obtain_conditions, concurrency::unlimited)
.input_family(selector, resource<conditions_client>{});
The transform uses the client as a process facility and publishes the returned calibration_record as a data product. The record can then carry normal product identity, provenance, and persistence semantics at its selected data layer.
The resource object address remains stable for the lifetime of the framework_graph. At the catalog boundary, resource_catalog::access_for<T>() returns gsl::not_null<T const*>. The node-level cache and algorithm-facing signature use ordinary T const* values. No TBB resource limiter is created, so a node using only unlimited-access resources remains an ordinary function_node or multifunction_node and retains its configured Phlex concurrency.
An unlimited-access resource is not the appropriate abstraction merely because an object is read-only and shared for a whole job. If an object represents scientific or reproducible state whose identity, origin, version, or contents may need provenance or persistence, it should be a data product at the appropriate layer. For example, a geometry_snapshot or calibration_record would normally be job-level data products; a geometry_database_client or conditions_client can be an unlimited-access resource.
const expresses mutation rights, not resource concurrency policy. Mutable concurrent access requires an explicit future access-policy spelling.
Single-Token Resources
A single-token resource declares an algorithm-facing token type that the framework can construct from its owned resource object. Phase 1 supports either a pointer-compatible token or a copyable value token.
A pointer-compatible resource-object token can be T* or T const*:
class database {
public:
using token_type = database const*;
explicit database(connection_options options);
};
g.add_resource<database>(options);
void query(request const& request, database const* database_resource);
m.observe("query", query, concurrency::unlimited)
.input_family(selector, resource<database>{});
The framework creates one limiter token from the address of its framework-owned database object and holds it while query executes. A pointer-compatible token refers to that framework-owned object.
A small resource may instead use its own type as the token type:
struct random_engine {
using token_type = random_engine;
explicit random_engine(unsigned int seed) : seed_{seed} {}
unsigned int seed_;
};
g.add_resource<random_engine>(42);
void generate(random_engine engine_token);
In this form, the framework copies its registered random_engine into the one-token limiter. The algorithm receives the acquired value token, not the catalog-owned resource object. Value tokens are for small, copyable capability values, not shared mutable resource state.
The effective maximum concurrency for a node is the minimum of the operator's concurrency setting and the resource's one-token capacity. A distinct token type that cannot be produced from T*, or a value token type that is not exactly T and copy constructible, is not a Phase 1 resource declaration.
Declaring Dependencies
All resource categories use the same registration-time marker:
Resources follow product selectors in input_family(...) and correspond, in order, to trailing algorithm parameters:
void process(input const&, geometry const*, database const*);
m.observe("process", process, concurrency::unlimited)
.input_family(selector, resource<geometry>{}, resource<database>{});
The registration layer counts resource markers as callable inputs but uses only preceding product-selector parameters for product type deduction. It does not assume resource parameters are pointers, so value-token parameters are valid.
Resource types identify registered resources. A graph permits one registration of each concrete resource type, and a dependency list may name each resource type at most once; duplicate resource dependencies are rejected at compile time.
Framework Responsibilities
framework_graph owns registered resource objects and their limiter state. Both must outlive graph nodes, since bounded nodes retain references to their TBB resource limiters.
Registration must occur before algorithm registration references the resource. Referencing an unregistered resource is a registration error with a clear diagnostic.
For a node that uses multiple bounded resources, the framework acquires one token from each resource for the whole body invocation. Resource declaration order is therefore part of observable scheduling behavior and remains consistent across algorithms.
Exclusions
Phase 1 does not enable multi-token resources or implement a Phlex-specific semaphore to emulate them. Multi-token resources are covered by Phase 3 (#853).
Status
Implemented by PR #850; pending merge.
Scope
Phase 1 supports resources registered directly on
framework_graphby code compiled against the concrete resource type. It provides two resource categories:token_type. It imposes no resource-capacity limit and may therefore be used concurrently by any number of algorithm invocations, subject to the facility's own thread-safety contract.token_type, but notokens(), and permits one algorithm invocation at a time.A declaration with both
token_typeandtokens()is recognized as a multi-token resource form, but Phase 1 rejects it at compile time. It must not be silently treated as a single-token resource.For bounded resources, the access value is a concurrency token held for the complete execution of the algorithm body. For unlimited resources, the access value grants access only and does not reserve capacity.
Unlimited-Access Resources
An unlimited-access resource is a graph-owned, process-lifetime facility that algorithms may use concurrently. It is not a data product: it has no data-layer association, producer provenance, output representation, or persistence semantics.
Use an unlimited-access resource for a non-data capability such as a thread-safe service client, library context, logging or metrics endpoint, device-runtime interface, or implementation cache. The resource author does not declare
token_typeortokens().A transform requesting the resource receives a pointer to the graph-owned facility. By default, the pointer is
T const*, so the facility must support concurrent read operations:The transform uses the client as a process facility and publishes the returned
calibration_recordas a data product. The record can then carry normal product identity, provenance, and persistence semantics at its selected data layer.The resource object address remains stable for the lifetime of the
framework_graph. At the catalog boundary,resource_catalog::access_for<T>()returnsgsl::not_null<T const*>. The node-level cache and algorithm-facing signature use ordinaryT const*values. No TBB resource limiter is created, so a node using only unlimited-access resources remains an ordinaryfunction_nodeormultifunction_nodeand retains its configured Phlex concurrency.An unlimited-access resource is not the appropriate abstraction merely because an object is read-only and shared for a whole job. If an object represents scientific or reproducible state whose identity, origin, version, or contents may need provenance or persistence, it should be a data product at the appropriate layer. For example, a
geometry_snapshotorcalibration_recordwould normally be job-level data products; ageometry_database_clientorconditions_clientcan be an unlimited-access resource.constexpresses mutation rights, not resource concurrency policy. Mutable concurrent access requires an explicit future access-policy spelling.Single-Token Resources
A single-token resource declares an algorithm-facing token type that the framework can construct from its owned resource object. Phase 1 supports either a pointer-compatible token or a copyable value token.
A pointer-compatible resource-object token can be
T*orT const*:The framework creates one limiter token from the address of its framework-owned
databaseobject and holds it whilequeryexecutes. A pointer-compatible token refers to that framework-owned object.A small resource may instead use its own type as the token type:
In this form, the framework copies its registered
random_engineinto the one-token limiter. The algorithm receives the acquired value token, not the catalog-owned resource object. Value tokens are for small, copyable capability values, not shared mutable resource state.The effective maximum concurrency for a node is the minimum of the operator's concurrency setting and the resource's one-token capacity. A distinct token type that cannot be produced from
T*, or a value token type that is not exactlyTand copy constructible, is not a Phase 1 resource declaration.Declaring Dependencies
All resource categories use the same registration-time marker:
resource<T>{}Resources follow product selectors in
input_family(...)and correspond, in order, to trailing algorithm parameters:The registration layer counts resource markers as callable inputs but uses only preceding product-selector parameters for product type deduction. It does not assume resource parameters are pointers, so value-token parameters are valid.
Resource types identify registered resources. A graph permits one registration of each concrete resource type, and a dependency list may name each resource type at most once; duplicate resource dependencies are rejected at compile time.
Framework Responsibilities
framework_graphowns registered resource objects and their limiter state. Both must outlive graph nodes, since bounded nodes retain references to their TBB resource limiters.Registration must occur before algorithm registration references the resource. Referencing an unregistered resource is a registration error with a clear diagnostic.
For a node that uses multiple bounded resources, the framework acquires one token from each resource for the whole body invocation. Resource declaration order is therefore part of observable scheduling behavior and remains consistent across algorithms.
Exclusions
Phase 1 does not enable multi-token resources or implement a Phlex-specific semaphore to emulate them. Multi-token resources are covered by Phase 3 (#853).