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 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 directionThe ± notation in tables below indicates the error value is offset ± ordinal, where the offset can be configured as positive or negative.
rpc::error::OK() // Configured via set_OK_val(), default = 0| Error Code | Value | Description |
|---|---|---|
OUT_OF_MEMORY |
±1 | Service has no more memory |
NEED_MORE_MEMORY |
±2 | Call needs more memory for out parameters |
| 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 |
| Error Code | Value | Description |
|---|---|---|
TRANSPORT_ERROR |
±5 | Custom transport error |
SERVICE_PROXY_LOST_CONNECTION |
±21 | Channel unavailable |
| 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 |
| Error Code | Value | Description |
|---|---|---|
OBJECT_NOT_FOUND |
±12 | Invalid object ID |
OBJECT_GONE |
±23 | Optimistic pointer target object has been released |
| Error Code | Value | Description |
|---|---|---|
INVALID_VERSION |
±13 | Unsupported RPC version |
INCOMPATIBLE_SERVICE |
±17 | Service incompatibility |
INCOMPATIBLE_SERIALISATION |
±18 | Unsupported encoding format |
| Error Code | Value | Description |
|---|---|---|
PROXY_DESERIALISATION_ERROR |
±15 | Proxy deserialization failed |
STUB_DESERIALISATION_ERROR |
±16 | Stub deserialization failed |
| 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 |
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";
}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";
}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";
}CORO_TASK(error_code) wrapper_operation(int input, [out] int& output)
{
auto error = CO_AWAIT calculator_->process(input, output);
CO_RETURN error; // Propagate up
}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;
}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();
}// 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);if (error != rpc::error::OK())
{
RPC_ERROR("Operation failed: {} (code={})",
error_to_string(static_cast<int>(error)),
static_cast<int>(error));
}RPC_ASSERT(ptr != nullptr);
RPC_ASSERT(count > 0);
RPC_ASSERT(error == rpc::error::OK());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.
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();
}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.
}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
}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);
}- Always check return values from RPC calls
- Handle OBJECT_GONE - optimistic pointer targets may be released independently
- Use logging for error context
- Use assertions for programmer errors
- Transform errors at appropriate layers
- Don't swallow errors - propagate or handle explicitly
- Telemetry - Debug with comprehensive logging
- Memory Management - Understanding lifecycle
- API Reference - Complete error code list