* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "eu/execute_unit.h"
#include "eu/sexecute_unit.h"
#include <climits>
#include <cstring>
#include <sys/stat.h>
#include "dfx/trace_record/ffrt_trace_record.h"
#include "dfx/trace/ffrt_trace.h"
#include "eu/co_routine_factory.h"
#include "eu/qos_interface.h"
#include "sched/scheduler.h"
#include "sched/workgroup_internal.h"
#include "util/ffrt_facade.h"
#include "util/slab.h"
#include "dfx/perf/ffrt_perf.h"
#include "dfx/sysevent/sysevent.h"
#include "internal_inc/config.h"
namespace {
* The stack canary is saved or restored during coroutine switch-out and switch-in,
* currently, only the stack canary used by the ohos compiler stack protection is global
* and is not affected by worker destruction.
*/
const size_t TIGGER_SUPPRESS_WORKER_COUNT = 4;
const size_t TIGGER_SUPPRESS_EXECUTION_NUM = 2;
const size_t MAX_ESCAPE_WORKER_NUM = 1024;
const int SEXECUTE_DESTRY_SLEEP_TIME = 1000;
const std::map<std::string,
void(*)(ffrt::SExecuteUnit*, const ffrt::QoS&, ffrt::TaskNotifyType)> NOTIFY_FUNCTION_FACTORY = {
{ "CameraDaemon", ffrt::SExecuteUnit::HandleTaskNotifyConservative },
#ifndef HWASAN_MODE
{ "bluetooth_service", ffrt::SExecuteUnit::HandleTaskNotifyUltraConservative },
#endif
};
}
namespace ffrt {
constexpr int MANAGER_DESTRUCT_TIMESOUT = 1000;
constexpr uint64_t DELAYED_WAKED_UP_TASK_TIME_INTERVAL = 5 * 1000 * 1000;
SExecuteUnit::SExecuteUnit() : ExecuteUnit(), handleTaskNotify(SExecuteUnit::HandleTaskNotifyDefault)
{
#ifdef OHOS_STANDARD_SYSTEM
for (const auto& notifyFunc : NOTIFY_FUNCTION_FACTORY) {
if (strstr(GetCurrentProcessName(), notifyFunc.first.c_str())) {
handleTaskNotify = notifyFunc.second;
break;
}
}
#endif
#ifdef FFRT_WORKERS_DYNAMIC_SCALING
int ret = BlockawareInit(&keyPtr);
if (ret != 0) {
FFRT_SYSEVENT_LOGE("blockaware init fail, ret[%d], key[0x%lx]", ret, keyPtr);
} else {
blockAwareInit = true;
}
#endif
FFRT_LOGD("Construction completed.");
}
SExecuteUnit::~SExecuteUnit()
{
tearDown = true;
for (auto qos = QoS::Min(); qos < QoS::Max(); ++qos) {
workerGroup[qos].SetTearDown();
}
for (auto qos = QoS::Min(); qos < QoS::Max(); ++qos) {
int try_cnt = MANAGER_DESTRUCT_TIMESOUT;
while (try_cnt-- > 0) {
{
std::lock_guard lk(*workerGroup[qos].mutex);
workerGroup[qos].cv.notify_all();
}
{
usleep(SEXECUTE_DESTRY_SLEEP_TIME);
std::shared_lock<std::shared_mutex> lck(workerGroup[qos].tgMutex);
if (workerGroup[qos].threads.empty()) {
break;
}
}
}
if (try_cnt <= 0) {
FFRT_SYSEVENT_LOGE("erase qos[%d] threads failed", qos);
}
}
FFRTFacade::GetDelayedWorker().Terminate();
FFRT_LOGD("Destruction completed.");
}
WorkerAction SExecuteUnit::WorkerIdleAction(CPUWorker* thread)
{
FFRT_PERF_TRACE_SCOPED_BY_GROUP(EU, SEU_WorkerIdleAction, DEFAULT_CONFIG);
if (tearDown) {
return WorkerAction::RETIRE;
}
auto& group = workerGroup[thread->GetQos()];
std::unique_lock lk(*group.mutex);
IntoSleep(thread->GetQos());
#ifdef FFRT_WORKERS_DYNAMIC_SCALING
BlockawareEnterSleeping();
#endif
#if !defined(SUPPORT_WORKER_DESTRUCT)
constexpr int waiting_seconds = 10;
#else
int waiting_seconds = std::max(16 - group.executingNum, 5);
#endif
if (group.cv.wait_for(lk, std::chrono::seconds(waiting_seconds), [this, thread] {
bool taskExistence = FFRTFacade::GetScheduler().GetGlobalTaskCnt(thread->GetQos());
return tearDown || taskExistence;
})) {
workerGroup[thread->GetQos()].OutOfSleep();
#ifdef FFRT_WORKERS_DYNAMIC_SCALING
BlockawareLeaveSleeping();
#endif
return WorkerAction::RETRY;
} else {
#if !defined(SUPPORT_WORKER_DESTRUCT)
workerGroup[thread->GetQos()].IntoDeepSleep();
CoStackFree();
if (IsExceedDeepSleepThreshold()) {
ffrt::CoRoutineReleaseMem();
}
group.cv.wait(lk, [this, thread] {
return tearDown ||
FFRTFacade::GetScheduler().GetTotalTaskCnt(thread->GetQos()) > 0;
});
workerGroup[thread->GetQos()].OutOfDeepSleep();
return WorkerAction::RETRY;
#else
workerGroup[thread->GetQos()].WorkerDestroy();
return WorkerAction::RETIRE;
#endif
}
}
void SExecuteUnit::WakeupWorkers(const QoS& qos)
{
if (tearDown) {
FFRT_SYSEVENT_LOGE("CPU Worker Manager exit");
return;
}
workerGroup[qos].cv.notify_one();
}
void SExecuteUnit::HandleTaskNotifyDefault(SExecuteUnit* manager, const QoS& qos, TaskNotifyType notifyType)
{
size_t taskCount = FFRTFacade::GetScheduler().GetGlobalTaskCnt(qos);
switch (notifyType) {
case TaskNotifyType::TASK_ADDED:
case TaskNotifyType::TASK_PICKED:
case TaskNotifyType::TASK_ESCAPED:
if (taskCount > 0) {
manager->PokeImpl(qos, taskCount, notifyType);
}
break;
default:
break;
}
}
void SExecuteUnit::HandleTaskNotifyConservative(SExecuteUnit* manager, const QoS& qos, TaskNotifyType notifyType)
{
int taskCount = FFRTFacade::GetScheduler().GetGlobalTaskCnt(qos);
if (taskCount == 0) {
return;
}
constexpr double thresholdTaskPick = 1.0;
CPUWorkerGroup& workerCtrl = manager->workerGroup[qos];
std::unique_lock<ffrt::fast_mutex> statusLock(workerCtrl.lock);
if (notifyType == TaskNotifyType::TASK_PICKED) {
int wakedWorkerCount = workerCtrl.executingNum;
double remainingLoadRatio = (wakedWorkerCount == 0) ? static_cast<double>(workerCtrl.maxConcurrency) :
static_cast<double>(taskCount) / static_cast<double>(wakedWorkerCount);
if (remainingLoadRatio <= thresholdTaskPick) {
return;
}
}
if (static_cast<uint32_t>(workerCtrl.executingNum) < workerCtrl.maxConcurrency) {
if (workerCtrl.sleepingNum == 0) {
FFRT_LOGI("begin to create worker, notifyType[%d]"
"execnum[%d], maxconcur[%d], slpnum[%d], dslpnum[%d]",
notifyType, workerCtrl.executingNum, workerCtrl.maxConcurrency,
workerCtrl.sleepingNum, workerCtrl.deepSleepingWorkerNum);
workerCtrl.WorkerCreate();
statusLock.unlock();
if (!manager->IncWorker(qos)) {
workerCtrl.RollBackCreate();
}
} else {
statusLock.unlock();
manager->WakeupWorkers(qos);
}
}
}
void SExecuteUnit::HandleTaskNotifyUltraConservative(SExecuteUnit* manager, const QoS& qos, TaskNotifyType notifyType)
{
(void)notifyType;
int taskCount = FFRTFacade::GetScheduler().GetGlobalTaskCnt(qos);
if (taskCount == 0) {
return;
}
CPUWorkerGroup& workerCtrl = manager->workerGroup[qos];
std::lock_guard lock(workerCtrl.lock);
int runningNum = static_cast<int>(manager->GetRunningNum(qos, workerCtrl));
#ifdef FFRT_WORKERS_DYNAMIC_SCALING
if (manager->blockAwareInit && !manager->stopMonitor && taskCount == runningNum) {
return;
}
#endif
if (taskCount < runningNum) {
return;
}
if (runningNum < static_cast<int>(workerCtrl.maxConcurrency)) {
if (workerCtrl.sleepingNum == 0) {
workerCtrl.WorkerCreate();
if (!manager->IncWorker(qos)) {
workerCtrl.RollBackCreate();
}
} else {
manager->WakeupWorkers(qos);
}
}
}
void SExecuteUnit::PokeImpl(const QoS& qos, uint32_t taskCount, TaskNotifyType notifyType)
{
FFRT_PERF_TRACE_SCOPED_BY_GROUP(EU, SEU_WorkerPoke, DEFAULT_CONFIG);
CPUWorkerGroup& workerCtrl = workerGroup[qos];
std::unique_lock<ffrt::fast_mutex> statusLock(workerCtrl.lock);
size_t runningNum = GetRunningNum(qos, workerCtrl);
size_t totalNum = static_cast<size_t>(workerCtrl.sleepingNum + workerCtrl.executingNum);
bool tiggerSuppression = (totalNum > TIGGER_SUPPRESS_WORKER_COUNT) &&
(runningNum > TIGGER_SUPPRESS_EXECUTION_NUM) && (taskCount < runningNum);
if (notifyType != TaskNotifyType::TASK_ADDED && notifyType != TaskNotifyType::TASK_ESCAPED && tiggerSuppression) {
return;
}
if ((static_cast<uint32_t>(workerCtrl.sleepingNum) > 0) && (runningNum < workerCtrl.maxConcurrency)) {
statusLock.unlock();
WakeupWorkers(qos);
} else if ((runningNum < workerCtrl.maxConcurrency) && (totalNum < workerCtrl.hardLimit)) {
workerCtrl.WorkerCreate();
FFRTTraceRecord::WorkRecord(qos(), workerCtrl.executingNum);
statusLock.unlock();
if (!IncWorker(qos)) {
workerCtrl.RollBackCreate();
}
} else if ((runningNum == 0) && (totalNum < MAX_ESCAPE_WORKER_NUM)) {
SubmitEscape(qos, totalNum);
}
}
void SExecuteUnit::ExecuteEscape(int qos)
{
if (FFRTFacade::GetScheduler().GetGlobalTaskCnt(qos) <= 0) {
return;
}
CPUWorkerGroup& workerCtrl = workerGroup[qos];
std::unique_lock<ffrt::fast_mutex> statusLock(workerCtrl.lock);
size_t runningNum = GetRunningNum(qos, workerCtrl);
size_t totalNum = static_cast<size_t>(workerCtrl.sleepingNum + workerCtrl.executingNum);
if ((workerCtrl.sleepingNum > 0) && (runningNum < workerCtrl.maxConcurrency)) {
statusLock.unlock();
WakeupWorkers(qos);
} else if ((runningNum == 0) && (totalNum < MAX_ESCAPE_WORKER_NUM)) {
size_t executingNum = workerCtrl.executingNum;
if (IsEscapeEnable()) {
workerCtrl.WorkerCreate();
executingNum++;
FFRTTraceRecord::WorkRecord(qos, executingNum);
auto curTime = std::chrono::steady_clock::now();
if (!isEscapeStageOne(executingNum) ||
(curTime - workerCtrl.escapeReportTime) > std::chrono::seconds(1)) {
workerCtrl.escapeReportTime = curTime;
statusLock.unlock();
ReportEscapeEvent(qos, executingNum);
} else {
statusLock.unlock();
}
if (!IncWorker(qos)) {
workerCtrl.RollBackCreate();
}
} else {
statusLock.unlock();
}
}
}
}