summaryrefslogtreecommitdiff
path: root/indra/viewer_components
diff options
context:
space:
mode:
Diffstat (limited to 'indra/viewer_components')
-rw-r--r--indra/viewer_components/login/CMakeLists.txt4
-rw-r--r--indra/viewer_components/login/lllogin.cpp284
-rw-r--r--indra/viewer_components/login/tests/lllogin_test.cpp152
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");
-
- }
}