Session API#

The Session class is the main interface for OUCH 5.0 communication. It extends soupbintcp::Session and provides connection handling, message routing, and state management.

Overview#

The Session class provides:

  • Connection handling with automatic failover

  • Login/logout with sequence number management

  • State management (Connecting, Established, Disconnected, etc.)

  • Event handling (LinkUp, LinkDown, etc.)

  • Message routing to type-safe handlers

Class Definition#

class Session : public soupbintcp::Session {
public:
    // Constructor
    Session(std::shared_ptr<IOContext> ctx, const Settings& settings);
    
    // Attach/detach listeners
    template <typename Handler>
    void attach_listener(Handler&& h);
    void detach_listener();
    
    // Login/logout
    std::uint64_t login(const std::string& session_id, 
                       std::uint64_t requested_seq_num,
                       std::chrono::milliseconds timeout);
    void async_login(const std::string& session_id,
                    std::uint64_t requested_seq_num,
                    std::chrono::milliseconds timeout,
                    std::function<void(std::uint64_t, const std::error_code&)> callback);
    template <typename Handler>
    std::uint64_t start_with_listeners(const std::string& session_id,
                                       std::uint64_t requested_seq_num,
                                       std::chrono::milliseconds timeout,
                                       Handler&& h);
    template <typename Handler>
    void async_start_with_listeners(const std::string& session_id,
                                    std::uint64_t requested_seq_num,
                                    std::chrono::milliseconds timeout,
                                    Handler&& listener_handler,
                                    std::function<void(const std::error_code&, std::uint64_t)> completion_handler);
    
    // Send messages
    template<typename MessageType>
    std::error_code send_message(const MessageType* msg);
    
    // UserRefNum generator
    UserRefNumGenerator& get_user_refnum_generator();
    
    // Message buffer
    OutboundMessageBuffer* get_message_buffer();
    bool is_buffering_enabled() const;
    std::error_code retransmit_unacknowledged();
};

Constructor#

Session#

Session(std::shared_ptr<IOContext> ctx, const Settings& settings);

Creates a new OUCH 5.0 session.

Parameters:

  • ctx: Shared pointer to IOContext managing the event loop

  • settings: Session configuration settings

Throws: std::runtime_error if persistence file cannot be opened (if persistence is enabled in settings)

Thread Safety: Constructor can be called from any thread, but Session must be used only from IOContext’s thread after construction.

Example:

auto io_ctx = std::make_shared<IOContext>(IOContext::Settings{"main", 0, false});
io_ctx->start();

Settings settings;
settings.transport_.username_ = "TRADER01";
settings.transport_.password_ = "password123";
settings.transport_.remote_a_ = Endpoint{"192.168.1.100", 64002};
settings.initial_user_ref_num_ = 1;

Session session(io_ctx, settings);

Listener Management#

The Session class provides two patterns for registering message handlers:

  1. Fluent Builder API (recommended): Method chaining for piecemeal handler registration

  2. Delegate Pattern: Single handler object with overloaded operator() methods

Performance Note: Both patterns use the same underlying delegate mechanism for message dispatch. The fluent API adds a small overhead (one null check and one helper function call) compared to direct delegate registration, but this overhead is negligible in practice (< 1% of total message processing time). The performance difference is typically not measurable in real-world applications, as message processing (parsing, business logic) dominates execution time. Choose based on code readability and maintainability rather than performance.

Delegate Pattern (Legacy)#

attach_listener#

template <typename Handler>
void attach_listener(Handler&& h);

Attaches a message handler to the session using the delegate pattern. The handler must provide overloaded operator() methods for each message type you want to handle.

Example with function object:

struct Handler {
    void operator()(Session* s, std::uint64_t seq, const OrderAccepted* msg) {
        // Handle OrderAccepted
    }
    void operator()(Session* s, std::uint64_t seq, const OrderExecuted* msg) {
        // Handle OrderExecuted
    }
};

Handler handler;
session.attach_listener(std::ref(handler));

Example with lambdas:

session.attach_listener(
    [](Session* s, std::uint64_t seq, const OrderAccepted* msg) {
        // Handle OrderAccepted
    },
    [](Session* s, std::uint64_t seq, const OrderExecuted* msg) {
        // Handle OrderExecuted
    }
);

Comparison:

The fluent API and delegate pattern can be used together. The fluent API is recommended for new code because it:

  • Reduces nesting (no need to define all handlers in one place)

  • Allows incremental registration

  • Provides better code readability

The delegate pattern is still available for backward compatibility and for cases where you need to handle multiple message types in a single handler object.

detach_listener#

void detach_listener();

Removes the currently attached listener.

Connection Management#

login#

std::uint64_t login(const std::string& session_id, 
                   std::uint64_t requested_seq_num,
                   std::chrono::milliseconds timeout);

