Skip to content

Latest commit

 

History

History
368 lines (281 loc) · 9.65 KB

File metadata and controls

368 lines (281 loc) · 9.65 KB

Error Handling

Scope note:

  • this document describes the current Canopy error-code model through the primary C++ implementation
  • the error names are shared protocol concepts, but helper APIs and examples here are C++-specific
  • for implementation scope, see C++ Status, Rust Status, and JavaScript Status

Canopy provides a comprehensive error handling system covering memory, transport, serialization, resource, native I/O, and lifecycle errors.

For authoritative error code definitions, see c++/rpc/include/rpc/internal/error_codes.h.

Error Code Offsets

Error codes can be customized using offset functions to avoid conflicts with application-specific error codes:

// Customize error code base values
rpc::error::set_OK_val(0);              // Set OK value (default 0)
rpc::error::set_offset_val(100);        // Set offset magnitude
rpc::error::set_offset_val_is_negative(false);  // Offset direction

The ± notation in tables below indicates the error value is offset ± ordinal, where the offset can be configured as positive or negative.

1. Error Code Reference

Success

rpc::error::OK()  // Configured via set_OK_val(), default = 0

Memory Errors

Error Code Value Description
OUT_OF_MEMORY ±1 Service has no more memory
NEED_MORE_MEMORY ±2 Call needs more memory for out parameters

Data Errors

Error Code Value Description
INVALID_DATA ±4 Invalid data received
INVALID_METHOD_ID ±6 Wrong method ordinal
INVALID_INTERFACE_ID ±7 Interface not implemented
INVALID_CAST ±8 Unable to cast interface
PROTOCOL_ERROR ±30 Peer, transport, or kernel-facing protocol state is inconsistent

Transport Errors

Error Code Value Description
TRANSPORT_ERROR ±5 Custom transport error
SERVICE_PROXY_LOST_CONNECTION ±21 Channel unavailable

Zone Errors

Error Code Value Description
ZONE_NOT_SUPPORTED ±9 Zone inconsistent with proxy
ZONE_NOT_INITIALISED ±10 Zone not ready
ZONE_NOT_FOUND ±11 Zone not found

Object Errors

Error Code Value Description
OBJECT_NOT_FOUND ±12 Invalid object ID
OBJECT_GONE ±23 Optimistic pointer target object has been released

Version/Compatibility Errors

Error Code Value Description
INVALID_VERSION ±13 Unsupported RPC version
INCOMPATIBLE_SERVICE ±17 Service incompatibility
INCOMPATIBLE_SERIALISATION ±18 Unsupported encoding format

Serialization Errors

Error Code Value Description
PROXY_DESERIALISATION_ERROR ±15 Proxy deserialization failed
STUB_DESERIALISATION_ERROR ±16 Stub deserialization failed

Other Errors

Error Code Value Description
SECURITY_ERROR ±3 Security-specific issue
EXCEPTION ±14 Uncaught exception
REFERENCE_COUNT_ERROR ±19 Ref count issue
CALL_CANCELLED ±22 Remote call cancelled
CALL_TIMEOUT ±24 Outbound call timed out waiting for a response
NOT_IMPLEMENTED ±25 Interface path exists but is not implemented
FRAUDULANT_REQUEST ±26 Request violates protocol/security sequencing and may be malicious
RESOURCE_CLOSED ±27 Local resource was closed or is no longer accepting work
OPERATION_CANCELLED ±28 Local asynchronous operation was cancelled before completion
RESOURCE_EXHAUSTED ±29 Local capacity was exhausted after retry/backpressure handling
NATIVE_IO_ERROR ±31 Native I/O operation failed; inspect operation-specific native result when available

2. Error Checking Patterns

Basic Error Check

auto error = CO_AWAIT calculator_->add(10, 20, result);

if (error == rpc::error::OK())
{
    std::cout << "Result: " << result << "\n";
}
else
{
    std::cerr << "Error: " << static_cast<int>(error) << "\n";
}

Branch on Error

auto error = CO_AWAIT calculator_->divide(a, b, result);

if (error == rpc::error::OK())
{
    std::cout << "Result: " << result << "\n";
}
else if (error == rpc::error::INVALID_DATA())
{
    std::cerr << "Invalid input\n";
}
else if (error == rpc::error::OBJECT_GONE())
{
    std::cerr << "Optimistic target was released\n";
}
else
{
    std::cerr << "Unknown error: " << static_cast<int>(error) << "\n";
}

Error Helper Functions

Canopy provides a built-in error-to-string converter:

const char* rpc::error::to_string(int err);

Example usage:

