Skip to content

Commit 379f5f5

Browse files
author
Nijat K
committed
Make configureable num threads, post on strand, not ioc
Signed-off-by: Nijat K <neej@nijats-mbp.mynetworksettings.com>
1 parent 4b8aeb3 commit 379f5f5

9 files changed

Lines changed: 77 additions & 38 deletions

cpp/csp/adapters/websocket/ClientConnectionRequestAdapter.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ ClientConnectionRequestAdapter::ClientConnectionRequestAdapter(
99
WebsocketEndpointManager * websocketManager,
1010
net::io_context& ioc,
1111
bool is_subscribe,
12-
size_t caller_id
12+
size_t caller_id,
13+
boost::asio::strand<boost::asio::io_context::executor_type>& strand
14+
1315
) : OutputAdapter( engine ),
1416
m_websocketManager( websocketManager ),
1517
m_ioc( ioc),
18+
m_strand( strand ),
1619
m_isSubscribe( is_subscribe ),
1720
m_callerId( caller_id ),
1821
m_checkPerformed( is_subscribe ? false : true ) // we only need to check for pruned input adapters
@@ -37,7 +40,7 @@ void ClientConnectionRequestAdapter::executeImpl()
3740
// m_ioc to handle the connection request. We want to keep
3841
// all updates to internal data structures at graph run-time
3942
// to that thread.
40-
boost::asio::post(m_ioc, [this, val=std::move(val)]() {
43+
boost::asio::post(m_strand, [this, val=std::move(val)]() {
4144
for(const auto& conn_req: val) {
4245
m_websocketManager->handleConnectionRequest(conn_req, m_callerId, m_isSubscribe);
4346
}

cpp/csp/adapters/websocket/ClientConnectionRequestAdapter.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class ClientConnectionRequestAdapter final: public OutputAdapter
2222
WebsocketEndpointManager * websocketManager,
2323
net::io_context& ioc,
2424
bool isSubscribe,
25-
size_t callerId
25+
size_t callerId,
26+
boost::asio::strand<boost::asio::io_context::executor_type>& strand
2627
);
2728

2829
void executeImpl() override;
@@ -32,6 +33,7 @@ class ClientConnectionRequestAdapter final: public OutputAdapter
3233
private:
3334
[[maybe_unused]] WebsocketEndpointManager* m_websocketManager;
3435
[[maybe_unused]] net::io_context& m_ioc;
36+
boost::asio::strand<boost::asio::io_context::executor_type>& m_strand;
3537
bool m_isSubscribe;
3638
size_t m_callerId;
3739
bool m_checkPerformed;

cpp/csp/adapters/websocket/ClientHeaderUpdateAdapter.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ class WebsocketEndpointManager;
77
ClientHeaderUpdateOutputAdapter::ClientHeaderUpdateOutputAdapter(
88
Engine * engine,
99
Dictionary& properties,
10-
WebsocketEndpointManager * mgr
11-
) : OutputAdapter( engine ), m_properties( properties ), m_mgr( mgr )
10+
WebsocketEndpointManager * mgr,
11+
boost::asio::strand<boost::asio::io_context::executor_type>& strand
12+
) : OutputAdapter( engine ), m_properties( properties ), m_mgr( mgr ), m_strand( strand )
1213
{ };
1314

1415
void ClientHeaderUpdateOutputAdapter::executeImpl()
@@ -19,8 +20,10 @@ void ClientHeaderUpdateOutputAdapter::executeImpl()
1920
headers.update(update->key(), update->value());
2021
}
2122
}
22-
auto endpoint = m_mgr -> getNonDynamicEndpoint();
23-
endpoint -> updateHeaders(std::move(headers));
23+
boost::asio::post(m_strand, [this, headers=std::move(headers)]() {
24+
auto endpoint = m_mgr -> getNonDynamicEndpoint();
25+
endpoint -> updateHeaders(std::move(headers));
26+
});
2427

2528
// Get the first e
2629
// ndpoint from the map and call updateHeaders

cpp/csp/adapters/websocket/ClientHeaderUpdateAdapter.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class ClientHeaderUpdateOutputAdapter final: public OutputAdapter
1919
ClientHeaderUpdateOutputAdapter(
2020
Engine * engine,
2121
Dictionary& properties,
22-
WebsocketEndpointManager * mgr
22+
WebsocketEndpointManager * mgr,
23+
boost::asio::strand<boost::asio::io_context::executor_type>& strand
2324
);
2425

2526
void executeImpl() override;
@@ -29,6 +30,9 @@ class ClientHeaderUpdateOutputAdapter final: public OutputAdapter
2930
private:
3031
[[maybe_unused]] Dictionary& m_properties;
3132
WebsocketEndpointManager * m_mgr;
33+
boost::asio::strand<boost::asio::io_context::executor_type>& m_strand;
34+
35+
3236

3337
};
3438

