Loading editor/src/webedit_api.cpp +26 −0 Changes for editor/src/webedit_api.cpp: 26 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ #include <mutex> #include <iterator> #include <cstdint> #include <thread> #include <htmlpp/html.h> #include <httppp/exception.h> Loading Loading @@ -3645,6 +3646,31 @@ json_object *webedit::Api::callAiTool(const std::string &sessionid, const std::s void webedit::Api::handleAiPrompt(libhttppp::HttpRequest &curreq, const std::string &sessionid) { // Admission control: this handler can block its worker thread for // minutes if the AI backend is slow/stuck (see kMaxAiToolRounds's // per-round connect/read timeouts below). The server's worker pool is // small and fixed-size (roughly one per CPU core) and shared by every // route, so letting unbounded concurrent AI requests pile up can starve // completely unrelated endpoints. Reject early instead of queuing up // behind an already-stuck request. static const int kMaxConcurrentAiRequests = []() { unsigned hw = std::thread::hardware_concurrency(); return (std::max)(1, static_cast<int>(hw / 2)); }(); struct AiRequestGuard { std::atomic<int> &counter; ~AiRequestGuard() { counter.fetch_sub(1, std::memory_order_relaxed); } }; if (_activeAiRequests.fetch_add(1, std::memory_order_relaxed) >= kMaxConcurrentAiRequests) { _activeAiRequests.fetch_sub(1, std::memory_order_relaxed); sendJsonError(curreq, 429, "Zu viele gleichzeitige KI-Anfragen -- bitte kurz warten und erneut versuchen."); return; } AiRequestGuard aiRequestGuard{_activeAiRequests}; std::string body = getRequestBody(curreq); json_object *req = json_tokener_parse(body.c_str()); if (!req) { Loading editor/src/webedit_api.h +12 −0 Changes for editor/src/webedit_api.h: 12 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ #include <memory> #include <mutex> #include <map> #include <atomic> #include <stdexcept> #include <json-c/json.h> Loading Loading @@ -212,6 +213,17 @@ namespace webedit { std::map<std::string, ConnSession> _connSessions; std::string _activeConnId; // most recently connected connId (guarded by _connSessionMtx) // handleAiPrompt can block its worker thread for a long time (up to // the AI backend's read/connect timeout) if the backend is slow or // stuck. The server's worker pool is a small, fixed size (roughly // one per CPU core) shared by every route, so enough concurrent // stuck AI requests can starve the whole server -- including // completely unrelated endpoints. This caps how many /api/ai/prompt // requests may occupy a worker at once; beyond that, new requests // are rejected immediately with a clear error instead of queuing // up behind an already-stuck one. std::atomic<int> _activeAiRequests{0}; // Document section list (for section_select widget field) void handleListDocumentSections(libhttppp::HttpRequest &curreq, const std::string &sessionid); Loading Loading
editor/src/webedit_api.cpp +26 −0 Changes for editor/src/webedit_api.cpp: 26 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ #include <mutex> #include <iterator> #include <cstdint> #include <thread> #include <htmlpp/html.h> #include <httppp/exception.h> Loading Loading @@ -3645,6 +3646,31 @@ json_object *webedit::Api::callAiTool(const std::string &sessionid, const std::s void webedit::Api::handleAiPrompt(libhttppp::HttpRequest &curreq, const std::string &sessionid) { // Admission control: this handler can block its worker thread for // minutes if the AI backend is slow/stuck (see kMaxAiToolRounds's // per-round connect/read timeouts below). The server's worker pool is // small and fixed-size (roughly one per CPU core) and shared by every // route, so letting unbounded concurrent AI requests pile up can starve // completely unrelated endpoints. Reject early instead of queuing up // behind an already-stuck request. static const int kMaxConcurrentAiRequests = []() { unsigned hw = std::thread::hardware_concurrency(); return (std::max)(1, static_cast<int>(hw / 2)); }(); struct AiRequestGuard { std::atomic<int> &counter; ~AiRequestGuard() { counter.fetch_sub(1, std::memory_order_relaxed); } }; if (_activeAiRequests.fetch_add(1, std::memory_order_relaxed) >= kMaxConcurrentAiRequests) { _activeAiRequests.fetch_sub(1, std::memory_order_relaxed); sendJsonError(curreq, 429, "Zu viele gleichzeitige KI-Anfragen -- bitte kurz warten und erneut versuchen."); return; } AiRequestGuard aiRequestGuard{_activeAiRequests}; std::string body = getRequestBody(curreq); json_object *req = json_tokener_parse(body.c_str()); if (!req) { Loading
editor/src/webedit_api.h +12 −0 Changes for editor/src/webedit_api.h: 12 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ #include <memory> #include <mutex> #include <map> #include <atomic> #include <stdexcept> #include <json-c/json.h> Loading Loading @@ -212,6 +213,17 @@ namespace webedit { std::map<std::string, ConnSession> _connSessions; std::string _activeConnId; // most recently connected connId (guarded by _connSessionMtx) // handleAiPrompt can block its worker thread for a long time (up to // the AI backend's read/connect timeout) if the backend is slow or // stuck. The server's worker pool is a small, fixed size (roughly // one per CPU core) shared by every route, so enough concurrent // stuck AI requests can starve the whole server -- including // completely unrelated endpoints. This caps how many /api/ai/prompt // requests may occupy a worker at once; beyond that, new requests // are rejected immediately with a clear error instead of queuing // up behind an already-stuck one. std::atomic<int> _activeAiRequests{0}; // Document section list (for section_select widget field) void handleListDocumentSections(libhttppp::HttpRequest &curreq, const std::string &sessionid); Loading