Merge cherrypicks of ['googleplex-android-review.googlesource.com/27324868'] into 24D1-release.
Change-Id: Ic667c93c65571cc6fb64a6f3fba832f2b59faa63
diff --git a/statsd/src/StatsService.cpp b/statsd/src/StatsService.cpp
index 86c2de8..afd5194 100644
--- a/statsd/src/StatsService.cpp
+++ b/statsd/src/StatsService.cpp
@@ -234,6 +234,7 @@
}
StatsService::~StatsService() {
+ onStatsdInitCompletedHandlerTermination();
if (mEventQueue != nullptr) {
stopReadingLogs();
mLogsReaderThread->join();
@@ -1062,6 +1063,7 @@
Status StatsService::informDeviceShutdown() {
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::informDeviceShutdown");
+ onStatsdInitCompletedHandlerTermination();
int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
int64_t wallClockNs = getWallClockNs();
mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, elapsedRealtimeNs, wallClockNs);
@@ -1115,7 +1117,14 @@
// This function is called from a dedicated thread without holding locks, so sleeping is ok.
// See MultiConditionTrigger::markComplete() executorThread for details
// For more details see http://b/277958338
- std::this_thread::sleep_for(std::chrono::seconds(mInitEventDelaySecs));
+
+ std::unique_lock<std::mutex> lk(mStatsdInitCompletedHandlerTerminationFlagMutex);
+ if (mStatsdInitCompletedHandlerTerminationFlag.wait_for(
+ lk, std::chrono::seconds(mInitEventDelaySecs),
+ [this] { return mStatsdInitCompletedHandlerTerminationRequested; })) {
+ VLOG("StatsService::onStatsdInitCompleted() Early termination is requested");
+ return;
+ }
}
mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs());
@@ -1132,6 +1141,7 @@
void StatsService::Terminate() {
ALOGI("StatsService::Terminating");
+ onStatsdInitCompletedHandlerTermination();
if (mProcessor != nullptr) {
int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
int64_t wallClockNs = getWallClockNs();
@@ -1142,6 +1152,14 @@
}
}
+void StatsService::onStatsdInitCompletedHandlerTermination() {
+ {
+ std::unique_lock<std::mutex> lk(mStatsdInitCompletedHandlerTerminationFlagMutex);
+ mStatsdInitCompletedHandlerTerminationRequested = true;
+ }
+ mStatsdInitCompletedHandlerTerminationFlag.notify_all();
+}
+
// Test only interface!!!
void StatsService::OnLogEvent(LogEvent* event) {
mProcessor->OnLogEvent(event);
@@ -1400,6 +1418,7 @@
void StatsService::statsCompanionServiceDiedImpl() {
ALOGW("statscompanion service died");
StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
+ onStatsdInitCompletedHandlerTermination();
if (mProcessor != nullptr) {
ALOGW("Reset statsd upon system server restarts.");
int64_t systemServerRestartNs = getElapsedRealtimeNs();
diff --git a/statsd/src/StatsService.h b/statsd/src/StatsService.h
index 0a167dd..6a5d954 100644
--- a/statsd/src/StatsService.h
+++ b/statsd/src/StatsService.h
@@ -416,10 +416,15 @@
void onStatsdInitCompleted();
/*
- * This method is used to stop log reader thread.
+ * This method is used to stop log reader thread.
*/
void stopReadingLogs();
+ /*
+ * Notify async StatsdInitCompleted handler about termination event
+ */
+ void onStatsdInitCompletedHandlerTermination();
+
std::atomic<bool> mIsStopRequested = false;
/**
@@ -468,6 +473,14 @@
std::unique_ptr<std::thread> mLogsReaderThread;
+ std::condition_variable mStatsdInitCompletedHandlerTerminationFlag;
+ std::mutex mStatsdInitCompletedHandlerTerminationFlagMutex;
+ /**
+ * @brief Used to communicated early termination request to onStatsdInitCompleted Handler
+ * @see onStatsdInitCompleted
+ */
+ bool mStatsdInitCompletedHandlerTerminationRequested = false;
+
MultiConditionTrigger mBootCompleteTrigger;
static const inline string kBootCompleteTag = "BOOT_COMPLETE";
static const inline string kUidMapReceivedTag = "UID_MAP";
diff --git a/statsd/src/utils/MultiConditionTrigger.cpp b/statsd/src/utils/MultiConditionTrigger.cpp
index d9ad25b..e574969 100644
--- a/statsd/src/utils/MultiConditionTrigger.cpp
+++ b/statsd/src/utils/MultiConditionTrigger.cpp
@@ -14,11 +14,10 @@
* limitations under the License.
*/
#define STATSD_DEBUG false // STOPSHIP if true
+#include "Log.h"
#include "MultiConditionTrigger.h"
-#include <thread>
-
namespace android {
namespace os {
namespace statsd {
@@ -33,8 +32,7 @@
mTrigger(std::move(trigger)),
mCompleted(mRemainingConditionNames.empty()) {
if (mCompleted) {
- std::thread executorThread([this] { mTrigger(); });
- executorThread.detach();
+ startExecutorThread();
}
}
@@ -50,10 +48,21 @@
doTrigger = mCompleted;
}
if (doTrigger) {
- std::thread executorThread([this] { mTrigger(); });
- executorThread.detach();
+ startExecutorThread();
}
}
+
+void MultiConditionTrigger::startExecutorThread() {
+ mExecutorThread = std::make_unique<std::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 7a09119..773197d 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,16 +38,22 @@
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& conditionName);
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
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