Error Handling and Diagnostics#

Most synchronous ovstage calls return an ovstage_api_status_t: OVSTAGE_OK (0) is success and any non-zero value is an error. Asynchronous enqueues instead return an ovstage_enqueue_result_t, and completion or per-op failures surface later through ovstage_wait_op. Distinguish enqueue success from execution success: OVSTAGE_OK at enqueue means accepted, not completed.

Status Codes#

Code

Value

Meaning

OVSTAGE_OK

0

Success.

OVSTAGE_ERROR_INVALID_ARGUMENT

1

A required argument was invalid.

OVSTAGE_ERROR_INVALID_HANDLE

2

A handle did not refer to a live object.

OVSTAGE_ERROR_NOT_FOUND

3

The requested item was not found.

OVSTAGE_ERROR_PRIM_NOT_FOUND

4

In INSERT (create-only) mode, a prim already exists at the target path. (A missing item is OVSTAGE_ERROR_NOT_FOUND above.)

OVSTAGE_ERROR_WRITE_FLOOR_VIOLATION

5

A write/apply targeted an ordinal at or below the write floor.

OVSTAGE_ERROR_NOT_SUPPORTED

6

The operation is not supported by this build.

OVSTAGE_ERROR_QUEUE_FULL

7

The submission queue is full.

OVSTAGE_ERROR_END_OF_ITERATION

8

No further groups to fetch.

OVSTAGE_ERROR_OUT_OF_MEMORY

9

Allocation failed.

OVSTAGE_ERROR_LAYOUT_CHANGED

10

The underlying column layout changed.

OVSTAGE_ERROR_TIMEOUT

11

Not ready within the timeout (a wait signal, not a failure).

OVSTAGE_ERROR_OP_FAILED

12

The op executed but failed.

OVSTAGE_ERROR_INTERNAL

99

An internal error occurred.

Turning Codes into Strings#

Three diagnostic accessors exist, with distinct return types and lifetimes:

Accessor

Returns

Use

ovstage_get_error_string(instance, code)

static const char* (never NULL)

Human-readable text for a status code. Vtable-dispatched, so it needs a live instance.

ovstage_get_last_op_error(instance, op_id)

ovx_string_t ({NULL,0} if the id is unknown or did not fail)

The message for a specific failed op. Transient — copy it to retain (refer to String Handling).

ovstage_get_last_error(void)

ovx_string_t

A free function readable even when ovstage_create_instance itself failed.

Checking a Synchronous Call#

// 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);
}

Inspecting Per-Op Errors After a Wait#

After a wait, inspect ovstage_op_wait_result_t.error_op_ids / error_op_id_count and call ovstage_get_last_op_error for each failed id immediately — the message is invalidated by the next wait on the same thread, so copy it if you need to keep it.

// Drive an async enqueue to completion. Enqueue success (OVSTAGE_OK) only
// means the op was accepted, so wait on the op id, report any per-op errors
// surfaced by the wait, and retire the op (ovstage_destroy_instance requires
// every op released first). The examples fail fast; a real application would
// propagate the errors 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);
}

Errors in Python#

Python raises exceptions instead of returning codes: ovstage.OvstageError for stage operations and ovstage.OvxError for path-dictionary operations. Both subclass RuntimeError and carry a numeric code and a message; asynchronous errors are raised from .wait().

# ovstage surfaces failures as exceptions: OvstageError for data-plane ops
# (a numeric .code mapping to ovstage.ErrorCode, plus .message) and OvxError
# for path-dictionary calls. Operation.wait() raises OvstageError if the
# enqueue was rejected, or the op (or its ordinal-keyed dependencies) failed.
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

Notes#

  • OVSTAGE_ERROR_TIMEOUT from ovstage_wait_op is “not ready yet,” not a failure; wait_result.lowest_pending_op_id reports the lowest still-pending op in the chain.

  • ovstage_advance_write_floor never raises WRITE_FLOOR_VIOLATION — backwards advances clamp rather than error.

Where to Go Next#