blob: 150494159eed47f3905cda979582022f5c2674a6 [file] [log] [blame]
// Copyright 2015 Google Inc.
//
// 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.
//
// Author: cheesy@google.com (Steve Hill)
#include "pagespeed/controller/named_lock_schedule_rewrite_controller.h"
#include <cstddef>
#include "base/logging.h"
#include "pagespeed/kernel/base/stl_util.h"
namespace net_instaweb {
const char NamedLockScheduleRewriteController::kLocksGranted[] =
"named-lock-rewrite-scheduler-granted";
const char NamedLockScheduleRewriteController::kLocksDenied[] =
"named-lock-rewrite-scheduler-denied";
const char NamedLockScheduleRewriteController::kLocksStolen[] =
"named-lock-rewrite-scheduler-stolen";
const char NamedLockScheduleRewriteController::kLocksReleasedWhenNotHeld[] =
"named-lock-rewrite-scheduler-released-not-held";
const char NamedLockScheduleRewriteController::kLocksCurrentlyHeld[] =
"named-lock-rewrite-scheduler-locks-held";
const int NamedLockScheduleRewriteController::kStealMs = 30000;
NamedLockScheduleRewriteController::NamedLockScheduleRewriteController(
NamedLockManager* lock_manager, ThreadSystem* thread_system,
Statistics* stats)
: mutex_(thread_system->NewMutex()),
lock_manager_(lock_manager),
shut_down_(false),
locks_granted_(stats->GetTimedVariable(kLocksGranted)),
locks_denied_(stats->GetTimedVariable(kLocksDenied)),
locks_stolen_(stats->GetTimedVariable(kLocksStolen)),
locks_released_when_not_held_(
stats->GetTimedVariable(kLocksReleasedWhenNotHeld)),
locks_currently_held_(stats->GetUpDownCounter(kLocksCurrentlyHeld)) {
}
NamedLockScheduleRewriteController::~NamedLockScheduleRewriteController() {
// We shouldn't actually have any locks held, but free any that are.
DCHECK(locks_.empty());
STLDeleteValues(&locks_);
}
void NamedLockScheduleRewriteController::InitStats(Statistics* statistics) {
statistics->AddTimedVariable(kLocksGranted, Statistics::kDefaultGroup);
statistics->AddTimedVariable(kLocksDenied, Statistics::kDefaultGroup);
statistics->AddTimedVariable(kLocksStolen, Statistics::kDefaultGroup);
statistics->AddTimedVariable(kLocksReleasedWhenNotHeld,
Statistics::kDefaultGroup);
statistics->AddUpDownCounter(kLocksCurrentlyHeld);
}
NamedLockScheduleRewriteController::LockInfo*
NamedLockScheduleRewriteController::GetLockInfo(const GoogleString& key) {
LockInfo*& info = locks_[key];
if (info == NULL) {
info = new LockInfo();
}
DCHECK_GE(info->pin_count, 0);
return info;
}
void NamedLockScheduleRewriteController::DeleteInfoIfUnused(
LockInfo* info, const GoogleString& key) {
DCHECK_GE(info->pin_count, 0);
if (info->lock.get() == nullptr && info->pin_count <= 0
&& info->pending_callbacks.empty()) {
size_t num_erased = locks_.erase(key);
CHECK_EQ(1, num_erased);
delete info;
}
}
void NamedLockScheduleRewriteController::LockObtained(Function* callback,
const GoogleString key,
NamedLock* named_lock) {
locks_granted_->IncBy(1);
locks_currently_held_->Add(1);
bool shut_down;
{
ScopedMutex mutex_lock(mutex_.get());
shut_down = shut_down_;
LockInfo* info = GetLockInfo(key);
// This lock may have been held by someone else, but it isn't any more!
if (info->lock.get() != NULL) {
locks_stolen_->IncBy(1);
locks_currently_held_->Add(-1);
}
// This function may delete a lock thats in the middle of being stolen.
// Your NamedLock implementation must support that, see
// NamedLockTester::StealWithDelete (and UnlockWithDelete).
info->lock.reset(named_lock);
info->pending_callbacks.erase(callback);
// No point calling DeleteInfoIfUnused since we know the lock is held.
}
// After ::ShutDown, we are no longer responsible for invoking callbacks.
if (!shut_down) {
callback->CallRun();
}
}
void NamedLockScheduleRewriteController::LockFailed(Function* callback,
const GoogleString key,
NamedLock* named_lock) {
locks_denied_->IncBy(1);
bool shut_down;
{
ScopedMutex mutex_lock(mutex_.get());
shut_down = shut_down_;
// named_lock is not actually held, so just delete it.
delete named_lock;
LockInfo* info = GetLockInfo(key);
info->pending_callbacks.erase(callback);
DeleteInfoIfUnused(info, key);
}
// After ::ShutDown, we are no longer responsible for invoking callbacks.
if (!shut_down) {
callback->CallCancel();
}
}
void NamedLockScheduleRewriteController::ScheduleRewrite(
const GoogleString& key, Function* callback) {
bool shut_down = false;
{
ScopedMutex mutex_lock(mutex_.get());
shut_down = shut_down_;
if (!shut_down) {
LockInfo* info = GetLockInfo(key);
info->pending_callbacks.insert(callback);
// Don't need DeleteInfoIfUnused() since pending_callbacks is non-empty.
}
}
if (shut_down) {
callback->CallCancel();
return;
}
NamedLock* named_lock = lock_manager_->CreateNamedLock(key);
// We can't call this with a lock on mutex_ since it may call back
// synchronously, which would deadlock.
named_lock->LockTimedWaitStealOld(
0 /* wait_ms */, kStealMs,
MakeFunction<NamedLockScheduleRewriteController, Function*,
const GoogleString, NamedLock*>(
this,
&NamedLockScheduleRewriteController::LockObtained,
&NamedLockScheduleRewriteController::LockFailed,
callback, key, named_lock));
}
void NamedLockScheduleRewriteController::NotifyRewriteComplete(
const GoogleString& key) {
// Because of lock stealing, this has the unfortunate property that if an
// operation completes after the steal deadline, it will release someone
// else's lock. Given that this is expected to be unlikely and the worst case
// is redundant work, it shouldn't matter too much.
LockInfo* info;
scoped_ptr<NamedLock> named_lock;
{
ScopedMutex mutex_lock(mutex_.get());
info = GetLockInfo(key);
// The lock might not actually be held if it was stolen and then released.
if (info->lock.get() == NULL) {
locks_released_when_not_held_->IncBy(1);
DeleteInfoIfUnused(info, key);
return;
}
++info->pin_count;
// Steal the pointer out of the info, which marks the info as not locked.
named_lock.reset(info->lock.release());
}
// Unlock could theoretically call back synchronously into one of our other
// routines, so we must not hold mutex_ when unlocking. Holding "info" outside
// the mutex is perfectly safe because we prevented it from being deleted by
// incrementing pin_count above.
locks_currently_held_->Add(-1);
named_lock->Unlock();
{
ScopedMutex mutex_lock(mutex_.get());
--info->pin_count;
// Note that a callback from Unlock() may have re-acquired the lock.
DeleteInfoIfUnused(info, key);
}
}
void NamedLockScheduleRewriteController::NotifyRewriteFailed(
const GoogleString& key) {
// This implemenation doesn't have special failure handling, so just call
// Complete.
this->NotifyRewriteComplete(key);
}
void NamedLockScheduleRewriteController::ShutDown() {
// After ShutDown, all existing callbacks will be cancelled, requests for
// scheduling will immediately be responded to with Cancel(), and the usual
// codepath will no longer involve callbacks.
std::vector<Function*> callbacks;
{
ScopedMutex mutex_lock(mutex_.get());
shut_down_ = true;
for (const auto& p : locks_) {
for (Function* f : p.second->pending_callbacks) {
callbacks.push_back(f);
}
}
}
for (Function* f : callbacks) {
f->CallCancel();
}
}
} // namespace net_instaweb