[statsd] Make executor thread a class member of MultiConditionTrigger

executorThread references class members after detaching. Making
executorThread as class member and joining in MultiConditionTrigger
destructor.

Ignore-AOSP-First: Security bugs merged into internal branch first
Test: atest statsd_test
Bug: 292160348
Flag: NONE mainline module bug fix
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4196da2ec586a631fc9011c7615f80f4f197caf3)
Merged-In: I7036eb3d506e8ca88e4a5faa6275dc4cba8020ee
Change-Id: I7036eb3d506e8ca88e4a5faa6275dc4cba8020ee
diff --git a/statsd/src/utils/MultiConditionTrigger.cpp b/statsd/src/utils/MultiConditionTrigger.cpp
index 43a6933..aae27c2 100644
--- a/statsd/src/utils/MultiConditionTrigger.cpp
+++ b/statsd/src/utils/MultiConditionTrigger.cpp
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #define DEBUG false  // STOPSHIP if true
+#include "Log.h"
 
 #include "MultiConditionTrigger.h"
 
-#include <thread>
-
 using namespace std;
 
 namespace android {
@@ -31,8 +31,7 @@
       mTrigger(trigger),
       mCompleted(mRemainingConditionNames.empty()) {
     if (mCompleted) {
-        thread executorThread([this] { mTrigger(); });
-        executorThread.detach();
+        startExecutorThread();
     }
 }
 
@@ -48,10 +47,21 @@
         doTrigger = mCompleted;
     }
     if (doTrigger) {
-        std::thread executorThread([this] { mTrigger(); });
-        executorThread.detach();
+        startExecutorThread();
     }
 }
+
+void MultiConditionTrigger::startExecutorThread() {
+    mExecutorThread = make_unique<thread>([this] { mTrigger(); });
+}
+
+MultiConditionTrigger::~MultiConditionTrigger() {
+    if (mExecutorThread != nullptr && mExecutorThread->joinable()) {
+        VLOG("MultiConditionTrigger waiting on execution thread termination");
+        mExecutorThread->join();
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/utils/MultiConditionTrigger.h b/statsd/src/utils/MultiConditionTrigger.h
index 51f6029..dee0071 100644
--- a/statsd/src/utils/MultiConditionTrigger.h
+++ b/statsd/src/utils/MultiConditionTrigger.h
@@ -19,6 +19,7 @@
 
 #include <mutex>
 #include <set>
+#include <thread>
 
 namespace android {
 namespace os {
@@ -27,8 +28,8 @@
 /**
  * This class provides a utility to wait for a set of named conditions to occur.
  *
- * It will execute the trigger runnable in a detached thread once all conditions have been marked
- * true.
+ * It will execute the trigger runnable in a separate thread (which will be joined at instance
+ * destructor time) once all conditions have been marked true.
  */
 class MultiConditionTrigger {
 public:
@@ -37,19 +38,24 @@
 
     MultiConditionTrigger(const MultiConditionTrigger&) = delete;
     MultiConditionTrigger& operator=(const MultiConditionTrigger&) = delete;
+    ~MultiConditionTrigger();
 
     // Mark a specific condition as true. If this condition has called markComplete already or if
     // the event was not specified in the constructor, the function is a no-op.
     void markComplete(const std::string& eventName);
 
 private:
+    void startExecutorThread();
+
     mutable std::mutex mMutex;
     std::set<std::string> mRemainingConditionNames;
     std::function<void()> mTrigger;
     bool mCompleted;
+    std::unique_ptr<std::thread> mExecutorThread;
 
     FRIEND_TEST(MultiConditionTriggerTest, TestCountDownCalledBySameEventName);
 };
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/StatsLogProcessor_test.cpp b/statsd/tests/StatsLogProcessor_test.cpp
index 4ae26f7..7558fb8 100644
--- a/statsd/tests/StatsLogProcessor_test.cpp
+++ b/statsd/tests/StatsLogProcessor_test.cpp
@@ -325,7 +325,6 @@
 
 TEST(StatsLogProcessorTest, InvalidConfigRemoved) {
     // Setup simple config key corresponding to empty config.
-    StatsdStats::getInstance().reset();
     sp<UidMap> m = new UidMap();
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
@@ -337,6 +336,9 @@
                         [](const int&, const vector<int64_t>&) {return true;});
     ConfigKey key(3, 4);
     StatsdConfig config = MakeConfig(true);
+    // Remove the config mConfigStats so that the Icebox starts at 0 configs.
+    p.OnConfigRemoved(key);
+    StatsdStats::getInstance().reset();
     p.OnConfigUpdated(0, key, config);
     EXPECT_EQ(1, p.mMetricsManagers.size());
     EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end());
@@ -354,7 +356,6 @@
               StatsdStats::getInstance().mConfigStats.find(key));
     // Both "config" and "invalidConfig" should be in the icebox.
     EXPECT_EQ(2, StatsdStats::getInstance().mIceBox.size());
-
 }
 
 
diff --git a/statsd/tests/utils/MultiConditionTrigger_test.cpp b/statsd/tests/utils/MultiConditionTrigger_test.cpp
index 32cecd3..b525f75 100644
--- a/statsd/tests/utils/MultiConditionTrigger_test.cpp
+++ b/statsd/tests/utils/MultiConditionTrigger_test.cpp
@@ -22,6 +22,8 @@
 #include <thread>
 #include <vector>
 
+#include "tests/statsd_test_util.h"
+
 #ifdef __ANDROID__
 
 using namespace std;
@@ -166,6 +168,125 @@
     }
 }
 
