diff options
| author | pepper <3782201+rohvani@users.noreply.github.com> | 2026-09-21 17:44:02 -0400 |
|---|---|---|
| committer | pepper <3782201+rohvani@users.noreply.github.com> | 2026-09-21 17:44:02 -0400 |
| commit | d06964f46025e135b74b3dd1e6c144c364bf6d1b (patch) | |
| tree | aae1bfdf8f6e6ef3f8f4adcd96c927ac8760da06 /indra/llcorehttp | |
| parent | cc5d3dd3cd653971fff16cf2bc1d380edd5ea7f2 (diff) | |
| parent | 0a60806f8973c05b68cf65a5a01c41d81ff2da7b (diff) | |
Merge release/26.5 into project/chat-modernization
Preserve the history reader's page position while incorporating the focused page-selector refresh fix.
Diffstat (limited to 'indra/llcorehttp')
| -rw-r--r-- | indra/llcorehttp/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | indra/llcorehttp/httpstats.cpp | 3 | ||||
| -rw-r--r-- | indra/llcorehttp/httpstats.h | 12 | ||||
| -rw-r--r-- | indra/llcorehttp/lljsonrpcws.cpp | 142 | ||||
| -rw-r--r-- | indra/llcorehttp/lljsonrpcws.h | 18 | ||||
| -rw-r--r-- | indra/llcorehttp/tests/llcorehttp_test.cpp | 1 | ||||
| -rw-r--r-- | indra/llcorehttp/tests/test_jsonrpcws.hpp | 146 |
7 files changed, 292 insertions, 31 deletions
diff --git a/indra/llcorehttp/CMakeLists.txt b/indra/llcorehttp/CMakeLists.txt index fbc25a7cd5..c2c4078ff0 100644 --- a/indra/llcorehttp/CMakeLists.txt +++ b/indra/llcorehttp/CMakeLists.txt @@ -119,6 +119,7 @@ if (LL_TESTS AND LLCOREHTTP_TESTS) tests/test_httpheaders.hpp tests/test_bufferarray.hpp tests/test_bufferstream.hpp + tests/test_jsonrpcws.hpp ) list(APPEND llcorehttp_TEST_SOURCE_FILES ${llcorehttp_TEST_HEADER_FILES}) diff --git a/indra/llcorehttp/httpstats.cpp b/indra/llcorehttp/httpstats.cpp index dc2ac35c71..f64752e91e 100644 --- a/indra/llcorehttp/httpstats.cpp +++ b/indra/llcorehttp/httpstats.cpp @@ -41,6 +41,7 @@ HTTPStats::~HTTPStats() void HTTPStats::resetStats() { + std::lock_guard<std::mutex> lock(mStatsMutex); mResutCodes.clear(); mDataDown.reset(); mDataUp.reset(); @@ -50,6 +51,7 @@ void HTTPStats::resetStats() void HTTPStats::recordResultCode(S32 code) { + std::lock_guard<std::mutex> lock(mStatsMutex); std::map<S32, S32>::iterator it; it = mResutCodes.find(code); @@ -86,6 +88,7 @@ namespace void HTTPStats::dumpStats() { + std::lock_guard<std::mutex> lock(mStatsMutex); std::stringstream out; out << "HTTP DATA SUMMARY" << std::endl; diff --git a/indra/llcorehttp/httpstats.h b/indra/llcorehttp/httpstats.h index 5c0f26d34e..08a537cda7 100644 --- a/indra/llcorehttp/httpstats.h +++ b/indra/llcorehttp/httpstats.h @@ -47,17 +47,23 @@ namespace LLCore void recordDataDown(size_t bytes) { + std::lock_guard<std::mutex> lock(mStatsMutex); mDataDown.push((F32)bytes); } void recordDataUp(size_t bytes) { + std::lock_guard<std::mutex> lock(mStatsMutex); mDataUp.push((F32)bytes); } - void recordHTTPRequest() { ++mRequests; } + void recordHTTPRequest() + { + std::lock_guard<std::mutex> lock(mStatsMutex); + ++mRequests; + } - void recordResultCode(S32 code); + void recordResultCode(S32 code); // http thread void dumpStats(); private: @@ -67,6 +73,8 @@ namespace LLCore S32 mRequests; std::map<S32, S32> mResutCodes; + + std::mutex mStatsMutex; }; diff --git a/indra/llcorehttp/lljsonrpcws.cpp b/indra/llcorehttp/lljsonrpcws.cpp index 3a0d3d1f26..5b704e6322 100644 --- a/indra/llcorehttp/lljsonrpcws.cpp +++ b/indra/llcorehttp/lljsonrpcws.cpp @@ -207,16 +207,17 @@ void LLJSONRPCConnection::processRequest(const LLSD& request) // Async handler — launched as a coroutine, response sent by the lambda. if (is_notification) { - LL_WARNS("JSONRPC") << "Async method " << method - << " called as notification; ignoring" << LL_ENDL; - return; + LL_WARNS("JSONRPC") << "Method " << method + << " called as notification; rejecting request" << LL_ENDL; + throw InvalidRequest("Method " + method + " cannot be called as a notification"); } ptr_t conn = std::static_pointer_cast<LLJSONRPCConnection>(getSelfPtr()); if (!conn) { - LL_WARNS("JSONRPC") << "Connection expired before async method " << method - << " could be launched" << LL_ENDL; - return; + LL_WARNS("JSONRPC") << "Connection expired before method " << method + << " could be launched; failing request" << LL_ENDL; + throw InternalError("Connection expired before method " + method + + " could be launched"); } LLMainThreadTask::dispatch( [handler, method, id, params, conn]() @@ -457,6 +458,24 @@ void LLJSONRPCConnection::sweepTimeouts() } } +void LLJSONRPCConnection::testInjectPendingRequest(const std::string& id, F64 deadline, ResponseCallback callback) +{ + LLMutexLock lock(&mMutex); + mPendingRequests[id] = std::move(callback); + mPendingDeadlines.push({ deadline, id }); +} + +void LLJSONRPCConnection::testSweepTimeouts() +{ + sweepTimeouts(); +} + +size_t LLJSONRPCConnection::testPendingRequestCount() const +{ + LLMutexLock lock(&mMutex); + return mPendingRequests.size(); +} + LLSD LLJSONRPCConnection::generateId() { // Server-wide atomic counter for efficient unique ID generation. @@ -497,6 +516,24 @@ void LLJSONRPCConnection::unregisterMethod(const std::string& method) LL_DEBUGS("JSONRPC") << "Unregistered method: " << method << LL_ENDL; } +std::set<std::string> LLJSONRPCConnection::getMethods() const +{ + LLMutexLock lock(&mMutex); + std::set<std::string> methods; + + for (const auto& [method, handler] : mMethodHandlers) + { + methods.insert(method); + } + + for (const auto& [method, handler] : mAsyncMethodHandlers) + { + methods.insert(method); + } + + return methods; +} + LLSD LLJSONRPCConnection::makeEnvelope(const LLSD& id, const std::string& method, const LLSD& params, @@ -651,23 +688,11 @@ LLJSONRPCServer::LLJSONRPCServer(const std::string& name, U16 port, bool local_o << " on port " << port << LL_ENDL; // Register standard JSON-RPC methods - registerGlobalMethod("system.listMethods", [this](const std::string& method, const LLSD& id, const LLSD& params) -> LLSD { - LL_DEBUGS("JSONRPC") << "System method " << method << " called" << LL_ENDL; - return getMethodList(); - }); - registerGlobalMethod("system.getStats", [this](const std::string& method, const LLSD& id, const LLSD& params) -> LLSD { LL_DEBUGS("JSONRPC") << "System method " << method << " called" << LL_ENDL; return getServerStats(); }); - registerGlobalMethod("system.ping", [](const std::string& method, const LLSD& id, const LLSD& params) -> LLSD { - LL_DEBUGS("JSONRPC") << "System method " << method << " called" << LL_ENDL; - LLSD result; - result["pong"] = LLDate::now().asString(); - result["params"] = params; - return result; - }); } LLWebsocketMgr::WSConnection::ptr_t LLJSONRPCServer::connectionFactory(LLWebsocketMgr::WSServer::ptr_t server, @@ -700,21 +725,80 @@ void LLJSONRPCServer::setupConnectionMethods(LLJSONRPCConnection::ptr_t connecti connection->registerMethod(method, handler); } - // Register session.ping handler for connection health monitoring - connection->registerMethod("session.ping", - [](const std::string&, const LLSD&, const LLSD& params) -> LLSD + std::weak_ptr<LLJSONRPCConnection> weak_connection = connection; + connection->registerMethod( + "system.listMethods", + [weak_connection]( + const std::string&, + const LLSD&, + const LLSD&) -> LLSD { - LLSD result; - // Echo back the original timestamp - if (params.has("timestamp")) + LLSD methods(LLSD::emptyArray()); + auto connection = weak_connection.lock(); + if (!connection) + { + return methods; + } + + for (const std::string& method : connection->getMethods()) { - result["timestamp"] = params["timestamp"]; + methods.append(method); } - // Add server's current time in milliseconds - result["server_time"] = static_cast<LLSD::Integer>( - LLDate::now().secondsSinceEpoch() * 1000.0); - return result; + + return methods; + }); + + connection->registerMethod( + "system.ping", + [this, weak_connection]( + const std::string& method, + const LLSD& id, + const LLSD& params) -> LLSD + { + LL_DEBUGS("JSONRPC") << "System method " << method + << " called" << LL_ENDL; + return handlePing(weak_connection.lock(), params); }); + + connection->registerMethod( + "system.getVersion", + [this, weak_connection]( + const std::string& method, + const LLSD& id, + const LLSD& params) -> LLSD + { + LL_DEBUGS("JSONRPC") << "System method " << method + << " called" << LL_ENDL; + return handleGetVersion(weak_connection.lock(), params); + }); + + connection->registerMethod( + "system.status", + [this, weak_connection]( + const std::string& method, + const LLSD& id, + const LLSD& params) -> LLSD + { + LL_DEBUGS("JSONRPC") << "System method " << method + << " called" << LL_ENDL; + return handleStatus(weak_connection.lock(), params); + }); +} + +LLSD LLJSONRPCServer::handlePing( + const LLJSONRPCConnection::ptr_t& connection, + const LLSD& params) const +{ + return LLSD("pong"); +} + +LLSD LLJSONRPCServer::handleStatus( + const LLJSONRPCConnection::ptr_t& connection, + const LLSD& params) const +{ + LLSD result; + result["status"] = "OK"; + return result; } void LLJSONRPCServer::registerGlobalMethod(const std::string& method, MethodHandler handler) diff --git a/indra/llcorehttp/lljsonrpcws.h b/indra/llcorehttp/lljsonrpcws.h index cd71473a47..aa4a96be4d 100644 --- a/indra/llcorehttp/lljsonrpcws.h +++ b/indra/llcorehttp/lljsonrpcws.h @@ -275,6 +275,12 @@ public: void unregisterMethod(const std::string& method); /** + * @brief Get the names of all registered methods + * @return Ordered set containing sync and async method names + */ + virtual std::set<std::string> getMethods() const; + + /** * @brief Make an asynchronous JSON-RPC call * @param method The method name to call * @param params The parameters to pass @@ -401,6 +407,11 @@ private: /// Invoked by the sweep timer; fires the timeout callback for any /// request whose deadline has passed. Safe to call from the main thread. void sweepTimeouts(); + +public: + void testInjectPendingRequest(const std::string& id, F64 deadline, ResponseCallback callback); + void testSweepTimeouts(); + size_t testPendingRequestCount() const; }; /** @@ -490,6 +501,13 @@ protected: LLWebsocketMgr::WSConnection::ptr_t connectionFactory(LLWebsocketMgr::WSServer::ptr_t server, LLWebsocketMgr::connection_h handle) override; + virtual LLSD handlePing(const LLJSONRPCConnection::ptr_t& connection, + const LLSD& params) const; + virtual LLSD handleGetVersion(const LLJSONRPCConnection::ptr_t& connection, + const LLSD& params) const = 0; + virtual LLSD handleStatus(const LLJSONRPCConnection::ptr_t& connection, + const LLSD& params) const; + /** * @brief Apply global method handlers to a new connection * @param connection The connection to configure diff --git a/indra/llcorehttp/tests/llcorehttp_test.cpp b/indra/llcorehttp/tests/llcorehttp_test.cpp index c7c50e6166..65d7fe93a9 100644 --- a/indra/llcorehttp/tests/llcorehttp_test.cpp +++ b/indra/llcorehttp/tests/llcorehttp_test.cpp @@ -44,6 +44,7 @@ #include "test_httprequest.hpp" #include "test_httpheaders.hpp" #include "test_httprequestqueue.hpp" +#include "test_jsonrpcws.hpp" #include "_httpservice.h" #include "llproxy.h" diff --git a/indra/llcorehttp/tests/test_jsonrpcws.hpp b/indra/llcorehttp/tests/test_jsonrpcws.hpp new file mode 100644 index 0000000000..6cf7932aa0 --- /dev/null +++ b/indra/llcorehttp/tests/test_jsonrpcws.hpp @@ -0,0 +1,146 @@ +/** + * @file test_jsonrpcws.hpp + * @brief unit tests for LLJSONRPCConnection helpers and dispatch behavior + */ + +#ifndef TEST_LLCORE_JSONRPCWS_H_ +#define TEST_LLCORE_JSONRPCWS_H_ + +#include "lljsonrpcws.h" +#include "lltimer.h" + +namespace +{ +class TestJSONRPCConnection : public LLJSONRPCConnection +{ +public: + TestJSONRPCConnection() + : LLJSONRPCConnection(LLWebsocketMgr::WSServer::ptr_t(), LLWebsocketMgr::connection_h()) + { + } + + using LLJSONRPCConnection::processMessage; + using LLJSONRPCConnection::validateMessage; + using LLJSONRPCConnection::testInjectPendingRequest; + using LLJSONRPCConnection::testPendingRequestCount; + using LLJSONRPCConnection::testSweepTimeouts; +}; +} + +namespace tut +{ + struct JSONRPCWSTestData + { + }; + + typedef test_group<JSONRPCWSTestData> JSONRPCWSTestGroupType; + typedef JSONRPCWSTestGroupType::object JSONRPCWSTestObjectType; + JSONRPCWSTestGroupType JSONRPCWSTestGroup("LLJSONRPCConnection Tests"); + + template<> template<> + void JSONRPCWSTestObjectType::test<1>() + { + set_test_name("makeEnvelope notification omits id"); + + LLSD params; + params["value"] = 42; + LLSD env = LLJSONRPCConnection::makeEnvelope(LLSD(), "runtime.debug", params, LLSD(), LLSD()); + + ensure("jsonrpc field should exist", env.has("jsonrpc")); + ensure_equals("jsonrpc version", env["jsonrpc"].asString(), "2.0"); + ensure("notification should omit id", !env.has("id")); + ensure_equals("method", env["method"].asString(), "runtime.debug"); + ensure_equals("param round trip", env["params"]["value"].asInteger(), 42); + } + + template<> template<> + void JSONRPCWSTestObjectType::test<2>() + { + set_test_name("makeEnvelope response keeps id slot"); + + LLSD env = LLJSONRPCConnection::makeEnvelope(LLSD(), std::string(), LLSD(), LLSD("ok"), LLSD()); + + ensure("response should include id", env.has("id")); + ensure("response should not include method", !env.has("method")); + ensure_equals("result", env["result"].asString(), "ok"); + } + + template<> template<> + void JSONRPCWSTestObjectType::test<3>() + { + set_test_name("validateMessage accepts valid request and rejects invalid params"); + + TestJSONRPCConnection conn; + + LLSD valid; + valid["jsonrpc"] = "2.0"; + valid["method"] = "session.ping"; + LLSD params = LLSD::emptyMap(); + params["timestamp"] = 123; + valid["params"] = params; + ensure("valid request should pass", conn.validateMessage(valid, true)); + + LLSD invalid = valid; + invalid["params"] = "not-an-array-or-object"; + ensure("invalid params type should fail", !conn.validateMessage(invalid, true)); + } + + template<> template<> + void JSONRPCWSTestObjectType::test<4>() + { + set_test_name("processMessage dispatches notification handler"); + + TestJSONRPCConnection conn; + + bool called = false; + LLSD seen_id; + LLSD seen_params; + conn.registerMethod("runtime.debug", + [&](const std::string& method, const LLSD& id, const LLSD& params) -> LLSD + { + called = true; + ensure_equals("method propagated", method, "runtime.debug"); + seen_id = id; + seen_params = params; + return LLSD(); + }); + + LLSD params; + params["message"] = "hello"; + LLSD notification = LLJSONRPCConnection::makeEnvelope(LLSD(), "runtime.debug", params, LLSD(), LLSD()); + conn.processMessage(notification); + + ensure("handler should be called", called); + ensure("notification id should be undefined", seen_id.isUndefined()); + ensure_equals("payload should be forwarded", seen_params["message"].asString(), "hello"); + } + + template<> template<> + void JSONRPCWSTestObjectType::test<5>() + { + set_test_name("sweepTimeouts expires overdue callbacks"); + + TestJSONRPCConnection conn; + + bool callback_called = false; + conn.testInjectPendingRequest( + "req_1", + LLTimer::getTotalSeconds() - 1.0, + [&](const LLSD& result, const LLSD& error) + { + callback_called = true; + ensure("timed out result should be undefined", result.isUndefined()); + ensure_equals( + "timeout code", + error["code"].asInteger(), + LLJSONRPCConnection::RPCError::REQUEST_TIMEOUT); + }); + + ensure_equals("pending request should be tracked", conn.testPendingRequestCount(), (size_t)1); + conn.testSweepTimeouts(); + ensure("timeout callback should be called", callback_called); + ensure_equals("pending request should be removed", conn.testPendingRequestCount(), (size_t)0); + } +} + +#endif
\ No newline at end of file |
