From 5bb2e5531863f4eb2b71ceacb78bc6bfb5c01a61 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Tue, 8 Aug 2017 13:26:50 -0700 Subject: [PATCH] Move Watchdog Timer to L3 Init Only Merge from Widevine repo of http://go/wvgerrit/31340 This CL moves the oemcrypto watchdog timer so that it only watches the L3 initialization. This will allow L1 initialization to take more than 5 seconds if it needs to. TEST: oemcrypto unit tests, Media GTS tests, Play Movies b/64069544 Change-Id: I7826e4d72eda52ae8b2c9f8b3ac360fb42cbb115 --- .../core/src/oemcrypto_adapter_dynamic.cpp | 369 +++++++++--------- 1 file changed, 184 insertions(+), 185 deletions(-) diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp index 432c1381..3ec8429a 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -376,6 +376,162 @@ void clear_cache_function(void *page, size_t len) { #endif } +// The WatchDog looks after a worker thread that is trying to initialize L3. +// Once in a rare while, the L3 init does not finish and eats up CPU cycles. +// If that happens, the watchdog thread will give up and return an error. +class WatchDog { + public: + // Created by main thread. + WatchDog() { + pthread_mutex_init(&mutex_, NULL); + pthread_cond_init(&condition_, NULL); + status_ = OEMCrypto_SUCCESS; + gave_up_ = false; + } + + // Deleted by either thread. + ~WatchDog() { + pthread_cond_destroy(&condition_); + } + + // Starts worker thread. + void StartThread() { + running_ = true; + if(pthread_create(&thread_, NULL, RunWatchDog, this)) { + LOGE("Could not create watch dog thread."); + status_ = OEMCrypto_ERROR_INIT_FAILED; + running_ = false; + return; + } + } + + // Function called by new worker thread in pthread_create. + static void *RunWatchDog(void *watcher) { + WatchDog* dog = reinterpret_cast(watcher); + dog->DoInit(); + dog->SignalDoneAndCleanUp(); + return NULL; + } + + // Called by worker thread. + void DoInit() { + std::string base_path; + wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3, + &base_path); + status_ = Level3_Initialize(clear_cache_function, + base_path.c_str()); + } + + std::string FailureFilename() { + std::string path; + if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3, + &path)) { + LOGW("WatchDog::FailureFilename: Unable to get base path"); + return "/data/l3_failure_file"; + } + path += "l3_failure_file"; + return path; + } + + // Check to see if the failure file was created before that last abort. + void CheckForPreviousFailure(wvcdm::metrics::CryptoMetrics* metrics) { + wvcdm::FileSystem file_system; + std::string filename = FailureFilename(); + if (!file_system.Exists(filename)) return; + wvcdm::File* file = file_system.Open(filename, file_system.kReadOnly); + if (file) { + uint32_t flag = 0; + ssize_t size = sizeof(flag); + ssize_t size_read = file->Read(reinterpret_cast(&flag), size); + file->Close(); + file_system.Remove(filename); + if (size == size_read && flag) { + LOGE("Previous L3 Init failed."); + if (metrics == nullptr) return; + M_RECORD( + metrics, + oemcrypto_initialization_mode_, + NO_TIME, + wvcdm::metrics::OEMCrypto_INITIALIZED_L3_INITIALIZATION_FAILED); + } + } + } + + // Save the failure file before we abort. + void SaveFailureInformation() { + wvcdm::FileSystem file_system; + std::string filename = FailureFilename(); + LOGD("failure filename = %s", filename.c_str()); + wvcdm::File* file = file_system.Open( + filename, file_system.kCreate | file_system.kTruncate); + if (!file) { + LOGE("Could not create file %s", filename.c_str()); + return; + } + uint32_t flag = 0x6261640a; // bad + ssize_t size = sizeof(flag); + ssize_t size_written = file->Write(reinterpret_cast(&flag), size); + file->Close(); + if (size != size_written) { + LOGE("Wrote %d bytes, not %d, to file %s", size_written, size, + filename.c_str()); + } else { + LOGE("I wrote %d to %s", size_written, filename.c_str()); + } + } + + // Called by worker thread after DoInit has finshed. + void SignalDoneAndCleanUp() { + pthread_mutex_lock(&mutex_); + running_ = false; + pthread_cond_signal(&condition_); + // If the main thread gave up, it won't delete this, so we must. + bool should_delete = gave_up_; + pthread_mutex_unlock(&mutex_); + // https://isocpp.org/wiki/faq/freestore-mgmt#delete-this + if (should_delete) delete this; + } + + // Called by main thread to wait for worker thread. + OEMCryptoResult WaitForStatusAndCleanUp() { + pthread_mutex_lock(&mutex_); + struct timespec time_to_giveup; + clock_gettime(CLOCK_REALTIME, &time_to_giveup); + time_to_giveup.tv_sec += 5; // wait 5 seconds. + if (running_) { + pthread_cond_timedwait(&condition_, &mutex_, &time_to_giveup); + } + if (running_) { + gave_up_ = true; + status_ = OEMCrypto_ERROR_INIT_FAILED; + LOGE("XXX WATCH DOG ERROR XXX"); + SaveFailureInformation(); + // This is controversial. The argument for an abort here is that if we + // do not abort, we will suck all the life out of the user's battery. By + // saving information to the file system, above, we can still track + // metrics. + abort(); + } + // If we gave up waiting for init thread, we should not delete the mutex + // out from under it. + bool should_delete = !gave_up_; + OEMCryptoResult status = status_; + pthread_mutex_unlock(&mutex_); + if (should_delete) delete this; + return status; + } + + OEMCryptoResult status() { return status_; } + + private: + OEMCryptoResult status_; + pthread_t thread_; + pthread_mutex_t mutex_; + pthread_cond_t condition_; + bool running_; + bool gave_up_; +}; + struct LevelSession { FunctionPointers* fcn; OEMCrypto_SESSION session; @@ -418,21 +574,29 @@ class Adapter { ~Adapter() { } - OEMCryptoResult Initialize(wvcdm::metrics::CryptoMetrics* metrics) { - if (metrics == nullptr) { - return OEMCrypto_ERROR_INIT_FAILED; - } + OEMCryptoResult Initialize() { + + /* + * To avoid changing the function signature and function contract - declare + * a one-off metrics group to collect detailed information about how + * oemcrypto was intialized. + * + * TODO(blueeyes): Refactor this to allow Initialize to provide the + * details to the caller or to use the metrics instance provided by + * the caller. + */ + wvcdm::metrics::CryptoMetrics metrics; + level1_ = FunctionPointers(); // start with all null pointers. level3_ = FunctionPointers(); // start with all null pointers. LoadLevel3(); - std::string base_path; - wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3, - &base_path); - OEMCryptoResult result = Level3_Initialize(clear_cache_function, - base_path.c_str()); + WatchDog *watcher = new WatchDog(); + watcher->CheckForPreviousFailure(&metrics); + watcher->StartThread(); + OEMCryptoResult result = watcher->WaitForStatusAndCleanUp(); if (Level3_IsInApp()) { M_RECORD( - metrics, + &metrics, oemcrypto_initialization_mode_, NO_TIME, wvcdm::metrics::OEMCrypto_INITIALIZED_USING_IN_APP); @@ -441,7 +605,7 @@ class Adapter { if (force_level3()) { LOGW("Test code. User requested falling back to L3"); M_RECORD( - metrics, + &metrics, oemcrypto_initialization_mode_, NO_TIME, wvcdm::metrics::OEMCrypto_INITIALIZED_FORCING_L3); @@ -451,7 +615,7 @@ class Adapter { if (!wvcdm::Properties::GetOEMCryptoPath(&library_name)) { LOGW("L1 library not specified. Falling back to L3"); M_RECORD( - metrics, + &metrics, oemcrypto_initialization_mode_, NO_TIME, wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_NO_L1_LIBRARY_PATH); @@ -462,13 +626,13 @@ class Adapter { LOGW("Could not load %s. Falling back to L3. %s", library_name.c_str(), dlerror()); M_RECORD( - metrics, + &metrics, oemcrypto_initialization_mode_, NO_TIME, wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_L1_OPEN_FAILED); return result; } - if (LoadLevel1(metrics)) { + if (LoadLevel1(&metrics)) { LOGD("OEMCrypto_Initialize Level 1 success. I will use level 1."); } else { level1_ = FunctionPointers(); // revert to all null pointers. @@ -841,173 +1005,6 @@ class Adapter { static Adapter* kAdapter = 0; -void *RunWatchDog(void *watcher); - -// The WatchDog looks after a worker thread that is trying to initialize L3. -// Once in a rare while, the L3 init does not finish and eats up CPU cycles. -// If that happens, the watchdog thread will give up and return an error. -class WatchDog { - public: - // Created by main thread. - WatchDog() { - pthread_mutex_init(&mutex_, NULL); - pthread_cond_init(&condition_, NULL); - status_ = OEMCrypto_SUCCESS; - gave_up_ = false; - } - - // Deleted by either thread. - ~WatchDog() { - pthread_cond_destroy(&condition_); - } - - // Starts worker thread. - void StartThread() { - running_ = true; - if(pthread_create(&thread_, NULL, RunWatchDog, this)) { - LOGE("Could not create watch dog thread."); - status_ = OEMCrypto_ERROR_INIT_FAILED; - running_ = false; - return; - } - } - - // Called by worker thread. - void DoInit() { - if (kAdapter) { - kAdapter->Terminate(); - delete kAdapter; - } - kAdapter = new Adapter(); - status_ = kAdapter->Initialize(&metrics_); - } - - std::string FailureFilename() { - std::string path; - if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3, - &path)) { - LOGW("DeviceFiles::StoreFileRaw: Unable to get base path"); - return "/data/l3_failure_file"; - } - path += "l3_failure_file"; - return path; - } - - // Check to see if the failure file was created before that last abort. - void CheckForPreviousFailure() { - wvcdm::FileSystem file_system; - std::string filename = FailureFilename(); - if (!file_system.Exists(filename)) return; - wvcdm::File* file = file_system.Open(filename, file_system.kReadOnly); - if (file) { - uint32_t flag = 0; - ssize_t size = sizeof(flag); - ssize_t size_read = file->Read(reinterpret_cast(&flag), size); - file->Close(); - file_system.Remove(filename); - if (size == size_read && flag) { - LOGE("Previous L3 Init failed."); - M_RECORD( - &metrics_, - oemcrypto_initialization_mode_, - NO_TIME, - wvcdm::metrics::OEMCrypto_INITIALIZED_L3_INITIALIZATION_FAILED); - } - } - } - - // Save the failure file before we abort. - void SaveFailureInformation() { - wvcdm::FileSystem file_system; - std::string filename = FailureFilename(); - LOGD("failure filename = %s", filename.c_str()); - wvcdm::File* file = file_system.Open( - filename, file_system.kCreate | file_system.kTruncate); - if (!file) { - LOGE("Could not create file %s", filename.c_str()); - return; - } - uint32_t flag = 0x6261640a; // bad - ssize_t size = sizeof(flag); - ssize_t size_written = file->Write(reinterpret_cast(&flag), size); - file->Close(); - if (size != size_written) { - LOGE("Wrote %d bytes, not %d, to file %s", size_written, size, - filename.c_str()); - } else { - LOGE("I wrote %d to %s", size_written, filename.c_str()); - } - } - - // Called by worker thread after DoInit has finshed. - void SignalDoneAndCleanUp() { - pthread_mutex_lock(&mutex_); - running_ = false; - pthread_cond_signal(&condition_); - // If the main thread gave up, it won't delete this, so we must. - bool should_delete = gave_up_; - pthread_mutex_unlock(&mutex_); - // https://isocpp.org/wiki/faq/freestore-mgmt#delete-this - if (should_delete) delete this; - } - - // Called by main thread to wait for worker thread. - OEMCryptoResult WaitForStatusAndCleanUp() { - pthread_mutex_lock(&mutex_); - struct timespec time_to_giveup; - clock_gettime(CLOCK_REALTIME, &time_to_giveup); - time_to_giveup.tv_sec += 5; // wait 5 seconds. - if (running_) { - pthread_cond_timedwait(&condition_, &mutex_, &time_to_giveup); - } - if (running_) { - gave_up_ = true; - status_ = OEMCrypto_ERROR_INIT_FAILED; - LOGE("XXX WATCH DOG ERROR XXX"); - SaveFailureInformation(); - // This is controversial. The argument for an abort here is that if we - // do not abort, we will suck all the life out of the user's battery. By - // saving information to the file system, above, we can still track - // metrics. - abort(); - } - // If we gave up waiting for init thread, we should not delete the mutex - // out from under it. - bool should_delete = !gave_up_; - OEMCryptoResult status = status_; - pthread_mutex_unlock(&mutex_); - if (should_delete) delete this; - return status; - } - - OEMCryptoResult status() { return status_; } - - private: - OEMCryptoResult status_; - pthread_t thread_; - pthread_mutex_t mutex_; - pthread_cond_t condition_; - bool running_; - bool gave_up_; - // A Metrics Group for the different initialization outcomes. Since one - // outcome is a watchdog timeout and abort on the PREVIOUS initialization, - // we put the group here, owned by the watchdog timer. - // - // TODO(blueeyes): Refactor this to allow Initialize to provide the - // details to the caller or to use the metrics instance provided by - // the caller. - - wvcdm::metrics::CryptoMetrics metrics_; -}; - -// Function called by new worker thread in pthread_create. -void *RunWatchDog(void *watcher) { - WatchDog* dog = reinterpret_cast(watcher); - dog->DoInit(); - dog->SignalDoneAndCleanUp(); - return NULL; -} - } // namespace namespace wvcdm { @@ -1228,10 +1225,12 @@ OEMCryptoResult OEMCrypto_CreateOldUsageEntry( } // namespace wvcdm extern "C" OEMCryptoResult OEMCrypto_Initialize(void) { - WatchDog *watcher = new WatchDog(); - watcher->CheckForPreviousFailure(); - watcher->StartThread(); - return watcher->WaitForStatusAndCleanUp(); + if (kAdapter) { + kAdapter->Terminate(); + delete kAdapter; + } + kAdapter = new Adapter(); + return kAdapter->Initialize(); } extern "C" OEMCryptoResult OEMCrypto_Terminate(void) {