Error Handling#

Join provides a standardized error handling system built on top of C++ <system_error>. Errors are reported through a thread-local error code and a custom error category.

Error handling features:

  • thread-safe — uses thread-local storage
  • standard compliant — integrates with std::error_code
  • expressive — human-readable error messages
  • compatible — maps to standard system error codes

Error codes#

Join defines common error conditions in the Errc enum:

Error CodeMessageDescription
InUsealready in useResource already in use
InvalidParaminvalid parametersInvalid parameters provided
ConnectionRefusedconnection refusedConnection was refused
ConnectionClosedconnection closedConnection closed by peer
TimedOuttimer expiredOperation timed out
PermissionDeniedoperation not permittedOperation not permitted
OutOfMemorycannot allocate memoryMemory allocation failed
OperationFailedoperation failedOperation failed
NotFoundresource not foundResource not found
MessageUnknownmessage unknownMessage unknown
MessageTooLongmessage too longMessage too long
TemporaryErrortemporary errorTemporary error, retry later
UnknownErrorunknown errorUnknown error occurred

Checking errors#

Using lastError#

Most Join functions return -1 on failure. Check the thread-local lastError for details:

#include <join/error.hpp>

using namespace join;

if (operation() == -1)
{
    std::cerr << "Error: " << lastError.message() << "\n";
}

Comparing error codes#

Errc is registered as std::is_error_condition_enum, so comparisons with == work directly:

if (lastError == Errc::TimedOut)
{
    // handle timeout
}

if (lastError == Errc::TemporaryError)
{
    // retry operation
}

Checking error category#

if (lastError.category() == join::getErrorCategory())
{
    // this is a Join error
}

Creating error codes#

std::error_code ec = join::make_error_code(Errc::InvalidParam);

lastError = join::make_error_code(Errc::ConnectionClosed);

Error equivalence#

Join errors map to standard system error codes via the equivalent() method. This allows comparing a raw errno-based std::error_code against a join::Errc condition directly.

Complete mappings#

Join errorEquivalent system errors
InUsealready_connected, connection_already_in_progress, address_in_use, file_exists
InvalidParamno_such_file_or_directory, address_family_not_supported, invalid_argument, protocol_not_supported, not_a_socket, bad_address, no_protocol_option, destination_address_required, operation_not_supported
ConnectionRefusedconnection_refused, network_unreachable
ConnectionClosedconnection_reset, not_connected, broken_pipe
TimedOuttimed_out
PermissionDeniedpermission_denied, operation_not_permitted
OutOfMemorytoo_many_files_open, too_many_files_open_in_system, no_buffer_space, not_enough_memory, no_lock_available
OperationFailedbad_file_descriptor
MessageUnknownno_message, bad_message, no_message_available
MessageTooLongmessage_size
TemporaryErrorinterrupted, resource_unavailable_try_again, operation_in_progress

Thread safety#

lastError is thread-local — each thread has its own independent error state:

void threadFunction()
{
    if (operation() == -1)
    {
        // lastError is specific to this thread
        std::cerr << lastError.message() << "\n";
    }
}

std::thread t1(threadFunction);
std::thread t2(threadFunction);

Best practices#

  • Check return values — most Join functions return -1 on error
  • Inspect lastError immediately — error state may be overwritten by subsequent calls
  • Handle TemporaryError — these errors indicate retry is appropriate
  • Use error equivalence — leverage automatic mapping to system errors

Example usage#

Basic error handling#

#include <join/error.hpp>

using namespace join;

Tcp::Socket socket;

if (socket.connect(endpoint) == -1)
{
    if (lastError == Errc::ConnectionRefused)
    {
        std::cerr << "Connection refused\n";
    }
    else
    {
        std::cerr << "Failed: " << lastError.message() << "\n";
    }
}

Retry on temporary errors#

int retryOperation()
{
    for (int attempts = 0; attempts < 3; ++attempts)
    {
        if (producer.tryPush(&msg) == 0)
        {
            return 0;  // success
        }

        if (lastError != Errc::TemporaryError)
        {
            return -1;  // permanent error
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    return -1;
}

Custom error dispatch#

void handleError(const std::error_code& ec)
{
    if (ec == Errc::TimedOut)
    {
        // retry logic
    }
    else if (ec == Errc::ConnectionClosed)
    {
        // reconnect logic
    }
    else
    {
        std::cerr << "Error: " << ec.message() << "\n";
    }
}

Summary#

FeatureSupported
Thread-local error state
Standard error integration
Human-readable messages
Error equivalence mapping
Custom error category