[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