blob: 622947570bde01fa88852358118c49431bef538e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
#include "pagespeed/kernel/util/mem_lock.h"
#include <algorithm>
#include "base/logging.h"
#include "pagespeed/kernel/base/function.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/util/mem_lock_manager.h"
#include "pagespeed/kernel/util/mem_lock_state.h"
namespace net_instaweb {
MemLock::MemLock(int64 sequence, MemLockState* lock_state)
: lock_state_(lock_state),
sequence_(sequence) {
Clear();
}
MemLock::~MemLock() {
if (IsPending()) {
Deny();
} else if (Held()) {
lock_state_->Unlock();
}
lock_state_->RemoveLock(this);
}
void MemLock::Clear() {
DCHECK(!lock_state_->IsHeldInOrderedSet(this));
callback_ = NULL;
cancel_time_ms_ = 0;
steal_ms_ = 0;
wakeup_time_ms_ = kNotPending;
grant_time_ms_ = kNotHeld;
}
// Return immediately. Wait wait_ms to take lock, invoke callback with lock
// held. On timeout, cancel callback.
void MemLock::LockTimedWait(int64 wait_ms, Function* callback) {
LockTimedWaitStealOld(wait_ms, kDoNotSteal, callback);
}
void MemLock::LockTimedWaitStealOld(int64 wait_ms, int64 steal_ms,
Function* callback) {
if (Held() || IsPending()) {
LOG(DFATAL) << "Requesting lock " << name() << " when it's already "
<< (IsPending() ? "pending" : "held");
callback->CallCancel();
} else if (lock_state_->GrabLock(this)) {
grant_time_ms_ = lock_state_->manager()->timer()->NowMs();
callback->CallRun();
} else {
DCHECK(!lock_state_->IsHeldInOrderedSet(this));
CHECK(callback_ == NULL);
cancel_time_ms_ = lock_state_->manager()->timer()->NowMs() + wait_ms;
steal_ms_ = steal_ms;
callback_ = callback;
lock_state_->ScheduleLock(this);
}
}
void MemLock::Wakeup() {
if (ShouldCancelOnWakeup()) {
Deny();
} else {
CHECK(CanSteal());
lock_state_->StealLock(this);
}
}
GoogleString MemLock::name() const {
return lock_state_->name();
}
void MemLock::CalculateWakeupTime(int64 held_lock_grant_time_ms) {
DCHECK(!lock_state_->IsHeldInOrderedSet(this));
if (!CanSteal() || (held_lock_grant_time_ms == kNotHeld)) {
wakeup_time_ms_ = cancel_time_ms_;
} else {
int64 steal_time_ms = steal_ms_ + held_lock_grant_time_ms;
wakeup_time_ms_ = std::min(cancel_time_ms_, steal_time_ms);
}
}
void MemLock::Unlock() {
// Locks can be stolen from the holder without notifying the owner,
// so it is not considered an error to try to unlock a NamedLock that
// is not held.
if (Held()) {
lock_state_->Unlock();
// "this" may have been freed by this point.
}
}
void MemLock::Grant(int64 grant_time_ms) {
lock_state_->UnscheduleLock(this);
Function* callback = callback_;
CHECK(!Held());
CHECK(IsPending());
Clear();
grant_time_ms_ = grant_time_ms;
CHECK(callback);
callback->CallRun();
}
void MemLock::Deny() {
lock_state_->UnscheduleLock(this);
Function* callback = callback_;
CHECK(IsPending());
CHECK(!Held());
Clear();
CHECK(callback);
callback->CallCancel();
}
// Computes a stable ordering for multiple locks with the same
// time-based criteria based primarily on the name, and secondarily on
// the sequence number.
int MemLock::StableCompare(const MemLock* that) const {
int cmp = 0;
if (lock_state_ == that->lock_state_) {
cmp = MemLockState::Compare(sequence_, that->sequence_);
} else {
cmp = MemLockState::Compare(lock_state_->name(), that->lock_state_->name());
}
// Note that if we don't get a strict ordering here between two
// different locks we will lose one of them in the map.
DCHECK_NE(0, cmp);
return cmp;
}
} // namespace net_instaweb