+namespace {
+
+class TriggerDependency {
+public:
+    TriggerDependency(mutex& lock, condition_variable& cv, bool& triggerCalled, int& triggerCount)
+        : mLock(lock), mCv(cv), mTriggerCalled(triggerCalled), mTriggerCount(triggerCount) {
+    }
+
+    void someMethod() {
+        lock_guard lg(mLock);
+        mTriggerCount++;
+        mTriggerCalled = true;
+        mCv.notify_all();
+    }
+
+private:
+    mutex& mLock;
+    condition_variable& mCv;
+    bool& mTriggerCalled;
+    int& mTriggerCount;
+};
+
+}  // namespace
+
+TEST(MultiConditionTrigger, TestTriggerHasSleep) {
+    const string t1 = "t1";
+    set<string> conditionNames = {t1};
+
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+    int triggerCount = 0;
+
+    {
+        TriggerDependency dependency(lock, cv, triggerCalled, triggerCount);
+        MultiConditionTrigger trigger(conditionNames, [&dependency] {
+            std::this_thread::sleep_for(std::chrono::milliseconds(50));
+            dependency.someMethod();
+        });
+        trigger.markComplete(t1);
+
+        // Here dependency instance will go out of scope and the thread within MultiConditionTrigger
+        // after delay will try to call method of already destroyed class instance
+        // with leading crash if trigger execution thread is detached in MultiConditionTrigger
+        // Instead since the MultiConditionTrigger destructor happens before TriggerDependency
+        // destructor, MultiConditionTrigger destructor is waiting on execution thread termination
+        // with thread::join
+    }
+    // At this moment the executor thread guaranteed terminated by MultiConditionTrigger destructor
+
+    // Ensure that the trigger fired.
+    {
+        unique_lock<mutex> unique_lk(lock);
+        cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+        EXPECT_TRUE(triggerCalled);
+        EXPECT_EQ(triggerCount, 1);
+    }
+}
+
+TEST(MultiConditionTrigger, TestTriggerHasSleepEarlyTermination) {
+    const string t1 = "t1";
+    set<string> conditionNames = {t1};
+
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+    int triggerCount = 0;
+
+    std::condition_variable triggerTerminationFlag;
+    std::mutex triggerTerminationFlagMutex;
+    bool terminationRequested = false;
+
+    // used for error threshold tolerance due to wait_for() is involved
+    const int64_t errorThresholdMs = 25;
+    const int64_t triggerEarlyTerminationDelayMs = 100;
+    const int64_t triggerStartNs = getElapsedRealtimeNs();
+    {
+        TriggerDependency dependency(lock, cv, triggerCalled, triggerCount);
+        MultiConditionTrigger trigger(
+                conditionNames, [&dependency, &triggerTerminationFlag, &triggerTerminationFlagMutex,
+                                 &lock, &triggerCalled, &cv, &terminationRequested] {
+                    std::unique_lock<std::mutex> lk(triggerTerminationFlagMutex);
+                    if (triggerTerminationFlag.wait_for(
+                                lk, std::chrono::seconds(1),
+                                [&terminationRequested] { return terminationRequested; })) {
+                        // triggerTerminationFlag was notified - early termination is requested
+                        lock_guard lg(lock);
+                        triggerCalled = true;
+                        cv.notify_all();
+                        return;
+                    }
+                    dependency.someMethod();
+                });
+        trigger.markComplete(t1);
+
+        // notify to terminate trigger executor thread after triggerEarlyTerminationDelayMs
+        std::this_thread::sleep_for(std::chrono::milliseconds(triggerEarlyTerminationDelayMs));
+        {
+            std::unique_lock<std::mutex> lk(triggerTerminationFlagMutex);
+            terminationRequested = true;
+        }
+        triggerTerminationFlag.notify_all();
+    }
+    // At this moment the executor thread guaranteed terminated by MultiConditionTrigger destructor
+
+    // check that test duration is closer to 100ms rather to 1s
+    const int64_t triggerEndNs = getElapsedRealtimeNs();
+    EXPECT_LE(NanoToMillis(triggerEndNs - triggerStartNs),
+              triggerEarlyTerminationDelayMs + errorThresholdMs);
+
+    // Ensure that the trigger fired but not the dependency.someMethod().
+    {
+        unique_lock<mutex> unique_lk(lock);
+        cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+        EXPECT_TRUE(triggerCalled);
+        EXPECT_EQ(triggerCount, 0);
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android