auto error = CO_AWAIT calculator_->add(10, 20, result);
if (error != rpc::error::OK())
{
    std::cerr << "Error: " << rpc::error::to_string(error) << "\n";
}

3. Propagating Errors

Simple Propagation

CORO_TASK(error_code) wrapper_operation(int input, [out] int& output)
{
    auto error = CO_AWAIT calculator_->process(input, output);
    CO_RETURN error;  // Propagate up
}

Error Transformation

CORO_TASK(error_code) safe_operation(int input, [out] int& output)
{
    auto error = CO_AWAIT calculator_->risky_operation(input, output);

    if (error == rpc::error::INVALID_DATA())
    {
        // Transform to a more specific error
        CO_RETURN rpc::error::INVALID_DATA();  // Or a custom error
    }

    CO_RETURN error;
}

Error Accumulation

CORO_TASK(error_code) multi_step_operation()
{
    int temp1, temp2, temp3;
    auto error;

    error = CO_AWAIT step1(temp1);
    if (error != rpc::error::OK())
        CO_RETURN error;

    error = CO_AWAIT step2(temp1, temp2);
    if (error != rpc::error::OK())
        CO_RETURN error;

    error = CO_AWAIT step3(temp2, temp3);
    if (error != rpc::error::OK())
        CO_RETURN error;

    CO_RETURN rpc::error::OK();
}

4. Logging Errors

Using Logging Macros

// Debug level
RPC_DEBUG("Operation completed with result {}", result);

// Info level
RPC_INFO("Calculator operation add({}, {}) = {}", a, b, result);

// Warning level
RPC_WARNING("Invalid data received: {}", data);

// Error level
RPC_ERROR("Transport error: {}", static_cast<int>(error));

// Critical level
RPC_CRITICAL("Fatal error in service {}", service_name);

Conditional Logging

if (error != rpc::error::OK())
{
    RPC_ERROR("Operation failed: {} (code={})",
              error_to_string(static_cast<int>(error)),
              static_cast<int>(error));
}

5. Assertions

Runtime Assertions

RPC_ASSERT(ptr != nullptr);
RPC_ASSERT(count > 0);
RPC_ASSERT(error == rpc::error::OK());

Assertion Modes

Debug build (aborts with assert message):

#define RPC_ASSERT(x) \
    if (!(x))         \
        assert(!"error failed " #x);

Release build (aborts immediately):

#define RPC_ASSERT(x) \
    if (!(x))         \
        std::abort();

For assertion investigations, configure with -DCANOPY_HANG_ON_FAILED_ASSERT=ON so the failed assertion path calls rpc::hang() before aborting.

6. Transport Status Handling

auto status = transport_->get_status();

switch (status)
{
    case transport_status::CONNECTING:
        // Wait for connection
        break;
    case transport_status::CONNECTED:
        // Ready for operations
        break;
    case transport_status::DISCONNECTING:
        // Beginning to shut down, a close signal is being sent or received
        break;
    case transport_status::DISCONNECTED:
        // Terminal state, close signal has been acknowledged, or there is a terminal failure, no further traffic allowed
        CO_RETURN rpc::error::TRANSPORT_ERROR();
}

7. Object Lifecycle Errors

Object Gone

auto error = CO_AWAIT optimistic_calculator->add(10, 20, result);

if (error == rpc::error::OBJECT_GONE())
{
    // The service released the object targeted by this optimistic pointer.
    // Re-discover the object or tolerate the independently managed lifetime.
}

Object Not Found

auto error = CO_AWAIT proxy_->get_object(object_id, result);

if (error == rpc::error::OBJECT_NOT_FOUND())
{
    // Invalid object ID or wrong zone
    // Verify object_id and zone configuration
}

8. Version Mismatch

Use INVALID_VERSION for unsupported RPC versions, generated IDL fingerprints, or message schemas. An unknown fingerprint alone is not fraud; the peer may simply be newer. Reserve FRAUDULANT_REQUEST for impossible sequencing, authenticated tamper, replay, downgrade attempts, or invalid request-scoped capability handoff.

auto error = CO_AWAIT proxy_->call(method_id, input, output);

if (error == rpc::error::INVALID_VERSION())
{
    // Protocol version mismatch
    // Negotiate version or upgrade client/server
    RPC_ERROR("Version mismatch - client={}, server={}",
              client_version, server_version);
}

9. Best Practices

  1. Always check return values from RPC calls
  2. Handle OBJECT_GONE - optimistic pointer targets may be released independently
  3. Use logging for error context
  4. Use assertions for programmer errors
  5. Transform errors at appropriate layers
  6. Don't swallow errors - propagate or handle explicitly

10. Next Steps