blob: dc5384dbf53fd8e47cdbf5affddd9d61d062f3f0 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/locks/lock_manager.h"
#include <algorithm>
#include <memory>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/guid.h"
#include "base/stl_util.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
using blink::mojom::LockMode;
namespace content {
namespace {
// Guaranteed to be smaller than any result of LockManager::NextLockId().
constexpr int64_t kPreemptiveLockId = 0;
// A LockHandle is passed to the client when a lock is granted. As long as the
// handle is held, the lock is held. Dropping the handle - either explicitly
// by script or by process termination - causes the lock to be released. The
// connection can also be closed here when a lock is stolen.
class LockHandleImpl final : public blink::mojom::LockHandle {
public:
static mojo::StrongBindingPtr<blink::mojom::LockHandle> Create(
base::WeakPtr<LockManager> context,
const url::Origin& origin,
int64_t lock_id,
blink::mojom::LockHandlePtr* ptr) {
return mojo::MakeStrongBinding(
std::make_unique<LockHandleImpl>(std::move(context), origin, lock_id),
mojo::MakeRequest(ptr));
}
LockHandleImpl(base::WeakPtr<LockManager> context,
const url::Origin& origin,
int64_t lock_id)
: context_(context), origin_(origin), lock_id_(lock_id) {}
~LockHandleImpl() override {
if (context_)
context_->ReleaseLock(origin_, lock_id_);
}
// Called when the handle will be released from this end of the pipe. It
// nulls out the context so that the lock will not be double-released.
void Close() { context_.reset(); }
private:
base::WeakPtr<LockManager> context_;
const url::Origin origin_;
const int64_t lock_id_;
DISALLOW_COPY_AND_ASSIGN(LockHandleImpl);
};
} // namespace
// A requested or held lock. When granted, a LockHandle will be minted
// and passed to the held callback. Eventually the client will drop the
// handle, which will notify the context and remove this.
class LockManager::Lock {
public:
Lock(const std::string& name,
LockMode mode,
int64_t lock_id,
const std::string& client_id,
blink::mojom::LockRequestPtr request)
: name_(name),
mode_(mode),
client_id_(client_id),
lock_id_(lock_id),
request_(std::move(request)) {}
~Lock() = default;
// Abort a lock request.
void Abort(const std::string& message) {
DCHECK(request_);
DCHECK(!handle_);
request_->Abort(message);
request_ = nullptr;
}
// Grant a lock request. This mints a LockHandle and returns it over the
// request pipe.
void Grant(base::WeakPtr<LockManager> context, const url::Origin& origin) {
DCHECK(context);
DCHECK(request_);
DCHECK(!handle_);
// Get a new ID when granted, to maintain map in grant order.
lock_id_ = context->NextLockId();
blink::mojom::LockHandlePtr ptr;
handle_ =
LockHandleImpl::Create(std::move(context), origin, lock_id_, &ptr);
request_->Granted(std::move(ptr));
request_ = nullptr;
}
// Break a granted lock. This terminates the connection, signaling an error
// on the other end of the pipe.
void Break() {
DCHECK(!request_);
DCHECK(handle_);
LockHandleImpl* impl = static_cast<LockHandleImpl*>(handle_->impl());
// Explicitly close the LockHandle first; this ensures that when the
// connection is subsequently closed it will not re-entrantly try to drop
// the lock.
impl->Close();
handle_->Close();
}
const std::string& name() const { return name_; }
LockMode mode() const { return mode_; }
int64_t lock_id() const { return lock_id_; }
const std::string& client_id() const { return client_id_; }
private:
const std::string name_;
const LockMode mode_;
const std::string client_id_;
// |lock_id_| is assigned when the request is arrives, and serves as the key
// in an ordered request map. If it is a PREEMPT request, it is given the
// special kPreemptiveLockId value which precedes all others, and is
// processed immediately. When the lock is granted, a new id is generated to
// be the key in the ordered map of held locks.
int64_t lock_id_;
// Exactly one of the following is non-null at any given time.
// |request_| is valid until the lock is granted (or failure).
blink::mojom::LockRequestPtr request_;
// Once granted, |handle_| holds this end of the pipe that lets us monitor
// for the other end going away.
mojo::StrongBindingPtr<blink::mojom::LockHandle> handle_;
};
LockManager::LockManager() : weak_ptr_factory_(this) {}
LockManager::~LockManager() = default;
class LockManager::OriginState {
public:
OriginState() = default;
~OriginState() = default;
const Lock* AddRequest(int64_t lock_id,
const std::string& name,
LockMode mode,
const std::string& client_id,
blink::mojom::LockRequestPtr request) {
auto it = requested_.emplace(
lock_id, std::make_unique<Lock>(name, mode, lock_id, client_id,
std::move(request)));
DCHECK(it.second) << "Insertion should have taken place";
return it.first->second.get();
}
bool EraseLock(int64_t lock_id) {
return requested_.erase(lock_id) || held_.erase(lock_id);
}
bool IsEmpty() const { return requested_.empty() && held_.empty(); }
#if DCHECK_IS_ON()
bool IsHeld(int64_t lock_id) { return held_.find(lock_id) != held_.end(); }
#endif
bool IsGrantable(const std::string& name, LockMode mode) const {
if (mode == LockMode::EXCLUSIVE) {
return !shared_.count(name) && !exclusive_.count(name);
} else {
return !exclusive_.count(name);
}
}
// Break any held locks by that name.
void Break(const std::string& name) {
for (auto it = held_.begin(); it != held_.end();) {
if (it->second->name() == name) {
std::unique_ptr<Lock> lock = std::move(it->second);
it = held_.erase(it);
// Deleting the LockHandleImpl will signal an error on the other end
// of the pipe, which will notify script that the lock was broken.
lock->Break();
} else {
++it;
}
}
}
void ProcessRequests(LockManager* lock_manager, const url::Origin& origin) {
shared_.clear();
exclusive_.clear();
for (const auto& id_lock_pair : held_) {
const auto& lock = id_lock_pair.second;
MergeLockState(lock->name(), lock->mode());
}
for (auto it = requested_.begin(); it != requested_.end();) {
auto& lock = it->second;
bool granted = IsGrantable(lock->name(), lock->mode());
MergeLockState(lock->name(), lock->mode());
if (granted) {
std::unique_ptr<Lock> grantee = std::move(lock);
it = requested_.erase(it);
grantee->Grant(lock_manager->weak_ptr_factory_.GetWeakPtr(), origin);
held_.insert(std::make_pair(grantee->lock_id(), std::move(grantee)));
} else {
++it;
}
}
}
std::vector<blink::mojom::LockInfoPtr> SnapshotRequested() const {
std::vector<blink::mojom::LockInfoPtr> out;
out.reserve(requested_.size());
for (const auto& id_lock_pair : requested_) {
const auto& lock = id_lock_pair.second;
out.emplace_back(base::in_place, lock->name(), lock->mode(),
lock->client_id());
}
return out;
}
std::vector<blink::mojom::LockInfoPtr> SnapshotHeld() const {
std::vector<blink::mojom::LockInfoPtr> out;
out.reserve(held_.size());
for (const auto& id_lock_pair : held_) {
const auto& lock = id_lock_pair.second;
out.emplace_back(base::in_place, lock->name(), lock->mode(),
lock->client_id());
}
return out;
}
private:
void MergeLockState(const std::string& name, LockMode mode) {
(mode == LockMode::SHARED ? shared_ : exclusive_).insert(name);
}
// These represent the actual state of locks in the origin.
std::map<int64_t, std::unique_ptr<Lock>> requested_;
std::map<int64_t, std::unique_ptr<Lock>> held_;
// These sets represent what is held or requested, so that "IsGrantable"
// tests are simple. These are cleared/rebuilt when the request queue
// is processed.
std::unordered_set<std::string> shared_;
std::unordered_set<std::string> exclusive_;
};
void LockManager::CreateService(blink::mojom::LockManagerRequest request,
const url::Origin& origin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(jsbell): This should reflect the 'environment id' from HTML,
// and be the same opaque string seen in Service Worker client ids.
const std::string client_id = base::GenerateGUID();
bindings_.AddBinding(this, std::move(request), {origin, client_id});
}
void LockManager::RequestLock(const std::string& name,
LockMode mode,
WaitMode wait,
blink::mojom::LockRequestPtr request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (wait == WaitMode::PREEMPT && mode != LockMode::EXCLUSIVE) {
mojo::ReportBadMessage("Invalid option combination");
return;
}
if (name.length() > 0 && name[0] == '-') {
mojo::ReportBadMessage("Reserved name");
return;
}
const auto& context = bindings_.dispatch_context();
int64_t lock_id =
(wait == WaitMode::PREEMPT) ? kPreemptiveLockId : NextLockId();
if (wait == WaitMode::PREEMPT) {
Break(context.origin, name);
} else if (wait == WaitMode::NO_WAIT &&
!IsGrantable(context.origin, name, mode)) {
request->Failed();
return;
}
request.set_connection_error_handler(base::BindOnce(&LockManager::ReleaseLock,
base::Unretained(this),
context.origin, lock_id));
const Lock* lock = origins_[context.origin].AddRequest(
lock_id, name, mode, context.client_id, std::move(request));
ProcessRequests(context.origin);
DCHECK_GT(lock->lock_id(), kPreemptiveLockId)
<< "Preemptive lock should be assigned a new id";
#if DCHECK_IS_ON()
DCHECK(wait != WaitMode::PREEMPT ||
origins_[context.origin].IsHeld(lock->lock_id()))
<< "Preemptive lock should be granted immediately";
#endif
}
void LockManager::ReleaseLock(const url::Origin& origin, int64_t lock_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!base::ContainsKey(origins_, origin))
return;
OriginState& state = origins_[origin];
bool dirty = state.EraseLock(lock_id);
if (state.IsEmpty())
origins_.erase(origin);
else if (dirty)
ProcessRequests(origin);
}
void LockManager::QueryState(QueryStateCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const url::Origin& origin = bindings_.dispatch_context().origin;
if (!base::ContainsKey(origins_, origin)) {
std::move(callback).Run(std::vector<blink::mojom::LockInfoPtr>(),
std::vector<blink::mojom::LockInfoPtr>());
return;
}
OriginState& state = origins_[origin];
std::move(callback).Run(state.SnapshotRequested(), state.SnapshotHeld());
}
bool LockManager::IsGrantable(const url::Origin& origin,
const std::string& name,
LockMode mode) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = origins_.find(origin);
if (it == origins_.end())
return true;
return it->second.IsGrantable(name, mode);
}
int64_t LockManager::NextLockId() {
int64_t lock_id = ++next_lock_id_;
DCHECK_GT(lock_id, kPreemptiveLockId);
return lock_id;
}
void LockManager::Break(const url::Origin& origin, const std::string& name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = origins_.find(origin);
if (it != origins_.end())
it->second.Break(name);
}
void LockManager::ProcessRequests(const url::Origin& origin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!base::ContainsKey(origins_, origin))
return;
OriginState& state = origins_[origin];
DCHECK(!state.IsEmpty());
state.ProcessRequests(this, origin);
DCHECK(!state.IsEmpty());
}
} // namespace content