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 loopsettings: 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:
Fluent Builder API (recommended): Method chaining for piecemeal handler registration
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.
Fluent Builder API (Recommended)#
The fluent API allows you to register handlers incrementally using method chaining, eliminating deep nesting and improving code readability. All fluent API methods are code-generated from the XML schema, ensuring they stay in sync with message definitions.
Basic Usage:
session
.on_order_accepted([](const OrderAccepted* msg) {
std::cout << "Order accepted: " << msg->get_user_ref_num() << std::endl;
})
.on_order_executed([](const OrderExecuted* msg) {
std::cout << "Order executed: " << msg->get_user_ref_num() << std::endl;
})
.on_rejected([](const Rejected* msg) {
std::cerr << "Order rejected: " << static_cast<int>(msg->get_reject_reason()) << std::endl;
});
Special Handlers:
session
.on_state_changed([](State old_state, State new_state) {
std::cout << "State changed: " << static_cast<int>(old_state)
<< " -> " << static_cast<int>(new_state) << std::endl;
})
.on_ready([]() {
std::cout << "Session is ready!" << std::endl;
})
.on_error([](std::error_code ec, const std::string& msg) {
std::cerr << "Error: " << ec.message() << " - " << msg << std::endl;
})
.on_unhandled_message([](const MessageView& view) {
std::cout << "Unhandled message type: " << view.get_message_type() << std::endl;
});
Centralized error handling details:
The
on_error()handler is invoked on the IOContext’s thread (same as all Session callbacks).The second argument is a context string describing the operation that failed (examples):
"send_message""recover_user_refnum""recover_and_retransmit""retransmit_unacknowledged""auto_retransmit_on_reconnect""start""async_start_with_listeners"
Incremental Registration:
// Register handlers as needed, not all at once
session.on_order_accepted([](const OrderAccepted* msg) { /* ... */ });
// Later, add more handlers
session.on_order_executed([](const OrderExecuted* msg) { /* ... */ });
Available Fluent API Methods:
All ingress message types (Exchange → Client) have corresponding on_<message_type>() methods:
on_system_event()- System events (StartOfDay, EndOfDay, etc.)on_order_accepted()- Order acceptance notificationson_order_replaced()- Order replacement notificationson_order_canceled()- Order cancellation notificationson_aiq_canceled()- AIQ cancellation notificationson_order_executed()- Order execution notificationson_broken_trade()- Broken trade notificationson_rejected()- Order rejection notificationson_cancel_pending()- Cancel pending notificationson_cancel_reject()- Cancel rejection notificationson_order_priority_update()- Order priority updateson_order_modified()- Order modification notificationson_order_restated()- Order restatement notificationson_mass_cancel_response()- Mass cancel responseson_disable_order_entry_response()- Disable order entry responseson_enable_order_entry_response()- Enable order entry responseson_account_query_response()- Account query responses
Thread Safety: All fluent API handlers are invoked on the IOContext’s thread. Handlers must not perform blocking operations.
Example with start():
session
.on_order_accepted([](const OrderAccepted* msg) {
// Handle order acceptance
})
.on_order_executed([](const OrderExecuted* msg) {
// Handle order execution
})
.start("", 0, std::chrono::milliseconds(2000), [](std::error_code ec) {
if (!ec) {
std::cout << "Session started successfully" << std::endl;
}
});
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 durationcallback: 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 durationcallback: 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 durationh: Handler to attach (forwarded toattach_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 durationlistener_handler: Handler to attach (forwarded toattach_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
UserRefNumGeneratortoNextUserRefNummarks egress message buffer up to
NextUserRefNum - 1as acknowledged (if buffering enabled and ack tracking enabled)
recover_and_retransmit(): same recovery, thenretransmit_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.
Example (recommended pattern)#
session
.on_state_changed([&](State, State new_state) {
if (new_state == State::Established) {
// ⚠️ This callback runs on IOContext's thread. Do not block.
session.recover_and_retransmit(std::chrono::milliseconds(5000), [](std::error_code ec) {
if (ec) {
// Handle timeout / send failure / retransmit failure
}
});
}
})
.on_ready([&]() {
// Session is connected AND UserRefNum recovered (safe to start order flow)
});
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 connectionEstablished: Transport connection established (not necessarily ready for order flow)Disconnected: Connection lostTerminating: Shutting downTerminated: 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 Establishedis_user_refnum_recovered(): true after anAccountQueryResponseis 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).