Skip to content

Phase 1 resource support #851

Description

@knoepfel

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:

  1. 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.
  2. 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:

resource<T>{}

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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions