From 66981fab0b3c8dcc3a031d50710a2b24ec6b0603 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 10 May 2018 21:46:07 -0400 Subject: SL-793: Use Boost.Fiber instead of the "dcoroutine" library. Longtime fans will remember that the "dcoroutine" library is a Google Summer of Code project by Giovanni P. Deretta. He originally called it "Boost.Coroutine," and we originally added it to our 3p-boost autobuild package as such. But when the official Boost.Coroutine library came along (with a very different API), and we still needed the API of the GSoC project, we renamed the unofficial one "dcoroutine" to allow coexistence. The "dcoroutine" library had an internal low-level API more or less analogous to Boost.Context. We later introduced an implementation of that internal API based on Boost.Context, a step towards eliminating the GSoC code in favor of official, supported Boost code. However, recent versions of Boost.Context no longer support the API on which we built the shim for "dcoroutine." We started down the path of reimplementing that shim using the current Boost.Context API -- then realized that it's time to bite the bullet and replace the "dcoroutine" API with the Boost.Fiber API, which we've been itching to do for literally years now. Naturally, most of the heavy lifting is in llcoros.{h,cpp} and lleventcoro.{h,cpp} -- which is good: the LLCoros layer abstracts away most of the differences between "dcoroutine" and Boost.Fiber. The one feature Boost.Fiber does not provide is the ability to forcibly terminate some other fiber. Accordingly, disable LLCoros::kill() and LLCoprocedureManager::shutdown(). The only known shutdown() call was in LLCoprocedurePool's destructor. We also took the opportunity to remove postAndSuspend2() and its associated machinery: FutureListener2, LLErrorEvent, errorException(), errorLog(), LLCoroEventPumps. All that dual-LLEventPump stuff was introduced at a time when the Responder pattern was king, and we assumed we'd want to listen on one LLEventPump with the success handler and on another with the error handler. We have never actually used that in practice. Remove associated tests, of course. There is one other semantic difference that necessitates patching a number of tests: with "dcoroutine," fulfilling a future IMMEDIATELY resumes the waiting coroutine. With Boost.Fiber, fulfilling a future merely marks the fiber as ready to resume next time the scheduler gets around to it. To observe the test side effects, we've inserted a number of llcoro::suspend() calls -- also in the main loop. For a long time we retained a single unit test exercising the raw "dcoroutine" API. Remove that. Eliminate llcoro_get_id.{h,cpp}, which provided llcoro::get_id(), which was a hack to emulate fiber-local variables. Since Boost.Fiber has an actual API for that, remove the hack. In fact, use (new alias) LLCoros::local_ptr for LLSingleton's dependency tracking in place of llcoro::get_id(). In CMake land, replace BOOST_COROUTINE_LIBRARY with BOOST_FIBER_LIBRARY. We don't actually use the Boost.Coroutine for anything (though there exist plausible use cases). --- indra/viewer_components/login/tests/lllogin_test.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indra/viewer_components/login/tests/lllogin_test.cpp') diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index e96c495446..774823d735 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -44,6 +44,7 @@ //#define DEBUG_ON #include "../../../test/debug.h" #include "llevents.h" +#include "lleventcoro.h" #include "stringize.h" #if LL_WINDOWS @@ -199,6 +200,7 @@ namespace tut credentials["passwd"] = "secret"; login.connect("login.bar.com", credentials); + llcoro::suspend(); ensure_equals("Online state", listener.lastEvent()["state"].asString(), "online"); } @@ -226,6 +228,7 @@ namespace tut credentials["passwd"] = "badpasswd"; login.connect("login.bar.com", credentials); + llcoro::suspend(); ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); @@ -265,6 +268,7 @@ namespace tut credentials["passwd"] = "matter"; login.connect("login.bar.com", credentials); + llcoro::suspend(); ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); @@ -300,6 +304,7 @@ namespace tut credentials["cfg_srv_timeout"] = 0.0f; login.connect("login.bar.com", credentials); + llcoro::suspend(); // Get the mainloop eventpump, which needs a pinging in order to drive the // SRV timeout. -- cgit v1.2.3 From 101ab28f0c317e8c8489f0189f81b7b4e2381e9a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 29 Dec 2018 10:27:16 -0500 Subject: SL-793: Fix lllogin_test.cpp for new LLCoros implementation. Delete the test for SRV timeout: lllogin no longer issues an SRV query. That test only confuses the test program without exercising any useful paths in production code. As with other tests dating from the previous LLCoros implementation, we need a few llcoro::suspend() calls sprinkled in so that a fiber marked ready -- by fulfilling the future for which it is waiting -- gets a chance to run. Clear LLEventPumps between test functions. --- .../viewer_components/login/tests/lllogin_test.cpp | 48 +++++----------------- 1 file changed, 10 insertions(+), 38 deletions(-) (limited to 'indra/viewer_components/login/tests/lllogin_test.cpp') diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index 774823d735..f8e48e3824 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -164,11 +164,15 @@ namespace tut { struct llviewerlogin_data { - llviewerlogin_data() : + llviewerlogin_data() : pumps(LLEventPumps::instance()) - {} - LLEventPumps& pumps; - }; + {} + ~llviewerlogin_data() + { + pumps.clear(); + } + LLEventPumps& pumps; + }; typedef test_group llviewerlogin_group; typedef llviewerlogin_group::object llviewerlogin_object; @@ -241,6 +245,7 @@ namespace tut data["responses"]["login"] = "false"; dummyXMLRPC.setResponse(data); dummyXMLRPC.sendReply(); + llcoro::suspend(); ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); } @@ -280,41 +285,8 @@ namespace tut data["transfer_rate"] = 0; dummyXMLRPC.setResponse(data); dummyXMLRPC.sendReply(); - - 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); llcoro::suspend(); - // 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"); - + ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); } } -- cgit v1.2.3 From a9191a83d9865c129cc483c33a8a338dc00f5cc9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 6 Jun 2019 21:05:02 -0400 Subject: SL-1968: Extend lllogin_test login-failed cases for new sync timing. On login failure, LLLogin now tries to sync up with SLVersionChecker. It waits for up to 10 seconds before shrugging and giving up. Since that coroutine can now block for that long, make the llogin_test failure cases wait at least that long too. --- .../viewer_components/login/tests/lllogin_test.cpp | 83 ++++++++++++++++------ 1 file changed, 61 insertions(+), 22 deletions(-) (limited to 'indra/viewer_components/login/tests/lllogin_test.cpp') diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index f8e48e3824..23db8d0fe3 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -37,6 +37,7 @@ // STL headers // std headers #include +#include // external library headers // other Linden headers #include "llsd.h" @@ -67,29 +68,57 @@ // 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; } + + LLSD waitFor(size_t prevcalls, F32 seconds) const + { + // remember when we started waiting + auto start = std::chrono::system_clock::now(); + // Break loop when the LLEventPump on which we're listening calls call() + while (getCalls() <= prevcalls) + { + // 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 elapsed = (now - start); + if (elapsed.count() > seconds) + { + tut::fail("LoginListener::waitFor() timed out"); + } + // 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(); + } friend std::ostream& operator<<(std::ostream& out, const LoginListener& listener) { @@ -191,12 +220,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"; @@ -220,11 +249,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"; @@ -236,6 +265,8 @@ namespace tut ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); + auto prev = listener.getCalls(); + // Send the failed auth request reponse LLSD data; data["status"] = "Complete"; @@ -245,7 +276,10 @@ namespace tut data["responses"]["login"] = "false"; dummyXMLRPC.setResponse(data); dummyXMLRPC.sendReply(); - llcoro::suspend(); + // 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"); } @@ -261,11 +295,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"; @@ -277,6 +311,8 @@ namespace tut ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); + auto prev = listener.getCalls(); + // Send the failed auth request reponse LLSD data; data["status"] = "OtherError"; @@ -285,7 +321,10 @@ namespace tut data["transfer_rate"] = 0; dummyXMLRPC.setResponse(data); dummyXMLRPC.sendReply(); - llcoro::suspend(); + // 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"); } -- cgit v1.2.3 From 28a54c2f7b25b9fda763c51746701f0cd21c8018 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 22 Oct 2019 16:49:29 -0400 Subject: DRTVWR-476: Infrastructure to help manage long-lived coroutines. Introduce LLCoros::Stop exception, with subclasses Stopping, Stopped and Shutdown. Add LLCoros::checkStop(), intended to be called periodically by any coroutine with nontrivial lifespan. It checks the LLApp status and, unless isRunning(), throws one of these new exceptions. Make LLCoros::toplevel() catch Stop specially and log forcible coroutine termination. Now that LLApp status matters even in a test program, introduce a trivial LLTestApp subclass whose sole function is to make isRunning() true. (LLApp::setStatus() is protected: only a subclass can call it.) Add LLTestApp instances to lleventcoro_test.cpp and lllogin_test.cpp. Make LLCoros::toplevel() accept parameters by value rather than by const reference so we can continue using them even after context switches. Make private LLCoros::get_CoroData() static. Given that we've observed some coroutines living past LLCoros destruction, making the caller call LLCoros::instance() is more dangerous than encapsulating it within a static method -- since the encapsulated call can check LLCoros::wasDeleted() first and do something reasonable instead. This also eliminates the need for both a const and non-const overload. Defend LLCoros::delete_CoroData() (cleanup function for fiber_specific_ptr for CoroData, implicitly called after coroutine termination) against calls after ~LLCoros(). Add a status string to coroutine-local data, with LLCoro::setStatus(), getStatus() and RAII class TempStatus. Add an optional 'when' string argument to LLCoros::printActiveCoroutines(). Make ~LLCoros() print the coroutines still active at destruction. --- indra/viewer_components/login/tests/lllogin_test.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/viewer_components/login/tests/lllogin_test.cpp') diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index 23db8d0fe3..0255e10e53 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -42,6 +42,7 @@ // other Linden headers #include "llsd.h" #include "../../../test/lltut.h" +#include "../../../test/lltestapp.h" //#define DEBUG_ON #include "../../../test/debug.h" #include "llevents.h" @@ -201,6 +202,7 @@ namespace tut pumps.clear(); } LLEventPumps& pumps; + LLTestApp testApp; }; typedef test_group llviewerlogin_group; -- cgit v1.2.3 From dc07509f296661cf7a4d4bceb88a1a897757de98 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 3 Apr 2020 10:38:53 -0400 Subject: DRTVWR-476: Cherry-pick debug aids from commit 77b0c53 (fiber-mutex) --- indra/viewer_components/login/tests/lllogin_test.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'indra/viewer_components/login/tests/lllogin_test.cpp') diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index 0255e10e53..0d933a3d64 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -43,7 +43,6 @@ #include "llsd.h" #include "../../../test/lltut.h" #include "../../../test/lltestapp.h" -//#define DEBUG_ON #include "../../../test/debug.h" #include "llevents.h" #include "lleventcoro.h" -- cgit v1.2.3 From fde7dad001d588e972cd49f5c41c5014f86f646e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 2 Apr 2020 15:00:17 -0400 Subject: DRTVWR-476: Make LoginListener::waitFor() take arbitrary predicate. This allows one of the tests to specifically waitFor() the completion status update from LLLogin, rather than the next status update to come along: the coroutine potentially emits a whole sequence of status updates before completion. Then the waitFor() overload that merely waits for the next status update is implemented by passing that specific predicate to the other overload. --- .../viewer_components/login/tests/lllogin_test.cpp | 35 ++++++++++++++-------- 1 file changed, 23 insertions(+), 12 deletions(-) (limited to 'indra/viewer_components/login/tests/lllogin_test.cpp') diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index 0d933a3d64..f9267533ff 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -36,16 +36,16 @@ #include "../lllogin.h" // STL headers // std headers -#include #include +#include // external library headers // other Linden headers -#include "llsd.h" -#include "../../../test/lltut.h" -#include "../../../test/lltestapp.h" #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 @@ -96,21 +96,24 @@ public: size_t getCalls() const { return mCalls; } - LLSD waitFor(size_t prevcalls, F32 seconds) const + // wait for arbitrary predicate to become true + template + 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 LLEventPump on which we're listening calls call() - while (getCalls() <= prevcalls) + // Break loop when the passed predicate returns true + while (! std::forward(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 elapsed = (now - start); + std::chrono::duration elapsed = (now - start); if (elapsed.count() > seconds) { - tut::fail("LoginListener::waitFor() timed out"); + 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 @@ -120,6 +123,14 @@ public: 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) { return out << "LoginListener(" << listener.mName << ')'; @@ -234,9 +245,9 @@ namespace tut credentials["passwd"] = "secret"; login.connect("login.bar.com", credentials); - llcoro::suspend(); - - ensure_equals("Online state", listener.lastEvent()["state"].asString(), "online"); + listener.waitFor( + "online state", + [&listener]()->bool{ return listener.lastEvent()["state"].asString() == "online"; }); } template<> template<> -- cgit v1.2.3