blob: 44e3878afe528c4f84e4ff0ca1fe05d46e73c1e0 [file] [log] [blame]
// Copyright 2022 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/browser/web_applications/locks/web_app_lock_manager.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_runner.h"
#include "base/types/pass_key.h"
#include "base/values.h"
#include "chrome/browser/web_applications/locks/all_apps_lock.h"
#include "chrome/browser/web_applications/locks/app_lock.h"
#include "chrome/browser/web_applications/locks/lock.h"
#include "chrome/browser/web_applications/locks/noop_lock.h"
#include "chrome/browser/web_applications/locks/partitioned_lock_id.h"
#include "chrome/browser/web_applications/locks/partitioned_lock_manager.h"
#include "chrome/browser/web_applications/locks/shared_web_contents_lock.h"
#include "chrome/browser/web_applications/locks/shared_web_contents_with_app_lock.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "components/webapps/common/web_app_id.h"
namespace web_app {
namespace {
// The WebAppLockManager uses the following breakdown to define its locks with
// the PartitionedLockManager:
// - NoopLock:
// - No locks.
// - SharedWebContentsLock:
// - Exclusive {kStatic, kBackgroundWebContents}
// - AllAppsLock:
// - Exclusive {kStatic, kAllApps}
// - AppLock:
// - Shared {kStatic, kAllApps}
// - Exclusive {kApp, <app_id>}
// - <repeat for each app id requested>
// - SharedWebContentsWithAppLock
// - Exclusive {kStatic, kBackgroundWebContents}
// - Shared {kStatic, kAllApps}
// - Exclusive {kApp, <app_id>}
// - <repeat for each app id requested>
//
// We use strict lock ordering to ensure no deadlocks occur, and to allow
// 'upgrading'. Locks must be in this order to facilitate the upgrade
// functionality provided by the system and not deadlock
// - kBackgroundWebContents (static partition)
// - kAllApps (static partition)
// - kApp (app partition)
//
// For example, the lock used for the SharedWebContentsLock has to be
// 'before' the locks used for the AppLock, as this allows the lock upgrade
// method `UpgradeAndAcquireLock` below where a SharedWebContentsLock can be
// upgraded to a SharedWebContentsWithAppLock.
// These values are not persisted to disk or logs.
enum class LockPartition {
kStatic = 0,
kApp = 1,
kMaxValue = kApp,
};
// These values are not persisted to disk or logs.
enum KeysOnStaticPartition {
kBackgroundWebContents = 0,
kAllApps = 1,
kMaxValue = kAllApps,
};
static_assert(LockPartition::kStatic < LockPartition::kApp);
static_assert(KeysOnStaticPartition::kBackgroundWebContents <
KeysOnStaticPartition::kAllApps);
// Since we use `base::NumberToString` for the static partition data below, and
// the PartitionedLockId uses alphabetical sorting for the data field, the value
// must stay below 10 until there is a different sorting / encoding scheme.
static_assert(KeysOnStaticPartition::kMaxValue < 10);
const char* KeysOnStaticPartitionToString(KeysOnStaticPartition key) {
switch (key) {
case KeysOnStaticPartition::kAllApps:
return "AllApps";
case KeysOnStaticPartition::kBackgroundWebContents:
return "BackgroundWebContents";
}
}
PartitionedLockManager::PartitionedLockRequest GetAllAppsLock(
PartitionedLockManager::LockType type) {
return PartitionedLockManager::PartitionedLockRequest(
PartitionedLockId(
{static_cast<int>(LockPartition::kStatic),
base::NumberToString(KeysOnStaticPartition::kAllApps)}),
type);
}
PartitionedLockManager::PartitionedLockRequest GetSharedWebContentsLock() {
return PartitionedLockManager::PartitionedLockRequest(
PartitionedLockId({static_cast<int>(LockPartition::kStatic),
base::NumberToString(
KeysOnStaticPartition::kBackgroundWebContents)}),
PartitionedLockManager::LockType::kExclusive);
}
std::vector<PartitionedLockManager::PartitionedLockRequest>
GetExclusiveAppIdLocks(const base::flat_set<webapps::AppId>& app_ids) {
std::vector<PartitionedLockManager::PartitionedLockRequest> lock_requests;
for (const webapps::AppId& app_id : app_ids) {
lock_requests.emplace_back(
PartitionedLockId({static_cast<int>(LockPartition::kApp), app_id}),
PartitionedLockManager::LockType::kExclusive);
}
return lock_requests;
}
std::vector<PartitionedLockManager::PartitionedLockRequest>
GetLockRequestsForLock(const LockDescription& lock) {
std::vector<PartitionedLockManager::PartitionedLockRequest> requests;
switch (lock.type()) {
case LockDescription::Type::kNoOp:
return {};
case LockDescription::Type::kApp:
requests = GetExclusiveAppIdLocks(lock.app_ids());
requests.push_back(
GetAllAppsLock(PartitionedLockManager::LockType::kShared));
return requests;
case LockDescription::Type::kAllAppsLock:
return {GetAllAppsLock(PartitionedLockManager::LockType::kExclusive)};
case LockDescription::Type::kAppAndWebContents:
requests = GetExclusiveAppIdLocks(lock.app_ids());
requests.push_back(
GetAllAppsLock(PartitionedLockManager::LockType::kShared));
requests.push_back(GetSharedWebContentsLock());
return requests;
case LockDescription::Type::kBackgroundWebContents:
return {GetSharedWebContentsLock()};
}
}
#if DCHECK_IS_ON()
void LogLockRequest(
const LockDescription& description,
const base::Location& location,
const std::vector<PartitionedLockManager::PartitionedLockRequest>& requests,
const PartitionedLockManager& lock_manager) {
DVLOG(1) << "Requesting or upgrading to lock " << base::ToString(description)
<< " for location " << location.ToString();
std::vector<base::Location> locations =
lock_manager.GetHeldAndQueuedLockLocations(requests);
if (!locations.empty()) {
DVLOG(1) << "Lock currently held (or queued to be held) by:";
for (const auto& held_location : locations) {
DVLOG(1) << " " << held_location.ToString();
}
}
}
#endif
} // namespace
template <class LockType>
void WebAppLockManager::GrantLock(base::WeakPtr<LockType> lock) {
// This callback is never called if the lock holder is destroyed, see
// invariant on PartitionedLockManager::AcquireLocks
CHECK(lock);
lock->GrantLock(*this);
}
template void WebAppLockManager::GrantLock<NoopLock>(
base::WeakPtr<NoopLock> lock);
template void WebAppLockManager::GrantLock<AppLock>(
base::WeakPtr<AppLock> lock);
template void WebAppLockManager::GrantLock<AllAppsLock>(
base::WeakPtr<AllAppsLock> lock);
template <>
void WebAppLockManager::GrantLock(base::WeakPtr<SharedWebContentsLock> lock) {
// This callback is never called if the lock holder is destroyed, see
// invariant on PartitionedLockManager::AcquireLocks
CHECK(lock);
lock->GrantLock(
*this, *provider_->command_manager().EnsureWebContentsCreated(PassKey()));
}
template <>
void WebAppLockManager::GrantLock(
base::WeakPtr<SharedWebContentsWithAppLock> lock) {
// This callback is never called if the lock holder is destroyed, see
// invariant on PartitionedLockManager::AcquireLocks
CHECK(lock);
lock->GrantLock(
*this, *provider_->command_manager().EnsureWebContentsCreated(PassKey()));
}
WebAppLockManager::WebAppLockManager() = default;
WebAppLockManager::~WebAppLockManager() = default;
void WebAppLockManager::SetProvider(base::PassKey<WebAppCommandManager>,
WebAppProvider& provider) {
provider_ = &provider;
}
bool WebAppLockManager::IsSharedWebContentsLockFree() {
return lock_manager_.TestLock(GetSharedWebContentsLock()) ==
PartitionedLockManager::TestLockResult::kFree;
}
template <class LockType>
void WebAppLockManager::AcquireLock(
const LockType::LockDescription& lock_description,
LockType& lock,
base::OnceClosure on_lock_acquired,
const base::Location& location) {
CHECK(!lock.IsGranted());
AcquireLockImpl(lock.GetLockHolder(PassKey()), lock_description,
base::BindOnce(&WebAppLockManager::GrantLock<LockType>,
GetWeakPtr(), lock.AsWeakPtr())
.Then(std::move(on_lock_acquired)),
location);
}
template void WebAppLockManager::AcquireLock<NoopLock>(
const NoopLockDescription& lock_description,
NoopLock& lock,
base::OnceClosure on_lock_acquired,
const base::Location& location);
template void WebAppLockManager::AcquireLock<SharedWebContentsLock>(
const SharedWebContentsLockDescription& lock_description,
SharedWebContentsLock& lock,
base::OnceClosure on_lock_acquired,
const base::Location& location);
template void WebAppLockManager::AcquireLock<AllAppsLock>(
const AllAppsLockDescription& lock_description,
AllAppsLock& lock,
base::OnceClosure on_lock_acquired,
const base::Location& location);
template void WebAppLockManager::AcquireLock<AppLock>(
const AppLockDescription& lock_description,
AppLock& lock,
base::OnceClosure on_lock_acquired,
const base::Location& location);
template void WebAppLockManager::AcquireLock<SharedWebContentsWithAppLock>(
const SharedWebContentsWithAppLockDescription& lock_description,
SharedWebContentsWithAppLock& lock,
base::OnceClosure on_lock_acquired,
const base::Location& location);
std::unique_ptr<SharedWebContentsWithAppLockDescription>
WebAppLockManager::UpgradeAndAcquireLock(
std::unique_ptr<SharedWebContentsLock> old_lock,
SharedWebContentsWithAppLock& new_lock,
const base::flat_set<webapps::AppId>& app_ids,
base::OnceClosure on_lock_acquired,
const base::Location& location) {
CHECK(!new_lock.IsGranted());
std::unique_ptr<SharedWebContentsWithAppLockDescription>
result_lock_description =
std::make_unique<SharedWebContentsWithAppLockDescription>(app_ids);
// Upgrading requires the new lock to still hold all of the old locks.
std::swap(new_lock.GetLockHolder(PassKey()).locks,
old_lock->GetLockHolder(PassKey()).locks);
// Note that the description given to `AcquireLock` is the
// `AppLockDescription` and not the `SharedWebContentsWithAppLock`. This is
// because `SharedWebContentsLock` already has the web contents lock granted,
// and we only need the extra app locks.
AcquireLockImpl(
new_lock.GetLockHolder(PassKey()), AppLockDescription(app_ids),
base::BindOnce(
&WebAppLockManager::GrantLock<SharedWebContentsWithAppLock>,
GetWeakPtr(), new_lock.AsWeakPtr())
.Then(std::move(on_lock_acquired)),
location);
return result_lock_description;
}
std::unique_ptr<AppLockDescription> WebAppLockManager::UpgradeAndAcquireLock(
std::unique_ptr<NoopLock> old_lock,
AppLock& new_lock,
const base::flat_set<webapps::AppId>& app_ids,
base::OnceClosure on_lock_acquired,
const base::Location& location) {
CHECK(!new_lock.IsGranted());
std::unique_ptr<AppLockDescription> result_lock_description =
std::make_unique<AppLockDescription>(app_ids);
// Upgrading requires the new lock to still hold all of the old locks.
std::swap(new_lock.GetLockHolder(PassKey()).locks,
old_lock->GetLockHolder(PassKey()).locks);
AcquireLockImpl(new_lock.GetLockHolder(PassKey()), *result_lock_description,
base::BindOnce(&WebAppLockManager::GrantLock<AppLock>,
GetWeakPtr(), new_lock.AsWeakPtr())
.Then(std::move(on_lock_acquired)),
location);
return result_lock_description;
}
base::Value WebAppLockManager::ToDebugValue() const {
return lock_manager_.ToDebugValue([](const PartitionedLockId& lock)
-> std::string {
// Out of bounds things should NOT happen here, but given this is being
// called from debug UI, it's better to return something reasonable
// instead of doing undefined behavior.
DCHECK_GE(lock.partition, 0);
DCHECK_LE(lock.partition, static_cast<int>(LockPartition::kMaxValue));
if (lock.partition < 0 ||
lock.partition > static_cast<int>(LockPartition::kMaxValue)) {
return base::StringPrintf("Invalid lock partition: %i", lock.partition);
}
LockPartition partition = static_cast<LockPartition>(lock.partition);
switch (partition) {
case LockPartition::kApp:
return base::StrCat({"App, ", lock.key});
case LockPartition::kStatic: {
int lock_key = -1;
if (!base::StringToInt(lock.key, &lock_key)) {
return base::StringPrintf("Static, invalid number '%s'",
lock.key.c_str());
}
DCHECK_GE(lock_key, 0);
DCHECK_LE(lock_key, static_cast<int>(KeysOnStaticPartition::kMaxValue));
if (lock_key < 0 ||
lock_key > static_cast<int>(KeysOnStaticPartition::kMaxValue)) {
return base::StringPrintf("Static, invalid key number: %i", lock_key);
}
KeysOnStaticPartition key =
static_cast<KeysOnStaticPartition>(lock_key);
return base::StringPrintf("Static, '%s'",
KeysOnStaticPartitionToString(key));
}
}
});
}
base::WeakPtr<WebAppLockManager> WebAppLockManager::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void WebAppLockManager::AcquireLockImpl(PartitionedLockHolder& holder,
const LockDescription& lock_description,
base::OnceClosure on_lock_acquired,
const base::Location& location) {
std::vector<PartitionedLockManager::PartitionedLockRequest> requests =
GetLockRequestsForLock(lock_description);
#if DCHECK_IS_ON()
LogLockRequest(lock_description, location, requests, lock_manager_);
#endif
lock_manager_.AcquireLocks(std::move(requests), holder,
std::move(on_lock_acquired), location);
}
} // namespace web_app