Bug #14565
open`WIOService` serializes all background work, for all sessions, through a single shared strand
0%
Description
Hi,
The Wt::WServer::post(...), Wt::WServer::schedule(...), Wt::WIOService::post(...) and Wt::WIOService::schedule(0, ...) all route work through a single boost::asio strand that is shared by every WApplication session, and all other callers, in the process.
As a result, all background work, across all users/sessions run in a single-thread through one queue, regardless of the num-threads configuration.
The behavior is undocumented in the public API; the docstring on WIOService::post() says "Posts a function into the thread-pool. The function will be executed within a thread of the thread-pool", which implies parallel execution. And in my case when posting long-running CPU intensive calculations to a background thread, only one of them can run at a time for all sessions (this is better than in Wt 3.7.1 where the background work would also block all UI events as well).
This behavior was introduced in commit e3726036 (March 2016, Wt 3.3.6); it seems like it was to fix a bug in Http::Client for streaming chunks, but was applied session wide. I had found this behavior in Wt 3.3.7, but since that was the last release of 3.X, didn't report it as a bug.
However, I now notice it is still in Wt 4.
The cause is in src/Wt/WIOService.C (Wt 4.12.6, lines 130–141):
void WIOService::schedule(std::chrono::steady_clock::duration millis,
const std::function<void()>& function)
{
if (millis.count() == 0)
asio::post(strand_, function); // guarantees execution order
else { ... timer path that does NOT use the strand ... }
}
In users code, two workarounds are to explicitly call the asio post function, or post with a delay, like:
WServer::instance()->ioService().boost::asio::io_service::post( [](){...} );
// Or
WServer::instance().WIOService::schedule( a_nonzero_delay, [](){...} );
Both paths avoids going through the strand issue.
In looking around the Wt source code some more, it seems src/web/WebMain.C (line ~62) notes this issue, and also works around it (which is probably the improvement over Wt 3.7.1 where it would block UI as well).
It looks like you can safely remove the WIOService::strand_ member variable, and instead add it to WebSession, and just use it in WServer::schedule(duration, sessionId, function, fallback) to maintain a strict ordering of events for a specific instance. It looks like this should take care of when Http::Client is created in a web session (which looks to be the only time it actually uses the strand case).
Or, maybe better yet, Http::Client::Impl looks to have its own strand, that could just be used in all paths where postSignals_ is true - but this assumes some other class doesnt implicitly rely on the old behavior - which I didn't see any, but this is maybe a bit out of my depth.
No data to display