// 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 #include #include #include #include #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 Create( base::WeakPtr context, const url::Origin& origin, int64_t lock_id, blink::mojom::LockHandlePtr* ptr) { return mojo::MakeStrongBinding( std::make_unique(std::move(context), origin, lock_id), mojo::MakeRequest(ptr)); } LockHandleImpl(base::WeakPtr 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 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 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(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 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(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 = 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 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 SnapshotRequested() const { std::vector 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 SnapshotHeld() const { std::vector 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> requested_; std::map> 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 shared_; std::unordered_set 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(), std::vector()); 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