// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/credential_provider/extension/task_manager.h"
#include <windows.h>
#include <memory>
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "chrome/credential_provider/extension/extension_utils.h"
#include "chrome/credential_provider/extension/user_context_enumerator.h"
#include "chrome/credential_provider/gaiacp/logging.h"
#include "chrome/credential_provider/gaiacp/reg_utils.h"
namespace credential_provider {
namespace extension {
namespace {
// Backoff policy for errors returned by performing task operation.
const net::BackoffEntry::Policy kRetryLaterPolicy = {
// Number of initial errors to ignore before starting to back off.
0,
// Initial delay in ms: 1 minute.
1000 * 60,
// Factor by which the waiting time is multiplied.
2,
// Fuzzing percentage; this spreads delays randomly between 80% and 100%
// of the calculated time.
0.20,
// Maximum delay in ms: 3 hours. However this field of back off policy is
// overridden by individual task's execution period when actually backing
// off.
1000 * 60 * 60 * 3,
// When to discard an entry: never.
-1,
// |always_use_initial_delay|; false means that the initial delay is
// applied after the first error, and starts backing off from there.
true,
};
// Returns the elapsed time delta since the last time the periodic sync were
// successfully performed for the given task registry.
base::TimeDelta GetTimeDeltaSinceLastPeriodicSync(
const std::wstring& task_reg_name) {
wchar_t last_sync_millis[512];
ULONG last_sync_size = std::size(last_sync_millis);
HRESULT hr = GetGlobalFlag(task_reg_name, last_sync_millis, &last_sync_size);
if (FAILED(hr)) {
// The periodic sync has never happened before.
return base::TimeDelta::Max();
}
int64_t last_sync_millis_int64;
base::StringToInt64(last_sync_millis, &last_sync_millis_int64);
const auto last_sync = base::Time::FromDeltaSinceWindowsEpoch(
base::Milliseconds(last_sync_millis_int64));
return base::Time::Now() - last_sync;
}
} // namespace
// static
TaskManager** TaskManager::GetInstanceStorage() {
static TaskManager* instance = new TaskManager();
return &instance;
}
// static
TaskManager* TaskManager::Get() {
return *GetInstanceStorage();
}
TaskManager::TaskManager() {}
TaskManager::~TaskManager() {}
void TaskManager::ExecuteTask(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
const std::string& task_name) {
LOGFN(VERBOSE);
// Get an instance of the task and execute it.
std::unique_ptr<Task> task((task_list_[task_name]).Run());
if (task == nullptr) {
LOGFN(ERROR) << task_name << " task is null";
return;
}
HRESULT hr =
UserContextEnumerator::Get()->PerformTask(task_name, *task.get());
// Calculate next time the task should be executed.
base::TimeDelta next_run;
if (FAILED(hr)) {
LOGFN(ERROR) << task_name << " failed hr=" << putHR(hr);
// Check whether a backoff entry exists for the task. If so, consider the
// backoff period when calculating next run.
if (task_execution_backoffs_.find(task_name) ==
task_execution_backoffs_.end()) {
// First set the template backoff policy for the task.
task_execution_policies_[task_name] =
std::make_unique<net::BackoffEntry::Policy>();
*task_execution_policies_[task_name] = kRetryLaterPolicy;
// Max backoff time shouldn't be more than task execution period.
task_execution_policies_[task_name]->maximum_backoff_ms =
task->GetConfig().execution_period.InMilliseconds();
const net::BackoffEntry::Policy* policy =
task_execution_policies_.find(task_name)->second.get();
// Create backoff entry as this is the first failure after a success.
task_execution_backoffs_[task_name] =
std::make_unique<net::BackoffEntry>(policy);
}
task_execution_backoffs_[task_name]->InformOfRequest(false);
// Get backoff time for the next request.
next_run = task_execution_backoffs_[task_name]->GetTimeUntilRelease();
} else {
// Clear the back off entry as the task succeeded. An alternative was to
// keep it and report success.
task_execution_backoffs_.erase(task_name);
next_run = task->GetConfig().execution_period;
LOGFN(INFO) << task_name << " was executed successfully!";
const base::Time sync_time = base::Time::Now();
const std::wstring sync_time_millis = base::NumberToWString(
sync_time.ToDeltaSinceWindowsEpoch().InMilliseconds());
SetGlobalFlag(GetLastSyncRegNameForTask(base::UTF8ToWide(task_name)),
sync_time_millis);
}
// Schedule next task execution.
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&TaskManager::ExecuteTask, base::Unretained(this),
task_runner, task_name),
next_run);
}
void TaskManager::RunTasks(
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
// Enumerate registered tasks and kick-off the periodic execution.
for (auto it = task_list_.begin(); it != task_list_.end(); ++it) {
// Get an instance of registered Task.
std::unique_ptr<Task> task((it->second).Run());
if (task == nullptr) {
LOGFN(ERROR) << it->first << " task is null";
continue;
}
// Calculate the next run so that periodic polling happens within
// proper time intervals. When the tasks are scheduled, we don't want to
// immediately start executing to allow some warm-up.
base::TimeDelta next_run = base::Seconds(10);
const base::TimeDelta time_since_last_run =
GetTimeDeltaSinceLastPeriodicSync(
GetLastSyncRegNameForTask(base::UTF8ToWide(it->first)));
if (time_since_last_run < task->GetConfig().execution_period)
next_run = task->GetConfig().execution_period - time_since_last_run;
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&TaskManager::ExecuteTask, base::Unretained(this),
task_runner, it->first),
next_run);
}
}
void TaskManager::RegisterTask(const std::string& task_name,
TaskCreator task_creator) {
LOGFN(VERBOSE);
task_list_.emplace(task_name, std::move(task_creator));
}
} // namespace extension
} // namespace credential_provider