Thread Safety Guide#
Understanding thread safety requirements is critical for using the OUCH 5.0 library correctly.
Key Principle#
The Session class is not thread-safe. Most methods must be called from the thread that runs the IOContext’s event loop. However, some operations (constructor, destructor, and async operations) can be called from any thread.
Safe Operations#
Operations That Can Be Called from Any Thread#
These operations can be called from any thread:
Session()constructor - Can be called from any thread, but Session must be used only from IOContext’s thread after construction~Session()destructor - Safe to call from any threadasync_login()- Asynchronous loginasync_start_with_listeners()- Asynchronous login with listenersasync_terminate()- Asynchronous terminationIOContext::post()- Post work to IOContext thread
Operations That Must Be Called from IOContext Thread#
These operations must be called from the IOContext’s thread:
send_message()- Send messages to exchangeget_user_refnum_generator()- Access UserRefNum generatorget_message_buffer()- Access message bufferattach_listener()/detach_listener()- Manage listeners
Operations That Must NOT Be Called from IOContext Thread#
These operations must NOT be called from the IOContext’s thread (will cause deadlock):
login()- Synchronous login (blocking). Useasync_login()or call from a non-IOContext threadstart_with_listeners()- Synchronous login with listeners. Useasync_start_with_listeners()or call from a non-IOContext thread
Sending Messages from Another Thread#
To send messages from a thread other than the IOContext’s thread, use IOContext::post():
// From main thread or other thread
io_ctx->post([session = session.get(), order]() {
// Now on IOContext's thread - safe to use Session
EnterOrder order_copy = order;
session->send_message(&order_copy); // Safe
});
Async Operations#
Async operations can be called from any thread, but their callbacks run on the IOContext’s thread:
// Can be called from any thread
session->async_login("", 0, std::chrono::milliseconds(2000),
[](std::uint64_t seq, const std::error_code& ec) {
// Callback runs on IOContext's thread
if (!ec) {
// Safe to use session here
std::cout << "Logged in with sequence: " << seq << std::endl;
}
});
UserRefNum Generator#
The UserRefNumGenerator is not thread-safe. It must be used from the IOContext’s thread:
// From IOContext's thread
auto& refnum_gen = session.get_user_refnum_generator();
UInt32 user_ref_num = refnum_gen.next(); // Safe
To generate UserRefNum from another thread:
// From another thread
io_ctx->post([&session]() {
auto& refnum_gen = session.get_user_refnum_generator();
UInt32 user_ref_num = refnum_gen.next(); // Safe: now on IOContext's thread
// Use user_ref_num...
});
Message Handlers#
Message handlers (listeners) are always called on the IOContext’s thread, so it’s safe to use session operations within handlers:
void on_order_accepted(Session* s, std::uint64_t seq, const OrderAccepted* msg) {
// This handler runs on IOContext's thread
// Safe to use session operations
if (auto* buffer = s->get_message_buffer()) {
buffer->mark_acknowledged(msg->get_user_ref_num()); // Safe
}
// Safe to send messages
CancelOrderRequest cancel;
cancel.set_user_ref_num(msg->get_user_ref_num());
cancel.set_quantity(0);
s->send_message(&cancel); // Safe
}
Best Practices#
Always use
IOContext::post()when callingsend_message()from other threadsUse async operations (
async_login,async_terminate) for non-blocking codeAccess UserRefNum generator only from IOContext’s thread (or use
post())Handle exceptions in handlers - wrap handler code in try-catch blocks
Configure CPU affinity - Pin threads to specific cores for performance-critical applications
Avoid blocking operations - Never perform blocking operations in message handlers
Attach listeners from IOContext thread - While not strictly required, attach listeners from the IOContext’s thread to avoid potential race conditions
Example: Multi-Threaded Trading Application#
// Main thread
auto io_ctx = std::make_shared<IOContext>(
IOContext::Settings{"ouch5", 1, false}
);
io_ctx->start();
auto session = std::make_unique<Session>(io_ctx, settings);
// Trading logic thread (different from IOContext thread)
std::thread trading_thread([&]() {
// async_login can be called directly from any thread
session->async_login("", 0, [](std::uint64_t seq, std::error_code ec) {
// Callback runs on ouch5_ctx's thread
});
while (running) {
// Generate order
Order order = generate_order();
// send_message() requires IOContext's thread, so use post()
io_ctx->post([session = session.get(), order]() {
// Now on IOContext's thread - safe to use Session
auto& refnum_gen = session->get_user_refnum_generator();
UInt32 user_ref_num = refnum_gen.next();
EnterOrder msg;
msg.set_user_ref_num(user_ref_num);
// ... set fields from order ...
session->send_message(&msg); // Safe: we're on the correct thread
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
// Keep running
trading_thread.join();