blob: aee08a47224dc143e76317414748449ecd675ae6 [file] [log] [blame]
/*
* Copyright 2013 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: morlovich@google.com (Maksim Orlovich)
#ifndef PAGESPEED_KERNEL_UTIL_CATEGORIZED_REFCOUNT_H_
#define PAGESPEED_KERNEL_UTIL_CATEGORIZED_REFCOUNT_H_
#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
namespace net_instaweb {
/* This class helps manage a reference count stored in an object where
* references can be classified into separate types, to further check their use
* and help in debugging. You would normally store an instance of
* CategorizedRefcount in the object being managed.
*
* There are the following requirements on ObjectType:
* void LastRefRemoved(); // called when refcount goes to 0, with
* // mutex_ held.
* StringPiece RefCategoryName(EnumType);
* const int kNumRefCategories which bounds the EnumType
*
* For example, you might have something like this (omitting RefCategoryName
* implementation, which is only needed for DebugString()):
*
* class AsyncDoerOfThings() {
* public:
* AsyncDoerOfThings() : ref_counts_(this) {
* ...
* ref_counts_.set_mutex(mutex_.get());
* }
*
* void ref() { ref_counts_.AddRef(kRefExternal); }
* void deref() { ref_counts_.ReleaseRef(kRefExternal); }
* void AsyncOp() {
* ref_counts_.AddRef(kRefInternal);
* DoSomeRpcOp(this, &AsyncDoerOfThings::AsyncOpComplete);
* }
*
* private:
* void AsyncOpComplete() { ref_counts_.ReleaseRef(kRefInternal); }
* void LastRefRemoved() { delete this; }
*
* enum RefCategory {
* kRefExternal,
* kRefInternal,
* kNumRefCategories
* };
* friend class CategorizedRefcount<AsyncDoerOfThings, RefCategory>;
* CategorizedRefcount<AsyncDoerOfThings, RefCategory> ref_counts_;
*
* };
*
* TODO(morlovich): Consider having a cap per kind, too? Some are meant to be
* 0-1 only.
*/
template<typename ObjectType, typename EnumType>
class CategorizedRefcount {
public:
// Note: set_mutex must be called before calling any other method on this
// class.
// TODO(jud): Instead of holding the mutex in this class, pass in the mutex to
// each function so that thread safety annotation can be used.
explicit CategorizedRefcount(ObjectType* object)
: total_refcount_(0), object_(object), mutex_(NULL) {
for (int i = 0; i < ObjectType::kNumRefCategories; ++i) {
ref_counts_[i] = 0;
}
}
// Sets the mutex that should be held when manipulating reference count
// of this object. Does not take ownership.
void set_mutex(AbstractMutex* mutex) {
mutex_ = mutex;
}
void AddRef(EnumType category) {
ScopedMutex hold(mutex_);
AddRefMutexHeld(category);
}
void AddRefMutexHeld(EnumType category) {
mutex_->DCheckLocked();
DCHECK_LE(0, category);
DCHECK_LT(category, ObjectType::kNumRefCategories);
++ref_counts_[category];
++total_refcount_;
}
void ReleaseRef(EnumType category) {
ScopedMutex hold(mutex_);
ReleaseRefMutexHeld(category);
}
void ReleaseRefMutexHeld(EnumType category) {
mutex_->DCheckLocked();
DCHECK_LE(0, category);
DCHECK_LT(category, ObjectType::kNumRefCategories);
--ref_counts_[category];
DCHECK_GE(ref_counts_[category], 0) << category;
--total_refcount_;
if (total_refcount_ == 0) {
object_->LastRefRemoved();
}
}
// QueryCount w/o mutex held externally makes no sense, since there
// would be no way of using the data.
int QueryCountMutexHeld(EnumType category) const {
return ref_counts_[category];
}
GoogleString DebugString() const {
ScopedMutex hold(mutex_);
return DebugStringMutexHeld();
}
GoogleString DebugStringMutexHeld() const {
mutex_->DCheckLocked();
GoogleString out;
for (int i = 0; i < ObjectType::kNumRefCategories; ++i) {
StrAppend(&out, "\t", object_->RefCategoryName(static_cast<EnumType>(i)),
": ", IntegerToString(ref_counts_[i]));
}
return out;
}
void DCheckAllCountsZero() {
ScopedMutex hold(mutex_);
DCheckAllCountsZeroMutexHeld();
}
void DCheckAllCountsZeroMutexHeld() {
mutex_->DCheckLocked();
DCHECK_EQ(0, total_refcount_);
for (int i = 0; i < ObjectType::kNumRefCategories; ++i) {
DCHECK_EQ(0, ref_counts_[i]);
}
}
private:
int ref_counts_[ObjectType::kNumRefCategories];
int total_refcount_;
ObjectType* object_;
AbstractMutex* mutex_;
DISALLOW_COPY_AND_ASSIGN(CategorizedRefcount);
};
} // namespace net_instaweb
#endif // PAGESPEED_KERNEL_UTIL_CATEGORIZED_REFCOUNT_H_