Loading src/blogi.cpp +31 −2 Changes for src/blogi.cpp: 31 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -359,6 +359,7 @@ blogi::Blogi::Blogi(std::vector<netplus::socket *> serversocket, bool debug) #endif } } ctx.plgArgs->dbMutex = std::vector<std::mutex>(ctx.plgArgs->database.size()); std::unique_ptr<dbpp::ReplicatedDatabase> initdb = std::make_unique<dbpp::ReplicatedDatabase>(dcfg.dbDriver, dcfg.dbConnection); for (const auto &rep : dcfg.dbReplicas) Loading Loading @@ -531,6 +532,7 @@ bool blogi::Blogi::reloadConfig() std::cerr << "[blogi] Reload: DB connect failed for '" << dcfg.names[0] << "': " << e.what() << std::endl; continue; } ctx->plgArgs->dbMutex = std::vector<std::mutex>(ctx->plgArgs->database.size()); std::unique_ptr<dbpp::ReplicatedDatabase> initdb = std::make_unique<dbpp::ReplicatedDatabase>(dcfg.dbDriver, dcfg.dbConnection); for (const auto &rep : dcfg.dbReplicas) Loading Loading @@ -3048,13 +3050,19 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U libhttppp::HttpResponse curres; std::string sessid; libhtmlpp::HtmlElement index; Session sess(*PlgArgs->database[tid]); if (curreq.isMobile()) index = ctx->mIndex; else index = ctx->index; { // database[tid] is only safe for one thread at a time (see the // dbMutex comment in plugin.h) — an offloaded /media/getimage/ // stream sharing this tid with another one on the same H2 // connection would otherwise race here on every request. std::lock_guard<std::mutex> dbLock(PlgArgs->dbMutex[tid]); Session sess(*PlgArgs->database[tid]); try { cookie.parse(curreq); Loading Loading @@ -3124,6 +3132,7 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U return; } } } try { Loading Loading @@ -3431,7 +3440,11 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U } libhtmlpp::HtmlString output; std::string _lang = blogi::getLang(*PlgArgs->database[tid]); std::string _lang; { std::lock_guard<std::mutex> dbLock(PlgArgs->dbMutex[tid]); _lang = blogi::getLang(*PlgArgs->database[tid]); } libhtmlpp::HtmlString err; err << "<div id=\"content\">" << "<span>" << blogi::tr(_lang, "Page or content not found") << "</span><br/>" Loading Loading @@ -3460,9 +3473,17 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U } catch (libhttppp::HTTPException &e) { bool shouldRetry = false; { std::lock_guard<std::mutex> dbLock(PlgArgs->dbMutex[tid]); if (!PlgArgs->database[tid]->isConnected() && retries < MAX_RETRIES) { PlgArgs->database[tid]->reset(); shouldRetry = true; } } if (shouldRetry) { ++retries; goto RETRY_REQUEST; } Loading @@ -3470,10 +3491,18 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U } catch (std::exception &e) { bool shouldRetry = false; { std::lock_guard<std::mutex> dbLock(PlgArgs->dbMutex[tid]); if (!PlgArgs->database[tid]->isConnected() && retries < MAX_RETRIES) { std::cerr << "RequestEvent: DB connection lost (" << e.what() << "), reconnecting (attempt " << retries + 1 << "/" << MAX_RETRIES << ")" << std::endl; PlgArgs->database[tid]->reset(); shouldRetry = true; } } if (shouldRetry) { ++retries; goto RETRY_REQUEST; } Loading src/plugin.h +11 −0 Changes for src/plugin.h: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include <memory> #include <map> #include <mutex> #include <vector> #include <chrono> #include <optional> Loading Loading @@ -94,6 +95,16 @@ namespace blogi { struct PluginArgs{ std::vector<std::unique_ptr<dbpp::ReplicatedDatabase>> database; // database[tid] was only ever safe to touch because exactly one // thread used a given tid at a time. The H2 offload pool (see // Blogi::shouldOffloadH2Dispatch) breaks that invariant for // requests handled on it — e.g. two /media/getimage/ streams // multiplexed on the same HTTP/2 connection share the connection's // tid and can run concurrently on different pool threads. Guards // any direct database[tid] access reachable from that path // (RequestEvent's session/cookie-consent handling); one mutex per // tid so unrelated tids/connections never contend. std::vector<std::mutex> dbMutex; std::unique_ptr<Auth> auth; std::unique_ptr<Theme> theme; std::unique_ptr<SmtpSettings> smtp; Loading Loading
src/blogi.cpp +31 −2 Changes for src/blogi.cpp: 31 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -359,6 +359,7 @@ blogi::Blogi::Blogi(std::vector<netplus::socket *> serversocket, bool debug) #endif } } ctx.plgArgs->dbMutex = std::vector<std::mutex>(ctx.plgArgs->database.size()); std::unique_ptr<dbpp::ReplicatedDatabase> initdb = std::make_unique<dbpp::ReplicatedDatabase>(dcfg.dbDriver, dcfg.dbConnection); for (const auto &rep : dcfg.dbReplicas) Loading Loading @@ -531,6 +532,7 @@ bool blogi::Blogi::reloadConfig() std::cerr << "[blogi] Reload: DB connect failed for '" << dcfg.names[0] << "': " << e.what() << std::endl; continue; } ctx->plgArgs->dbMutex = std::vector<std::mutex>(ctx->plgArgs->database.size()); std::unique_ptr<dbpp::ReplicatedDatabase> initdb = std::make_unique<dbpp::ReplicatedDatabase>(dcfg.dbDriver, dcfg.dbConnection); for (const auto &rep : dcfg.dbReplicas) Loading Loading @@ -3048,13 +3050,19 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U libhttppp::HttpResponse curres; std::string sessid; libhtmlpp::HtmlElement index; Session sess(*PlgArgs->database[tid]); if (curreq.isMobile()) index = ctx->mIndex; else index = ctx->index; { // database[tid] is only safe for one thread at a time (see the // dbMutex comment in plugin.h) — an offloaded /media/getimage/ // stream sharing this tid with another one on the same H2 // connection would otherwise race here on every request. std::lock_guard<std::mutex> dbLock(PlgArgs->dbMutex[tid]); Session sess(*PlgArgs->database[tid]); try { cookie.parse(curreq); Loading Loading @@ -3124,6 +3132,7 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U return; } } } try { Loading Loading @@ -3431,7 +3440,11 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U } libhtmlpp::HtmlString output; std::string _lang = blogi::getLang(*PlgArgs->database[tid]); std::string _lang; { std::lock_guard<std::mutex> dbLock(PlgArgs->dbMutex[tid]); _lang = blogi::getLang(*PlgArgs->database[tid]); } libhtmlpp::HtmlString err; err << "<div id=\"content\">" << "<span>" << blogi::tr(_lang, "Page or content not found") << "</span><br/>" Loading Loading @@ -3460,9 +3473,17 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U } catch (libhttppp::HTTPException &e) { bool shouldRetry = false; { std::lock_guard<std::mutex> dbLock(PlgArgs->dbMutex[tid]); if (!PlgArgs->database[tid]->isConnected() && retries < MAX_RETRIES) { PlgArgs->database[tid]->reset(); shouldRetry = true; } } if (shouldRetry) { ++retries; goto RETRY_REQUEST; } Loading @@ -3470,10 +3491,18 @@ void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq, const int tid, U } catch (std::exception &e) { bool shouldRetry = false; { std::lock_guard<std::mutex> dbLock(PlgArgs->dbMutex[tid]); if (!PlgArgs->database[tid]->isConnected() && retries < MAX_RETRIES) { std::cerr << "RequestEvent: DB connection lost (" << e.what() << "), reconnecting (attempt " << retries + 1 << "/" << MAX_RETRIES << ")" << std::endl; PlgArgs->database[tid]->reset(); shouldRetry = true; } } if (shouldRetry) { ++retries; goto RETRY_REQUEST; } Loading
src/plugin.h +11 −0 Changes for src/plugin.h: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include <memory> #include <map> #include <mutex> #include <vector> #include <chrono> #include <optional> Loading Loading @@ -94,6 +95,16 @@ namespace blogi { struct PluginArgs{ std::vector<std::unique_ptr<dbpp::ReplicatedDatabase>> database; // database[tid] was only ever safe to touch because exactly one // thread used a given tid at a time. The H2 offload pool (see // Blogi::shouldOffloadH2Dispatch) breaks that invariant for // requests handled on it — e.g. two /media/getimage/ streams // multiplexed on the same HTTP/2 connection share the connection's // tid and can run concurrently on different pool threads. Guards // any direct database[tid] access reachable from that path // (RequestEvent's session/cookie-consent handling); one mutex per // tid so unrelated tids/connections never contend. std::vector<std::mutex> dbMutex; std::unique_ptr<Auth> auth; std::unique_ptr<Theme> theme; std::unique_ptr<SmtpSettings> smtp; Loading