Best Practices#

Follow these recommendations to build robust, production-ready OUCH 5.0 applications.

Session Management#

Always Attach Listeners Before Login#

Recommended: Use start_with_listeners() or async_start_with_listeners() to prevent race conditions where messages arrive before listeners are ready:

Session session(io_ctx, settings);

// Recommended: Attach listeners and login atomically
session.start_with_listeners("", 0, std::chrono::milliseconds(2000),
    [](Session* s, std::uint64_t seq, const OrderAccepted* msg) {
        // Handle OrderAccepted
    });

Alternative: If you need to attach listeners separately, attach them before calling login() or async_login():

Session session(io_ctx, settings);

// Attach listeners FIRST
session.attach_listener(/* handlers */);

// Then login (must be from non-IOContext thread to avoid deadlock)
session.login("", 0, std::chrono::milliseconds(2000));

Use Async Operations#

Prefer async operations (async_login, async_terminate) for non-blocking code:

// Good: Non-blocking
session.async_login("", 0, std::chrono::milliseconds(2000),
    [](std::uint64_t seq, const std::error_code& ec) {
        // Handle result
    });

// Avoid: Blocking (unless necessary)
// session.login("", 0, std::chrono::milliseconds(2000));

UserRefNum Management#

Always Recover After Reconnection#

Always recover UserRefNum after reconnection using AccountQueryRequest:

void on_established(Session* s) {
    // Recover UserRefNum immediately after connection
    AccountQueryRequest query;
    s->send_message(&query);
}

void on_account_query_response(Session* s, std::uint64_t seq, const AccountQueryResponse* msg) {
    UInt32 next_user_ref_num = msg->get_next_user_ref_num();
    s->get_user_refnum_generator().reset(next_user_ref_num);
}

Use Persistence in Production#

Enable UserRefNum persistence for production applications:

Settings settings;
UserRefNumGenerator::PersistenceSettings persistence;
persistence.file_path_ = "./user_refnum.dat";
persistence.save_interval_ = std::chrono::seconds(5);
settings.user_refnum_persistence_ = persistence;

Message Buffering#

Enable for Production#

Enable message buffering for production applications that need retransmission:

OutboundMessageBufferSettings buffer_settings;
buffer_settings.max_messages_ = 10000;
buffer_settings.max_bytes_ = 10 * 1024 * 1024;
buffer_settings.track_acknowledgments_ = true;
settings.buffer_settings_ = buffer_settings;
settings.auto_retransmit_on_reconnect_ = true;

Mark Messages as Acknowledged (Optional)#

Consider marking messages as acknowledged when you receive responses to optimize retransmission volume. This is optional - according to OUCH 5.0 specification, the exchange will ignore retransmitted messages with UserRefNum lower than the last processed one (benign retransmission), so acknowledgment tracking is an optimization, not a requirement.

void on_order_accepted(Session* s, std::uint64_t seq, const OrderAccepted* msg) {
    if (auto* buffer = s->get_message_buffer()) {
        buffer->mark_acknowledged(msg->get_user_ref_num());
    }
}

Error Handling#

Check All Return Values#

Always check return values from send_message():

std::error_code ec = session.send_message(&order);
if (ec) {
    std::cerr << "Failed to send order: " << ec.message() << std::endl;
    // Handle error
}

See Error Handling Guide for detailed information on possible errors and handling recommendations.

Handle All Message Types#

Handle all message types you care about, or use a fallback handler:

session.attach_listener(
    [](Session* s, std::uint64_t seq, const OrderAccepted* msg) {
        // Handle OrderAccepted
    },
    [](Session* s, std::uint64_t seq, const Rejected* msg) {
        // Handle Rejected
    },
    // ... other handlers ...
    [](Session* s, std::uint64_t seq, const MessageView& msg) {
        // Fallback for unhandled message types
        std::cout << "Unhandled message type: " << msg.message_type() << std::endl;
    }
);

Thread Safety#

See Thread Safety Guide for comprehensive information on thread safety requirements, including:

  • Operations that can be called from any thread vs. IOContext’s thread

  • Using IOContext::post() for cross-thread access

  • Thread safety for UserRefNum generator and message buffer

  • Best practices for multi-threaded applications

  • Complete examples

Order State Tracking#

Track Order State in Your Application#

Maintain your own order state tracking:

struct OrderState {
    UInt32 user_ref_num_;
    UInt32 quantity_;
    UInt32 executed_quantity_;
    OrderStatus status_;
};

std::map<UInt32, OrderState> order_states_;

void on_order_accepted(Session* s, std::uint64_t seq, const OrderAccepted* msg) {
    UInt32 user_ref_num = msg->get_user_ref_num();
    order_states_[user_ref_num] = OrderState{
        user_ref_num,
        msg->get_quantity(),
        0,
        OrderStatus::Accepted
    };
}

Performance#

Configure CPU Affinity#

Pin threads to specific cores for performance-critical applications:

// Configure IOContext with specific CPU
// CPU affinity mask: bitmask where bit N = CPU core N
// Examples: 0x1 = CPU 0, 0x2 = CPU 1, 0x4 = CPU 2, 0x8 = CPU 3
// Combine cores: 0x3 = CPUs 0 and 1, 0x5 = CPUs 0 and 2
auto io_ctx = std::make_shared<IOContext>(
    IOContext::Settings{"main", 0x1, false}  // CPU 0 (0x1 = bit 0 set)
);

Avoid Blocking in Handlers#

Never perform blocking operations in message handlers:

// Bad: Blocking operation in handler
void on_order_accepted(Session* s, std::uint64_t seq, const OrderAccepted* msg) {
    std::this_thread::sleep_for(std::chrono::seconds(1));  // DON'T DO THIS
}

// Good: Non-blocking handler
void on_order_accepted(Session* s, std::uint64_t seq, const OrderAccepted* msg) {
    // Process immediately, or post to another thread for heavy processing
    process_order_accepted(msg);
}

Code Organization#

Use Type-Safe Message Types#

Always use strongly-typed message types:

// Good: Type-safe
EnterOrder order;
order.set_user_ref_num(user_ref_num);

// Avoid: Manual message construction
// (unless absolutely necessary)

Use Price Type#

Always use the Price type for prices:

// Good: Type-safe price
order.set_price(Price::from_double(150.50));

// Avoid: Raw integer manipulation
// order.set_price_raw(1505000);

Testing#

Test Reconnection Scenarios#

Test your application’s behavior during reconnection:

  • Network disconnection

  • Server restart

  • UserRefNum recovery

  • Message retransmission

Test Error Conditions#

Test error handling:

  • Rejected orders

  • Cancel rejects

  • Connection failures