cpp/csp/adapters/websocket/ClientOutputAdapter.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@ ClientOutputAdapter::ClientOutputAdapter(
66
Engine * engine,
77
WebsocketEndpointManager * websocketManager,
88
size_t caller_id,
9-
net::io_context& ioc
9+
net::io_context& ioc,
10+
boost::asio::strand<boost::asio::io_context::executor_type>& strand
1011
) : OutputAdapter( engine ),
1112
m_websocketManager( websocketManager ),
1213
m_callerId( caller_id ),
13-
m_ioc( ioc )
14+
m_ioc( ioc ),
15+
m_strand( strand )
1416
{ };
1517

1618
void ClientOutputAdapter::executeImpl()
1719
{
1820
// TODO Add here picking the right endpoints to send to
1921
// Based on the caller id
2022
const std::string & value = input() -> lastValueTyped<std::string>();
21-
boost::asio::post(m_ioc, [this, value=value]() {
23+
boost::asio::post(m_strand, [this, value=value]() {
2224
// something something lifetime? Not sure
2325
m_websocketManager->send(value, m_callerId);
2426
});

cpp/csp/adapters/websocket/ClientOutputAdapter.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class ClientOutputAdapter final: public OutputAdapter
2121
Engine * engine,
2222
WebsocketEndpointManager * websocketManager,
2323
size_t caller_id,
24-
net::io_context& ioc
24+
net::io_context& ioc,
25+
boost::asio::strand<boost::asio::io_context::executor_type>& strand
2526
// bool dynamic
2627
);
2728

@@ -32,7 +33,8 @@ class ClientOutputAdapter final: public OutputAdapter
3233
private:
3334
WebsocketEndpointManager* m_websocketManager;
3435
size_t m_callerId;
35-
net::io_context& m_ioc;
36+
[[maybe_unused]] net::io_context& m_ioc;
37+
boost::asio::strand<boost::asio::io_context::executor_type>& m_strand;
3638
// bool m_dynamic;
3739
// std::unordered_map<std::string, std::vector<bool>>& m_endpoint_consumers;
3840
};

cpp/csp/adapters/websocket/WebsocketEndpointManager.cpp

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
namespace csp::adapters::websocket {
55

66
WebsocketEndpointManager::WebsocketEndpointManager( ClientAdapterManager* mgr, const Dictionary & properties, Engine* engine )
7-
: m_ioc(),
7+
: m_num_threads( static_cast<size_t>(properties.get<int64_t>("num_threads")) ),
8+
m_ioc( m_num_threads ),
89
m_engine( engine ),
10+
m_strand( boost::asio::make_strand(m_ioc) ),
911
m_mgr( mgr ),
1012
m_active( false ),
1113
m_shouldRun( false ),
@@ -22,8 +24,7 @@ WebsocketEndpointManager::WebsocketEndpointManager( ClientAdapterManager* mgr, c
2224
// m_work_guard(properties.get<bool>("dynamic") ?
2325
// std::make_optional(boost::asio::make_work_guard(m_ioc)) :
2426
// std::nullopt),
25-
m_dynamic( properties.get<bool>("dynamic") )
26-
{
27+
m_dynamic( properties.get<bool>("dynamic") ){
2728
// Total number of subscribe and send function calls, set on the adapter manager
2829
// when is it created. Note, that some of the input adapters might have been
2930
// pruned from the graph and won't get created.
@@ -49,21 +50,29 @@ WebsocketEndpointManager::WebsocketEndpointManager( ClientAdapterManager* mgr, c
4950
void WebsocketEndpointManager::start(DateTime starttime, DateTime endtime) {
5051
// maybe restart here?
5152
m_shouldRun = true;
52-
m_thread = std::make_unique<std::thread>([this]() {
53-
m_ioc.reset();
54-
if( !m_dynamic ){
55-
boost::asio::post(m_ioc, [this]() {
56-
// We subscribe for both the subscribe and send calls
57-
// But we probably should check here.
58-
if( m_outputAdapters.size() == 1)
59-
handleConnectionRequest(Dictionary(m_properties), 0, false);
60-
// // If we have an input adapter call AND it's not pruned.
61-
if( m_inputAdapters.size() == 1 && !adapterPruned(0))
62-
handleConnectionRequest(Dictionary(m_properties), 0, true);
63-
});
64-
}
65-
m_ioc.run();
66-
});
53+
// std::vector<std::thread> threads;
54+
55+
m_ioc.reset();
56+
if( !m_dynamic ){
57+
boost::asio::post(m_strand, [this]() {
58+
// We subscribe for both the subscribe and send calls
59+
// But we probably should check here.
60+
if( m_outputAdapters.size() == 1)
61+
handleConnectionRequest(Dictionary(m_properties), 0, false);
62+
// // If we have an input adapter call AND it's not pruned.
63+
if( m_inputAdapters.size() == 1 && !adapterPruned(0))
64+
handleConnectionRequest(Dictionary(m_properties), 0, true);
65+
});
66+
}
67+
for (auto i = 0; i < m_num_threads; ++i) {
68+
m_threads.emplace_back(std::make_unique<std::thread>([this]() {
69+
m_ioc.run();
70+
}));
71+
}
72+
// m_thread = std::make_unique<std::thread>([this]() {
73+
// // m_ioc.reset();
74+
// m_ioc.run();
75+
// });
6776
};
6877

6978
bool WebsocketEndpointManager::adapterPruned( size_t caller_id ){
@@ -403,7 +412,7 @@ void WebsocketEndpointManager::stop() {
403412
// Stop all endpoints
404413
// Endpoints running on m_ioc thread,
405414
// So we call stop there
406-
boost::asio::post(m_ioc, [this]() {
415+
boost::asio::post(m_strand, [this]() {
407416
for (auto& [endpoint_id, _] : m_endpoints) {
408417
// TODO ponder
409418
// Since this is called from the main thread,
@@ -422,6 +431,16 @@ void WebsocketEndpointManager::stop() {
422431
m_ioc.stop();
423432
m_cv.notify_one();
424433
if( m_thread ) m_thread->join();
434+
435+
// Wait for all threads to finish
436+
for (auto& thread : m_threads) {
437+
if (thread && thread->joinable()) {
438+
thread->join();
439+
}
440+
}
441+
442+
// Clear threads before other members are destroyed
443+
m_threads.clear();
425444
};
426445

427446
PushInputAdapter* WebsocketEndpointManager::getInputAdapter(CspTypePtr & type, PushMode pushMode, const Dictionary & properties)
@@ -445,15 +464,15 @@ OutputAdapter* WebsocketEndpointManager::getOutputAdapter( const Dictionary & pr
445464
assert(!properties.get<bool>("is_subscribe"));
446465
assert(m_outputAdapters.size() == validated_id);
447466

448-
auto output_adapter = m_engine -> createOwnedObject<ClientOutputAdapter>( this, validated_id, m_ioc );
467+
auto output_adapter = m_engine -> createOwnedObject<ClientOutputAdapter>( this, validated_id, m_ioc, m_strand );
449468
m_outputAdapters[validated_id] = output_adapter;
450469
return m_outputAdapters[validated_id];
451470
};
452471

453472
OutputAdapter * WebsocketEndpointManager::getHeaderUpdateAdapter()
454473
{
455474
if (m_updateAdapter == nullptr)
456-
m_updateAdapter = m_engine -> createOwnedObject<ClientHeaderUpdateOutputAdapter>( m_endpoint -> getProperties(), this );
475+
m_updateAdapter = m_engine -> createOwnedObject<ClientHeaderUpdateOutputAdapter>( m_endpoint -> getProperties(), this, m_strand );
457476

458477
return m_updateAdapter;
459478
};
@@ -465,7 +484,7 @@ OutputAdapter * WebsocketEndpointManager::getConnectionRequestAdapter( const Dic
465484
auto is_subscribe = properties.get<bool>("is_subscribe");
466485

467486
auto* adapter = m_engine->createOwnedObject<ClientConnectionRequestAdapter>(
468-
this, m_ioc, is_subscribe, caller_id
487+
this, m_ioc, is_subscribe, caller_id, m_strand
469488
);
470489
m_connectionRequestAdapters.push_back(adapter);
471490

cpp/csp/adapters/websocket/WebsocketEndpointManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,10 @@ class WebsocketEndpointManager {
133133
using UriInfo = std::tuple<int32_t, std::unordered_set<uint64_t>>; //TODO remove
134134
using OptWorkGuard = std::optional<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>;
135135
std::unique_ptr<WebsocketEndpointManager> m_endpointManager;
136+
size_t m_num_threads;
136137
net::io_context m_ioc;
137138
Engine* m_engine;
139+
[[maybe_unused]] boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
138140
ClientAdapterManager* m_mgr;
139141
[[maybe_unused]] bool m_active;
140142
[[maybe_unused]] std::atomic<bool> m_shouldRun{false};
@@ -145,6 +147,7 @@ class WebsocketEndpointManager {
145147
[[maybe_unused]] ClientOutputAdapter* m_outputAdapter;
146148
ClientHeaderUpdateOutputAdapter* m_updateAdapter;
147149
std::unique_ptr<std::thread> m_thread;
150+
std::vector<std::unique_ptr<std::thread>> m_threads;
148151
Dictionary m_properties;
149152
// For each subscribe call, which uri's it is subscribed to
150153
std::vector<std::unordered_set<std::string>> m_subscribeFromUri;

csp/adapters/websocket.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ def __init__(
422422
headers: Optional[Dict[str, str]] = None,
423423
dynamic: bool = False,
424424
connection_request: Optional[ConnectionRequest] = None,
425-
num_threads: int = 1,
425+
num_threads: int = 2,
426426
):
427427
"""
428428
uri: str
@@ -433,8 +433,9 @@ def __init__(
433433
headers to apply to the request during the handshake
434434
dynamic: bool = False
435435
Whether we accept dynamically altering the connections via ConnectionRequest objects.
436-
num_threads: int = 1
437-
If in dynamic mode, determines number of threads to allocate for thread pool handling websocket endpoints.
436+
num_threads: int = 2
437+
Determines number of threads to allocate for thread pool handling websocket endpoints.
438+
Defaults to 2 to avoid deadlocks and latency spikes
438439
"""
439440

440441
self._properties = dict(dynamic=dynamic, num_threads=num_threads)

0 commit comments

Comments
 (0)