blob: d55ccafc7edcb567d154ccb8e7af780e957f0278 [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.
//
// Portions (c) 2011 The Chromium Authors.
#include "kudu/util/mutex.h"
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <ostream>
#include <string>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "kudu/gutil/port.h"
#include "kudu/gutil/strings/substitute.h"
#include "kudu/gutil/walltime.h"
#include "kudu/util/debug-util.h"
#include "kudu/util/env.h"
#include "kudu/util/flag_tags.h"
#include "kudu/util/trace.h"
using std::string;
using strings::Substitute;
using strings::SubstituteAndAppend;
#ifndef NDEBUG
DEFINE_bool(debug_mutex_collect_stacktrace, false,
"Whether to collect a stacktrace on Mutex contention in a DEBUG build");
TAG_FLAG(debug_mutex_collect_stacktrace, advanced);
TAG_FLAG(debug_mutex_collect_stacktrace, hidden);
#endif
namespace kudu {
Mutex::Mutex()
#ifndef NDEBUG
: owning_tid_(0),
stack_trace_(new StackTrace())
#endif
{
#ifndef NDEBUG
// In debug, setup attributes for lock error checking.
pthread_mutexattr_t mta;
int rv = pthread_mutexattr_init(&mta);
DCHECK_EQ(0, rv) << ". " << strerror(rv);
rv = pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_ERRORCHECK);
DCHECK_EQ(0, rv) << ". " << strerror(rv);
rv = pthread_mutex_init(&native_handle_, &mta);
DCHECK_EQ(0, rv) << ". " << strerror(rv);
rv = pthread_mutexattr_destroy(&mta);
DCHECK_EQ(0, rv) << ". " << strerror(rv);
#else
// In release, go with the default lock attributes.
pthread_mutex_init(&native_handle_, NULL);
#endif
}
Mutex::~Mutex() {
int rv = pthread_mutex_destroy(&native_handle_);
DCHECK_EQ(0, rv) << ". " << strerror(rv);
}
bool Mutex::TryAcquire() {
int rv = pthread_mutex_trylock(&native_handle_);
#ifndef NDEBUG
DCHECK(rv == 0 || rv == EBUSY) << ". " << strerror(rv) << ". " << GetOwnerThreadInfo();
if (rv == 0) {
CheckUnheldAndMark();
}
#endif
return rv == 0;
}
void Mutex::Acquire() {
// Optimize for the case when mutexes are uncontended. If they
// are contended, we'll have to go to sleep anyway, so the extra
// cost of branch mispredictions is moot.
//
// TryAcquire() is implemented as a simple CompareAndSwap inside
// pthreads so this does not require a system call.
if (PREDICT_TRUE(TryAcquire())) {
return;
}
// If we weren't able to acquire the mutex immediately, then it's
// worth gathering timing information about the mutex acquisition.
MicrosecondsInt64 start_time = GetMonoTimeMicros();
int rv = pthread_mutex_lock(&native_handle_);
DCHECK_EQ(0, rv) << ". " << strerror(rv)
#ifndef NDEBUG
<< ". " << GetOwnerThreadInfo()
#endif
; // NOLINT(whitespace/semicolon)
MicrosecondsInt64 end_time = GetMonoTimeMicros();
int64_t wait_time = end_time - start_time;
if (wait_time > 0) {
TRACE_COUNTER_INCREMENT("mutex_wait_us", wait_time);
}
#ifndef NDEBUG
CheckUnheldAndMark();
#endif
}
void Mutex::Release() {
#ifndef NDEBUG
CheckHeldAndUnmark();
#endif
int rv = pthread_mutex_unlock(&native_handle_);
DCHECK_EQ(0, rv) << ". " << strerror(rv);
}
#ifndef NDEBUG
void Mutex::AssertAcquired() const {
DCHECK_EQ(Env::Default()->gettid(), owning_tid_);
}
void Mutex::CheckHeldAndUnmark() {
AssertAcquired();
owning_tid_ = 0;
if (FLAGS_debug_mutex_collect_stacktrace) {
stack_trace_->Reset();
}
}
void Mutex::CheckUnheldAndMark() {
DCHECK_EQ(0, owning_tid_);
owning_tid_ = Env::Default()->gettid();
if (FLAGS_debug_mutex_collect_stacktrace) {
stack_trace_->Collect();
}
}
string Mutex::GetOwnerThreadInfo() const {
string str = Substitute("Owner tid: $0; Self tid: $1; ", owning_tid_, Env::Default()->gettid());
if (FLAGS_debug_mutex_collect_stacktrace) {
SubstituteAndAppend(&str, "Owner stack:\n$0", stack_trace_->Symbolize());
} else {
str += "To collect the owner stack trace, enable the flag --debug_mutex_collect_stacktrace";
}
return str;
}
#endif
} // namespace kudu