Synchronously logs in to the OUCH 5.0 server.

Parameters:

  • session_id: Session identifier (empty string for new session)

  • requested_seq_num: Requested sequence number (0 for new session)

  • timeout: Login timeout duration

Returns: Sequence number after login

Thread Safety: Must NOT be called from IOContext’s thread (will cause deadlock). This is a blocking method that waits for IOContext progress, so calling it from the IOContext thread will deadlock. Call from a non-IOContext thread, or use async_login() instead.

Example:

// From main thread (non-IOContext thread)
std::uint64_t seq = session.login("", 0, std::chrono::milliseconds(2000));

async_login#

void async_login(const std::string& session_id,
                std::uint64_t requested_seq_num,
                std::chrono::milliseconds timeout,
                std::function<void(std::uint64_t, const std::error_code&)> callback);

Asynchronously logs in to the OUCH 5.0 server. Can be called from any thread.

Parameters:

  • session_id: Session identifier (empty string for new session)

  • requested_seq_num: Requested sequence number (0 for new session)

  • timeout: Login timeout duration

  • callback: Callback function called when login completes

Example:

session.async_login("", 0, std::chrono::milliseconds(2000),
    [](std::uint64_t seq, const std::error_code& ec) {
        if (!ec) {
            std::cout << "Logged in with sequence: " << seq << std::endl;
        } else {
            std::cerr << "Login failed: " << ec.message() << std::endl;
        }
    });

start#

void start(const std::string& session_id = "",
           std::uint64_t requested_seq = 0,
           std::chrono::milliseconds timeout = std::chrono::milliseconds(2000),
           std::function<void(std::error_code)> callback = nullptr);

Convenience method that starts the session after configuring handlers via the fluent API. This method calls async_login() internally and invokes the callback when login completes.

Parameters:

  • session_id: Session identifier (empty string for new session)

  • requested_seq: Requested sequence number (0 for new session)

  • timeout: Login timeout duration

  • callback: Optional callback invoked when login completes

Thread Safety: Can be called from any thread (async operation).

Example with fluent API:

session
    .on_order_accepted([](const OrderAccepted* msg) {
        // Handle OrderAccepted
    })
    .on_order_executed([](const OrderExecuted* msg) {
        // Handle OrderExecuted
    })
    .start("", 0, std::chrono::milliseconds(2000), [](std::error_code ec) {
        if (!ec) {
            std::cout << "Session started successfully" << std::endl;
        }
    });

start_with_listeners#

template <typename Handler>
std::uint64_t start_with_listeners(const std::string& session_id,
                                   std::uint64_t requested_seq_num,
                                   std::chrono::milliseconds timeout,
                                   Handler&& h);

Convenience method that attaches listeners using the delegate pattern and then calls login() synchronously in a single call. This prevents race conditions where messages arrive before listeners are ready.

Parameters:

  • session_id: Session identifier (empty string for new session)

  • requested_seq_num: Requested sequence number (0 for new session)

  • timeout: Login timeout duration

  • h: Handler to attach (forwarded to attach_listener())

Returns: Sequence number assigned by server, or 0 on failure

Thread Safety: Must NOT be called from IOContext’s thread (will throw std::runtime_error). Safe to call from non-IO threads while IOContext is running.

Example:

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

Note: For new code, prefer the fluent API with start() method instead of start_with_listeners().

async_start_with_listeners#

template <typename Handler>
void async_start_with_listeners(const std::string& session_id,
                                std::uint64_t requested_seq_num,
                                std::chrono::milliseconds timeout,
                                Handler&& listener_handler,
                                std::function<void(const std::error_code&, std::uint64_t)> completion_handler);

Convenience method that attaches listeners and then calls async_login() in a single call. This prevents race conditions where messages arrive before listeners are ready.

Parameters:

  • session_id: Session identifier (empty string for new session)

  • requested_seq_num: Requested sequence number (0 for new session)

  • timeout: Login timeout duration

  • listener_handler: Handler to attach (forwarded to attach_listener())

  • completion_handler: Callback invoked when login completes: void(const std::error_code&, std::uint64_t)

Thread Safety: Can be called from any thread (async operation).

Example:

session.async_start_with_listeners("", 0, std::chrono::milliseconds(2000),
    [](Session* s, std::uint64_t seq, const OrderAccepted* msg) {
        // Handle OrderAccepted
    },
    [](const std::error_code& ec, std::uint64_t seq) {
        if (!ec) {
            std::cout << "Logged in with sequence: " << seq << std::endl;
        }
    });

Sending Messages#

send_message#

template<typename MessageType>
std::error_code send_message(const MessageType* msg);

Sends an egress message (Client → Exchange) to the exchange. Must be called from the IOContext’s thread.

