From cbf146f2b3fc255bc83f2b01101dc29658bea6ea Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Thu, 24 Oct 2019 12:54:38 -0400
Subject: [PATCH] DRTVWR-476: Pump coroutines a few more times when we start
 quitting.

By the time "LLApp" listeners are notified that the app is quitting, the
mainloop is no longer running. Even though those listeners do things like
close work queues and inject exceptions into pending promises, any coroutines
waiting on those resources must regain control before they can notice and shut
down properly. Add a final "LLApp" listener that resumes ready coroutines a
few more times.

Make sure every other "LLApp" listener is positioned before that new one.
---
 indra/llcommon/llcoros.cpp               | 32 ++++++++++++++++++++++++
 indra/llcommon/llcoros.h                 |  3 +++
 indra/llcommon/lleventcoro.cpp           |  5 +++-
 indra/llmessage/llcoproceduremanager.cpp |  9 ++++---
 4 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index 78a0c5d2257..ea54f1aa925 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -128,6 +128,38 @@ LLCoros::LLCoros():
     mStackSize(256*1024)
 #endif
 {
+    // Set up a listener to notice when the viewer is starting to shut down.
+    // Store the connection in an LLTempBoundListener so it will automatically
+    // disconnect.
+    mAppListener = LLEventPumps::instance().obtain("LLApp").listen(
+        "final",              // must be the LAST listener on this LLEventPump
+        [this](const LLSD& status)
+        {
+            if (status["status"].asString() == "quitting")
+            {
+                // Other LLApp status-change listeners do things like close
+                // work queues and inject the Stop exception into pending
+                // promises, to force coroutines waiting on those things to
+                // notice and terminate. The only problem is that by the time
+                // LLApp sets "quitting" status, the main loop has stopped
+                // pumping the fiber scheduler with yield() calls. A waiting
+                // coroutine still might not wake up until after resources on
+                // which it depends have been freed. Pump it a few times
+                // ourselves. Of course, stop pumping as soon as the last of
+                // the coroutines has terminated.
+                for (size_t count = 0; count < 10 && ! mCoros.empty(); ++count)
+                {
+                    // don't use llcoro::suspend() because that module depends
+                    // on this one
+                    boost::this_fiber::yield();
+                }
+            }
+            // If we're really the last listener, it shouldn't matter whether
+            // we consume this event -- but our being last depends on every
+            // other listen() call specifying before "final", which would be
+            // all too easy to forget. So do not consume the event.
+            return false;
+        });
 }
 
 LLCoros::~LLCoros()
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index de7b6912841..171d1ebd2a7 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -34,6 +34,7 @@
 #include <boost/fiber/future/promise.hpp>
 #include <boost/fiber/future/future.hpp>
 #include "llsingleton.h"
+#include "llevents.h"
 #include <boost/ptr_container/ptr_map.hpp>
 #include <boost/function.hpp>
 #include <string>
@@ -284,6 +285,8 @@ class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
     typedef boost::ptr_map<std::string, CoroData> CoroMap;
     CoroMap mCoros;
 
+    LLTempBoundListener mAppListener;
+
     // Identify the current coroutine's CoroData. This local_ptr isn't static
     // because it's a member of an LLSingleton, and we rely on it being
     // cleaned up in proper dependency order.
diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp
index 967c4d74d8c..785c231f2cf 100644
--- a/indra/llcommon/lleventcoro.cpp
+++ b/indra/llcommon/lleventcoro.cpp
@@ -153,6 +153,7 @@ postAndSuspendSetup(const std::string& callerName,
     // The relative order of the two listen() calls below would only matter if
     // "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to
     // notice the pending LLApp status first.
+    // Run this listener before the "final" listener.
     LLBoundListener stopper(
         LLEventPumps::instance().obtain("LLApp").listen(
             listenerName,
@@ -181,7 +182,9 @@ postAndSuspendSetup(const std::string& callerName,
                 }
                 // do not consume -- every listener must see status
                 return false;
-            }));
+            },
+            LLEventPump::NameList{},            // after
+            LLEventPump::NameList{ "final "})); // before
     LLBoundListener connection(
         replyPump.listen(
             listenerName,
diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp
index a8f6b8aa674..a71a31bfd20 100644
--- a/indra/llmessage/llcoproceduremanager.cpp
+++ b/indra/llmessage/llcoproceduremanager.cpp
@@ -218,8 +218,9 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size):
     mHTTPPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID),
     mCoroMapping()
 {
-    // store in our LLTempBoundListener so that when the LLCoprocedurePool is
-    // destroyed, we implicitly disconnect from this LLEventPump
+    // Store in our LLTempBoundListener so that when the LLCoprocedurePool is
+    // destroyed, we implicitly disconnect from this LLEventPump.
+    // Run this listener before the "final" listener.
     mStatusListener = LLEventPumps::instance().obtain("LLApp").listen(
         poolName,
         [this, poolName](const LLSD& status)
@@ -235,7 +236,9 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size):
                 mPendingCoprocs.close();
             }
             return false;
-        });
+        },
+        LLEventPump::NameList{},           // after
+        LLEventPump::NameList{ "final "}); // before
 
     for (size_t count = 0; count < mPoolSize; ++count)
     {
-- 
GitLab