diff options
author | Andrey Kleshchev <andreykproductengine@lindenlab.com> | 2020-08-11 00:37:14 +0300 |
---|---|---|
committer | Andrey Kleshchev <andreykproductengine@lindenlab.com> | 2020-08-11 00:37:14 +0300 |
commit | b540e9f401c4e63ed6488e49a828829f8b5bfb01 (patch) | |
tree | f8ed027616215a58e80b910a0cc0ede1ddd91350 /indra/viewer_components | |
parent | 703cbef8ab07db9fe65a39c577377a3e40f63728 (diff) | |
parent | 89cde15fb8c52071805af78e61848e743f2ab2f1 (diff) |
Merged master into DRTVWR-514-keymappings
Diffstat (limited to 'indra/viewer_components')
-rw-r--r-- | indra/viewer_components/login/CMakeLists.txt | 4 | ||||
-rw-r--r-- | indra/viewer_components/login/lllogin.cpp | 284 | ||||
-rw-r--r-- | indra/viewer_components/login/tests/lllogin_test.cpp | 152 |
3 files changed, 236 insertions, 204 deletions
diff --git a/indra/viewer_components/login/CMakeLists.txt b/indra/viewer_components/login/CMakeLists.txt index 3bedeb7292..23518b791c 100644 --- a/indra/viewer_components/login/CMakeLists.txt +++ b/indra/viewer_components/login/CMakeLists.txt @@ -50,7 +50,7 @@ target_link_libraries(lllogin ${LLMATH_LIBRARIES} ${LLXML_LIBRARIES} ${BOOST_THREAD_LIBRARY} - ${BOOST_COROUTINE_LIBRARY} + ${BOOST_FIBER_LIBRARY} ${BOOST_CONTEXT_LIBRARY} ${BOOST_SYSTEM_LIBRARY} ) @@ -62,7 +62,7 @@ if(LL_TESTS) set_source_files_properties( lllogin.cpp PROPERTIES - LL_TEST_ADDITIONAL_LIBRARIES "${LLMESSAGE_LIBRARIES};${LLCOREHTTP_LIBRARIES};${LLCOMMON_LIBRARIES};${BOOST_COROUTINE_LIBRARY};${BOOST_CONTEXT_LIBRARY};${BOOST_THREAD_LIBRARY};${BOOST_SYSTEM_LIBRARY}" + LL_TEST_ADDITIONAL_LIBRARIES "${LLMESSAGE_LIBRARIES};${LLCOREHTTP_LIBRARIES};${LLCOMMON_LIBRARIES};${BOOST_FIBER_LIBRARY};${BOOST_CONTEXT_LIBRARY};${BOOST_THREAD_LIBRARY};${BOOST_SYSTEM_LIBRARY}" ) LL_ADD_PROJECT_UNIT_TESTS(lllogin "${lllogin_TEST_SOURCE_FILES}") diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp index 9193d32b49..d485203fa1 100644 --- a/indra/viewer_components/login/lllogin.cpp +++ b/indra/viewer_components/login/lllogin.cpp @@ -23,6 +23,7 @@ * $/LicenseInfo$ */ +#include "llwin32headers.h" #include "linden_common.h" #include "llsd.h" #include "llsdutil.h" @@ -147,167 +148,170 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params) } try { - LL_DEBUGS("LLLogin") << "Entering coroutine " << LLCoros::instance().getName() - << " with uri '" << uri << "', parameters " << printable_params << LL_ENDL; + LL_DEBUGS("LLLogin") << "Entering coroutine " << LLCoros::getName() + << " with uri '" << uri << "', parameters " << printable_params << LL_ENDL; - LLEventPump& xmlrpcPump(LLEventPumps::instance().obtain("LLXMLRPCTransaction")); - // EXT-4193: use a DIFFERENT reply pump than for the SRV request. We used - // to share them -- but the EXT-3934 fix made it possible for an abandoned - // SRV response to arrive just as we were expecting the XMLRPC response. - LLEventStream loginReplyPump("loginreply", true); + LLEventPump& xmlrpcPump(LLEventPumps::instance().obtain("LLXMLRPCTransaction")); + // EXT-4193: use a DIFFERENT reply pump than for the SRV request. We used + // to share them -- but the EXT-3934 fix made it possible for an abandoned + // SRV response to arrive just as we were expecting the XMLRPC response. + LLEventStream loginReplyPump("loginreply", true); - LLSD::Integer attempts = 0; + LLSD::Integer attempts = 0; - LLSD request(login_params); - request["reply"] = loginReplyPump.getName(); - request["uri"] = uri; - std::string status; + LLSD request(login_params); + request["reply"] = loginReplyPump.getName(); + request["uri"] = uri; + std::string status; - // Loop back to here if login attempt redirects to a different - // request["uri"] - for (;;) - { - ++attempts; - LLSD progress_data; - progress_data["attempt"] = attempts; - progress_data["request"] = request; - if (progress_data["request"].has("params") - && progress_data["request"]["params"].has("passwd")) - { - progress_data["request"]["params"]["passwd"] = "*******"; - } - sendProgressEvent("offline", "authenticating", progress_data); - - // We expect zero or more "Downloading" status events, followed by - // exactly one event with some other status. Use postAndSuspend() the - // first time, because -- at least in unit-test land -- it's - // possible for the reply to arrive before the post() call - // returns. Subsequent responses, of course, must be awaited - // without posting again. - for (mAuthResponse = validateResponse(loginReplyPump.getName(), - llcoro::postAndSuspend(request, xmlrpcPump, loginReplyPump, "reply")); - mAuthResponse["status"].asString() == "Downloading"; - mAuthResponse = validateResponse(loginReplyPump.getName(), - llcoro::suspendUntilEventOn(loginReplyPump))) + // Loop back to here if login attempt redirects to a different + // request["uri"] + for (;;) { - // Still Downloading -- send progress update. - sendProgressEvent("offline", "downloading"); - } + ++attempts; + LLSD progress_data; + progress_data["attempt"] = attempts; + progress_data["request"] = request; + if (progress_data["request"].has("params") + && progress_data["request"]["params"].has("passwd")) + { + progress_data["request"]["params"]["passwd"] = "*******"; + } + sendProgressEvent("offline", "authenticating", progress_data); + + // We expect zero or more "Downloading" status events, followed by + // exactly one event with some other status. Use postAndSuspend() the + // first time, because -- at least in unit-test land -- it's + // possible for the reply to arrive before the post() call + // returns. Subsequent responses, of course, must be awaited + // without posting again. + for (mAuthResponse = validateResponse(loginReplyPump.getName(), + llcoro::postAndSuspend(request, xmlrpcPump, loginReplyPump, "reply")); + mAuthResponse["status"].asString() == "Downloading"; + mAuthResponse = validateResponse(loginReplyPump.getName(), + llcoro::suspendUntilEventOn(loginReplyPump))) + { + // Still Downloading -- send progress update. + sendProgressEvent("offline", "downloading"); + } - LL_DEBUGS("LLLogin") << "Auth Response: " << mAuthResponse << LL_ENDL; - status = mAuthResponse["status"].asString(); + LL_DEBUGS("LLLogin") << "Auth Response: " << mAuthResponse << LL_ENDL; + status = mAuthResponse["status"].asString(); - // Okay, we've received our final status event for this - // request. Unless we got a redirect response, break the retry - // loop for the current rewrittenURIs entry. - if (!(status == "Complete" && - mAuthResponse["responses"]["login"].asString() == "indeterminate")) - { - break; - } + // Okay, we've received our final status event for this + // request. Unless we got a redirect response, break the retry + // loop for the current rewrittenURIs entry. + if (!(status == "Complete" && + mAuthResponse["responses"]["login"].asString() == "indeterminate")) + { + break; + } - sendProgressEvent("offline", "indeterminate", mAuthResponse["responses"]); + sendProgressEvent("offline", "indeterminate", mAuthResponse["responses"]); - // Here the login service at the current URI is redirecting us - // to some other URI ("indeterminate" -- why not "redirect"?). - // The response should contain another uri to try, with its - // own auth method. - request["uri"] = mAuthResponse["responses"]["next_url"].asString(); - request["method"] = mAuthResponse["responses"]["next_method"].asString(); - } // loop back to try the redirected URI + // Here the login service at the current URI is redirecting us + // to some other URI ("indeterminate" -- why not "redirect"?). + // The response should contain another uri to try, with its + // own auth method. + request["uri"] = mAuthResponse["responses"]["next_url"].asString(); + request["method"] = mAuthResponse["responses"]["next_method"].asString(); + } // loop back to try the redirected URI - // Here we're done with redirects. - if (status == "Complete") - { - // StatusComplete does not imply auth success. Check the - // actual outcome of the request. We've already handled the - // "indeterminate" case in the loop above. - if (mAuthResponse["responses"]["login"].asString() == "true") - { - sendProgressEvent("online", "connect", mAuthResponse["responses"]); - } - else + // Here we're done with redirects. + if (status == "Complete") { - // Synchronize here with the updater. We synchronize here rather - // than in the fail.login handler, which actually examines the - // response from login.cgi, because here we are definitely in a - // coroutine and can definitely use suspendUntilBlah(). Whoever's - // listening for fail.login might not be. - - // If the reason for login failure is that we must install a - // required update, we definitely want to pass control to the - // updater to manage that for us. We'll handle any other login - // failure ourselves, as usual. We figure that no matter where you - // are in the world, or what kind of network you're on, we can - // reasonably expect the Viewer Version Manager to respond more or - // less as quickly as login.cgi. This synchronization is only - // intended to smooth out minor races between the two services. - // But what if the updater crashes? Use a timeout so that - // eventually we'll tire of waiting for it and carry on as usual. - // Given the above, it can be a fairly short timeout, at least - // from a human point of view. - - // Since sSyncPoint is an LLEventMailDrop, we DEFINITELY want to - // consume the posted event. - LLCoros::OverrideConsuming oc(true); - // Timeout should produce the isUndefined() object passed here. - LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL; - LLSD updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD()); - if (updater.isUndefined()) + // StatusComplete does not imply auth success. Check the + // actual outcome of the request. We've already handled the + // "indeterminate" case in the loop above. + if (mAuthResponse["responses"]["login"].asString() == "true") { - LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login" - << LL_ENDL; + sendProgressEvent("online", "connect", mAuthResponse["responses"]); } else { - LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL; + // Synchronize here with the updater. We synchronize here rather + // than in the fail.login handler, which actually examines the + // response from login.cgi, because here we are definitely in a + // coroutine and can definitely use suspendUntilBlah(). Whoever's + // listening for fail.login might not be. + + // If the reason for login failure is that we must install a + // required update, we definitely want to pass control to the + // updater to manage that for us. We'll handle any other login + // failure ourselves, as usual. We figure that no matter where you + // are in the world, or what kind of network you're on, we can + // reasonably expect the Viewer Version Manager to respond more or + // less as quickly as login.cgi. This synchronization is only + // intended to smooth out minor races between the two services. + // But what if the updater crashes? Use a timeout so that + // eventually we'll tire of waiting for it and carry on as usual. + // Given the above, it can be a fairly short timeout, at least + // from a human point of view. + + // Since sSyncPoint is an LLEventMailDrop, we DEFINITELY want to + // consume the posted event. + LLCoros::OverrideConsuming oc(true); + // Timeout should produce the isUndefined() object passed here. + LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL; + LLSD updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD()); + if (updater.isUndefined()) + { + LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login" + << LL_ENDL; + } + else + { + LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL; + } + // Let the fail.login handler deal with empty updater response. + LLSD responses(mAuthResponse["responses"]); + responses["updater"] = updater; + sendProgressEvent("offline", "fail.login", responses); } - // Let the fail.login handler deal with empty updater response. - LLSD responses(mAuthResponse["responses"]); - responses["updater"] = updater; - sendProgressEvent("offline", "fail.login", responses); + return; // Done! } - return; // Done! - } -// /* Sometimes we end with "Started" here. Slightly slow server? -// * Seems to be ok to just skip it. Otherwise we'd error out and crash in the if below. -// */ -// if( status == "Started") -// { -// LL_DEBUGS("LLLogin") << mAuthResponse << LL_ENDL; -// continue; -// } - - // If we don't recognize status at all, trouble - if (! (status == "CURLError" - || status == "XMLRPCError" - || status == "OtherError")) - { - LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: " - << mAuthResponse << LL_ENDL; - return; - } +/*==========================================================================*| + // Sometimes we end with "Started" here. Slightly slow server? Seems + // to be ok to just skip it. Otherwise we'd error out and crash in the + // if below. + if( status == "Started") + { + LL_DEBUGS("LLLogin") << mAuthResponse << LL_ENDL; + continue; + } +|*==========================================================================*/ - // Here status IS one of the errors tested above. - // Tell caller this didn't work out so well. - - // *NOTE: The response from LLXMLRPCListener's Poller::poll method returns an - // llsd with no "responses" node. To make the output from an incomplete login symmetrical - // to success, add a data/message and data/reason fields. - LLSD error_response(LLSDMap - ("reason", mAuthResponse["status"]) - ("errorcode", mAuthResponse["errorcode"]) - ("message", mAuthResponse["error"])); - if(mAuthResponse.has("certificate")) - { - error_response["certificate"] = mAuthResponse["certificate"]; - } - sendProgressEvent("offline", "fail.login", error_response); + // If we don't recognize status at all, trouble + if (! (status == "CURLError" + || status == "XMLRPCError" + || status == "OtherError")) + { + LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: " + << mAuthResponse << LL_ENDL; + return; + } + + // Here status IS one of the errors tested above. + // Tell caller this didn't work out so well. + + // *NOTE: The response from LLXMLRPCListener's Poller::poll method returns an + // llsd with no "responses" node. To make the output from an incomplete login symmetrical + // to success, add a data/message and data/reason fields. + LLSD error_response(LLSDMap + ("reason", mAuthResponse["status"]) + ("errorcode", mAuthResponse["errorcode"]) + ("message", mAuthResponse["error"])); + if(mAuthResponse.has("certificate")) + { + error_response["certificate"] = mAuthResponse["certificate"]; + } + sendProgressEvent("offline", "fail.login", error_response); } catch (...) { - CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::instance().getName() - << "('" << uri << "', " << printable_params << ")")); + LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::getName() + << "('" << uri << "', " << printable_params << ")")); + throw; } } diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index e96c495446..f9267533ff 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -36,14 +36,16 @@ #include "../lllogin.h" // STL headers // std headers +#include <chrono> #include <iostream> // external library headers // other Linden headers -#include "llsd.h" -#include "../../../test/lltut.h" -//#define DEBUG_ON #include "../../../test/debug.h" +#include "../../../test/lltestapp.h" +#include "../../../test/lltut.h" #include "llevents.h" +#include "lleventcoro.h" +#include "llsd.h" #include "stringize.h" #if LL_WINDOWS @@ -66,29 +68,68 @@ // This is a listener to receive results from lllogin. class LoginListener: public LLEventTrackable { - std::string mName; - LLSD mLastEvent; + std::string mName; + LLSD mLastEvent; + size_t mCalls{ 0 }; Debug mDebug; public: - LoginListener(const std::string& name) : - mName(name), + LoginListener(const std::string& name) : + mName(name), mDebug(stringize(*this)) - {} + {} - bool call(const LLSD& event) - { - mDebug(STRINGIZE("LoginListener called!: " << event)); - - mLastEvent = event; - return false; - } + bool call(const LLSD& event) + { + mDebug(STRINGIZE("LoginListener called!: " << event)); + + mLastEvent = event; + ++mCalls; + return false; + } LLBoundListener listenTo(LLEventPump& pump) { return pump.listen(mName, boost::bind(&LoginListener::call, this, _1)); - } + } + + LLSD lastEvent() const { return mLastEvent; } - LLSD lastEvent() const { return mLastEvent; } + size_t getCalls() const { return mCalls; } + + // wait for arbitrary predicate to become true + template <typename PRED> + LLSD waitFor(const std::string& desc, PRED&& pred, double seconds=2.0) const + { + // remember when we started waiting + auto start = std::chrono::system_clock::now(); + // Break loop when the passed predicate returns true + while (! std::forward<PRED>(pred)()) + { + // but if we've been spinning here too long, test failed + // how long have we been here, anyway? + auto now = std::chrono::system_clock::now(); + // the default ratio for duration is seconds + std::chrono::duration<double> elapsed = (now - start); + if (elapsed.count() > seconds) + { + tut::fail(STRINGIZE("LoginListener::waitFor() took more than " + << seconds << " seconds waiting for " << desc)); + } + // haven't yet received the new call, nor have we timed out -- + // just wait + llcoro::suspend(); + } + // oh good, we've gotten at least one new call! Return its event. + return lastEvent(); + } + + // wait for any call() calls beyond prevcalls + LLSD waitFor(size_t prevcalls, double seconds) const + { + return waitFor(STRINGIZE("more than " << prevcalls << " calls"), + [this, prevcalls]()->bool{ return getCalls() > prevcalls; }, + seconds); + } friend std::ostream& operator<<(std::ostream& out, const LoginListener& listener) { @@ -163,11 +204,16 @@ namespace tut { struct llviewerlogin_data { - llviewerlogin_data() : + llviewerlogin_data() : pumps(LLEventPumps::instance()) - {} - LLEventPumps& pumps; - }; + {} + ~llviewerlogin_data() + { + pumps.clear(); + } + LLEventPumps& pumps; + LLTestApp testApp; + }; typedef test_group<llviewerlogin_data> llviewerlogin_group; typedef llviewerlogin_group::object llviewerlogin_object; @@ -186,12 +232,12 @@ namespace tut // Have dummy XMLRPC respond immediately. LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc", respond_immediately); - dummyXMLRPC.listenTo(xmlrpcPump); + LLTempBoundListener conn1 = dummyXMLRPC.listenTo(xmlrpcPump); LLLogin login; LoginListener listener("test_ear"); - listener.listenTo(login.getEventPump()); + LLTempBoundListener conn2 = listener.listenTo(login.getEventPump()); LLSD credentials; credentials["first"] = "foo"; @@ -199,8 +245,9 @@ namespace tut credentials["passwd"] = "secret"; login.connect("login.bar.com", credentials); - - ensure_equals("Online state", listener.lastEvent()["state"].asString(), "online"); + listener.waitFor( + "online state", + [&listener]()->bool{ return listener.lastEvent()["state"].asString() == "online"; }); } template<> template<> @@ -214,11 +261,11 @@ namespace tut LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); - dummyXMLRPC.listenTo(xmlrpcPump); + LLTempBoundListener conn1 = dummyXMLRPC.listenTo(xmlrpcPump); LLLogin login; LoginListener listener("test_ear"); - listener.listenTo(login.getEventPump()); + LLTempBoundListener conn2 = listener.listenTo(login.getEventPump()); LLSD credentials; credentials["first"] = "who"; @@ -226,9 +273,12 @@ namespace tut credentials["passwd"] = "badpasswd"; login.connect("login.bar.com", credentials); + llcoro::suspend(); ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); + auto prev = listener.getCalls(); + // Send the failed auth request reponse LLSD data; data["status"] = "Complete"; @@ -238,6 +288,10 @@ namespace tut data["responses"]["login"] = "false"; dummyXMLRPC.setResponse(data); dummyXMLRPC.sendReply(); + // we happen to know LLLogin uses a 10-second timeout to try to sync + // with SLVersionChecker -- allow at least that much time before + // giving up + listener.waitFor(prev, 11.0); ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); } @@ -253,11 +307,11 @@ namespace tut LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); - dummyXMLRPC.listenTo(xmlrpcPump); + LLTempBoundListener conn1 = dummyXMLRPC.listenTo(xmlrpcPump); LLLogin login; LoginListener listener("test_ear"); - listener.listenTo(login.getEventPump()); + LLTempBoundListener conn2 = listener.listenTo(login.getEventPump()); LLSD credentials; credentials["first"] = "these"; @@ -265,9 +319,12 @@ namespace tut credentials["passwd"] = "matter"; login.connect("login.bar.com", credentials); + llcoro::suspend(); ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); + auto prev = listener.getCalls(); + // Send the failed auth request reponse LLSD data; data["status"] = "OtherError"; @@ -276,40 +333,11 @@ namespace tut data["transfer_rate"] = 0; dummyXMLRPC.setResponse(data); dummyXMLRPC.sendReply(); + // we happen to know LLLogin uses a 10-second timeout to try to sync + // with SLVersionChecker -- allow at least that much time before + // giving up + listener.waitFor(prev, 11.0); ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); } - - template<> template<> - void llviewerlogin_object::test<4>() - { - DEBUG; - // Test SRV request timeout. - set_test_name("LLLogin SRV timeout testing"); - - // Testing normal login procedure. - - LLLogin login; - LoginListener listener("test_ear"); - listener.listenTo(login.getEventPump()); - - LLSD credentials; - credentials["first"] = "these"; - credentials["last"] = "don't"; - credentials["passwd"] = "matter"; - credentials["cfg_srv_timeout"] = 0.0f; - - login.connect("login.bar.com", credentials); - - // Get the mainloop eventpump, which needs a pinging in order to drive the - // SRV timeout. - LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); - LLSD frame_event; - mainloop.post(frame_event); - - ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); - ensure_equals("Attempt", listener.lastEvent()["data"]["attempt"].asInteger(), 1); - ensure_equals("URI", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.bar.com"); - - } } |