diff options
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"); - -	}  } | 
