Error Handling and Diagnostics#
Check synchronous return values immediately. For asynchronous calls, check both submission and completion. A successful submission does not mean the operation succeeded.
Checking Errors#
Python raises ovstage.OvstageError for stage errors and
ovstage.OvxError for path-dictionary errors. Both derive from RuntimeError
and carry a numeric code and a message. Some invalid Python arguments raise TypeError or ValueError
before calling the native API. On success, wait() returns the operation or
handle it was called on.
In C, check the status returned by the call. After ovstage_wait_op(),
also inspect every operation ID in error_op_ids. Retrieve its message
before the next wait on the same thread, which invalidates the list and messages.
Release operation tracking only after reading the diagnostics.
The boundary where a failure is discovered affects its reported code. A rejected
enqueue can return a specific status such as WRITE_FLOOR_VIOLATION or
OUT_OF_RANGE directly. For an accepted operation that fails during execution,
wait_op and mutation wait() generally report OP_FAILED; inspect the
diagnostic for the underlying reason.
Reads can validate an already-ready query at enqueue, or defer validation until
their producer runs. A fetch that waits for a failed producer can report
OP_FAILED; a fetch after that producer has completed can expose its specific
recorded status. Resolve the query first when using OUT_OF_RANGE to choose
a current-state fallback, and still handle deferred producer failures.
The snippets below show Python catching a failed write and the C++ helper used by the C examples to wait, check errors, and release operation tracking.
# wait() reports rejected submissions and failed operations or dependencies.
# OvstageError carries a numeric code and a message.
def write_checked(stage, query, attr, ordinal, values) -> bool:
try:
stage.write_attribute(query, attr, ordinal=ordinal, tensors=values, is_array=False).wait()
return True
except OvstageError as err:
print(f"ovstage write failed (code {int(err.code)}): {err.message}")
return False
The synchronous status helper used by waitOp() is:
// Check a synchronous ovstage call: compare against OVSTAGE_OK and stringify
// failures with ovstage_get_error_string (vtable-dispatched, so it takes the
// instance). The examples fail fast -- print and exit; a real application
// would propagate the error instead.
static void check(ovstage_instance_t* stage, ovstage_api_status_t status, const char* what)
{
if (status == OVSTAGE_OK)
return;
std::fprintf(stderr, "ovstage %s failed (code %u): %s\n", what, status,
stage ? ovstage_get_error_string(stage, status) : "(no instance)");
std::exit(EXIT_FAILURE);
}
The wait helper checks both the returned status and failed operation IDs:
// Wait for the operation and its dependencies, then inspect any errors.
// Read messages before another wait and release tracking after completion.
// This example exits on error; applications can propagate the error instead.
static void waitOp(ovstage_instance_t* stage, ovstage_enqueue_result_t enq, const char* what)
{
check(stage, enq.status, what);
ovstage_op_wait_result_t wait{};
const ovstage_api_status_t code = ovstage_wait_op(stage, enq.op_index, OVSTAGE_TIMEOUT_INFINITE, &wait);
for (size_t i = 0; i < wait.error_op_id_count; ++i)
{
const ovx_string_t e = ovstage_get_last_op_error(stage, wait.error_op_ids[i]);
std::fprintf(stderr, "ovstage %s op %llu failed: %.*s\n", what,
(unsigned long long)wait.error_op_ids[i], (int)e.length, e.ptr ? e.ptr : "");
}
if (wait.error_op_id_count != 0)
std::exit(EXIT_FAILURE);
ovstage_release_op(stage, enq.op_index);
check(stage, code, what);
}
Status Codes#
Use ovstage.ErrorCode names, not numeric values. C uses the corresponding
constants in ovstage_api_status_t: OVSTAGE_OK for ErrorCode.OK,
and OVSTAGE_ERROR_* for the other members.
Status |
Meaning |
|---|---|
|
The call succeeded. For an enqueue, this means it was accepted. |
|
An argument is invalid. |
|
The handle is invalid or has been released. |
|
The requested item was not found. |
|
Despite its name, this reports an existing prim rejected by |
|
A mutation conflicts with an applicable watermark or retained value/prim-lifecycle change under the configured admission policy, or a read selects an unsealed change at or before its requested end. The end itself is not compared with the watermark. |
|
This build does not support the operation or requested data. |
|
The submission queue is full. |
|
There are no more results to fetch. |
|
An allocation or size calculation failed recoverably. |
|
The attribute layout changed during a map operation. |
|
The wait or fetch did not finish within its timeout. |
|
An asynchronous operation failed. Inspect its diagnostic. |
|
A selected attribute on a queried path has a recorded change after the requested end. The older value is unavailable even after sealing the later change. Request a newer end or current state; see Reading Attributes. |
|
An internal error occurred. |
ovstage.Read.fetch_next() and ovstage.Map.fetch_next() return
None at the end of iteration rather than raising an exception.
See ovstage Overview for diagrams explaining watermark and range-read errors. A watermark advance to a lower ordinal has no effect; it does not move the watermark backwards or report a watermark violation.
Recovering from a Failed Write#
A failed batch can leave some changes applied:
Successfully completed entries remain applied when a later entry fails. The API does not return a list of completed entries. Repair a batch that may have partly succeeded using a newer ordinal.
If no entry completed, a corrected retry at the same ordinal may be accepted, subject to the current watermark and other write rules. This includes a one-entry write whose change was not applied successfully.
A failed write can leave a prim or attribute visible to queries. Visibility alone does not prove a change was recorded.
PrimMode.INSERTcan reject this existing target;PrimMode.UPSERTcan repair it.
Reserved metadata entries can complete before ordinary attribute entries during execution and remain committed if a later entry fails. Allocating ordinary attribute storage alone does not count as a successful write.
Under STRICTLY_INCREASING, replaying completed cells at their recorded
ordinal is rejected. Disjoint unfinished cells may still admit a same-ordinal
retry; NONDECREASING and FLOOR_ONLY may also allow replay of completed
cells. Every retry must satisfy current watermarks, the selected policy, and
stage state. Failure does not reserve an ordinal for later retry. See
Ordinal Admission Policies.
Note
A successful repair can record attribute changes without recording creation
of a prim left present by the earlier failure. Do not infer creation events
from query visibility. The ovstage.Stage.write_attributes() reference
describes the full recovery contract.
In C, a synchronous recoverable allocation failure returns
OVSTAGE_ERROR_OUT_OF_MEMORY. A failure during an enqueued write is reported
as OVSTAGE_ERROR_OP_FAILED through the wait, with the allocation error in
the operation diagnostic.
Warning
If an entry was applied successfully but the runtime cannot record its committed prims or attributes, ordinal ownership, and change records consistently, the implementation terminates the process. This is not a returned status or a Python exception.
Diagnostic Messages#
Python exceptions carry the error message. In C, use the accessor that matches the failure:
Accessor |
Result and lifetime |
|---|---|
Static, non-null |
|
Borrowed |
|
Borrowed |
An unknown or non-failed ID returns an empty view from
ovstage_get_last_op_error(). See String Handling for copying returned
strings and Asynchronous Operations for timeouts.
Map Commit Failures#
A rejected ovstage_unmap_group enqueue leaves the map and group unchanged.
Except for availability-event record failures,
after acceptance, execution failure makes the map terminal; final unmap reports
that failure while releasing it. Repair uses a new map or write, not another
commit attempt in the failed session. A failed write does not commit new prims
or attributes, schema or semantic changes, hierarchy connections, or value and
prim-lifecycle change records. It also records no new ordinal ownership. Groups
committed earlier remain committed, including their structural changes.
Successful final unmap commits only remaining groups and does not repeat changes
from earlier commits.
Map commits are not transactional and provide no rollback guarantee. A failed write may have modified some stored rows; these partial changes can be observable even though the write was not recorded as successful. Do not infer which rows completed from those changes or treat final unmap as an atomic commit of the whole session.
An unplaced map uses CPU storage, so any such unmap that still has payload to
commit rejects a non-zero cuda_stream or cuda_event. That rejection is
an accepted failure and is therefore terminal for the session: finalize with
ovstage_unmap_attribute and start a new map to retry. A cleanup-only close
of ovstage_unmap_attribute – every group already committed by
ovstage_unmap_group – has no payload and never inspects the sync, so this
is not a general argument check.
Placement Availability Failures#
The experimental placement extension has a post-commit exception: if CUDA
refuses to record availability_event, an otherwise successful write or map
commit reports OP_FAILED while its values, change records, and ordinal
ownership remain committed. Retrying must satisfy the configured admission
policy; STRICTLY_INCREASING requires a newer ordinal for those cells.
A failed event record on unmap_group leaves the session open, while final
unmap_attribute still consumes its handle. A placed read fails instead of
returning payload. An operation completed during enqueue can report this
failure synchronously after its effects have occurred.
The event remains untouched and cannot prove device completion. After observing
the failure, retain write sources until synchronizing the specific placement
stream, or the recording device for a default/per-thread stream. An ordinary
synchronous rejection owes no event record; do not blindly wait on the event
after rejection. See Completion, Availability, and Buffer Lifetime and the
ovstage.Placement reference for ordering, lifetime, and allocation-failure
details. Event completion alone never replaces checking operation status.
Query Refresh Failures#
In this implementation, a failed query refresh after a structural change marks all ready query handles on the stage as failed, including unrelated queries. Release and recreate those handles; retrying them does not restore readiness. The refresh failure is not reported on the operation that caused the change, which can still report success. It first surfaces when an affected handle is used, currently without a diagnostic message.