Parameters:

  • msg: Pointer to the message to send

Returns: Error code (empty if successful)

Example:

EnterOrder order;
order.set_user_ref_num(user_ref_num);
order.set_side(Side::Buy);
// ... set other fields ...

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

Thread Safety: This method must be called from the IOContext’s thread. Calling it from another thread is undefined behavior. To send from another thread, marshal to the IOContext thread via IOContext::post() (or dispatch()):

io_ctx->post([session = session.get()]() {
    EnterOrder order;
    // ... set fields ...
    session->send_message(&order);  // Safe: now on IOContext's thread
});

Note: Avoid posting a pointer to a stack message that may go out of scope before the handler runs. Prefer constructing the message inside the posted handler (as shown) or capturing it by value.

UserRefNum Management#

get_user_refnum_generator#

UserRefNumGenerator& get_user_refnum_generator();

Returns a reference to the UserRefNum generator for this session.

Example:

auto& refnum_gen = session.get_user_refnum_generator();
UInt32 user_ref_num = refnum_gen.next();  // Strictly increasing

See UserRefNum Management for more details.

Message Buffering#

get_message_buffer#

OutboundMessageBuffer* get_message_buffer();

Returns a pointer to the message buffer if buffering is enabled, or nullptr otherwise.

Example:

if (auto* buffer = session.get_message_buffer()) {
    buffer->mark_acknowledged(user_ref_num);
}

is_buffering_enabled#

bool is_buffering_enabled() const;

Returns true if message buffering is enabled for this session.

See Message Buffering for more details.

retransmit_unacknowledged#

std::error_code retransmit_unacknowledged();

Retransmits all unacknowledged messages from the buffer. This is typically called after reconnecting to recover from a disconnection.

Returns: Error code (empty if successful, or last error if any message failed to send)

Note: This is a no-op if buffering is disabled. Called automatically on reconnect if auto_retransmit_on_reconnect_ is true in Settings.

Thread Safety: Must be called from IOContext’s thread.

Example:

// After reconnection
if (auto* buffer = session.get_message_buffer()) {
    std::error_code ec = session.retransmit_unacknowledged();
    if (ec) {
        std::cerr << "Retransmission error: " << ec.message() << std::endl;
    }
}

See Message Buffering for more details.

Simplified Recovery API#

OUCH 5.0 clients typically recover UserRefNum state after (re)connect by sending an AccountQueryRequest and waiting for AccountQueryResponse (which carries NextUserRefNum).

Session provides one-call helpers that perform the standard recovery steps automatically:

  • recover_user_refnum(): sends query, waits for response (with timeout), then:

    • resets UserRefNumGenerator to NextUserRefNum

    • marks egress message buffer up to NextUserRefNum - 1 as acknowledged (if buffering enabled and ack tracking enabled)

  • recover_and_retransmit(): same recovery, then retransmit_unacknowledged() (no-op if buffering disabled)

recover_user_refnum#

void recover_user_refnum(
    std::chrono::milliseconds timeout = std::chrono::milliseconds(5000),
    std::function<void(std::error_code)> callback = nullptr);

Thread Safety: Can be called from any thread. The callback is invoked on the IOContext thread. Do not block in the callback.

recover_and_retransmit#

void recover_and_retransmit(
    std::chrono::milliseconds timeout = std::chrono::milliseconds(5000),
    std::function<void(std::error_code)> callback = nullptr);

Thread Safety: Can be called from any thread. The callback is invoked on the IOContext thread. Do not block in the callback.

Settings#

struct Settings {
    soupbintcp::Settings transport_;              // SoupBinTCP transport settings
    UInt32 initial_user_ref_num_ = 1;            // Initial UserRefNum value
    std::optional<OutboundMessageBufferSettings> buffer_settings_;  // Optional message buffering
    bool auto_retransmit_on_reconnect_ = false;   // Auto-retransmit on reconnect
    std::optional<UserRefNumGenerator::PersistenceSettings> user_refnum_persistence_;  // Optional persistence
};

State Management#

The session maintains connection state and provides automatic readiness tracking:

  • Connecting: Establishing connection

  • Established: Transport connection established (not necessarily ready for order flow)

  • Disconnected: Connection lost

  • Terminating: Shutting down

  • Terminated: Fully shut down

Readiness for order flow is exposed via:

bool is_connected() const noexcept;
bool is_user_refnum_recovered() const noexcept;
bool is_ready() const noexcept;
  • is_connected(): true when the session is Established

  • is_user_refnum_recovered(): true after an AccountQueryResponse is received (standard OUCH 5.0 recovery step)

  • is_ready(): true when connected AND recovered

on_ready() is invoked once per transition into ready state (and is also invoked immediately if registered after the session is already ready).