
Use SequenceChecker::EnableStackLogging() to enable this logging. When DCHECK() fails for a ThreadChecker or SequenceChecker, if the error is not at the site of checking, it takes a great amount of knowledge and persistence to figure out where the erroroneous thread/sequence- attachment occurred. This is especially fun for a WeakPtr, which if checked on the wrong thread becomes bound to the thread. Then if the WeakPtrFactory is invalidated later, it's difficult to determine why it believes it is on the wrong thread. This adds a copy of the StackTrace in SequenceChecker and ThreadChecker that is set whenever they are bound to a thread/sequence. Then on failure the DCHECK() callers will get back a copy of the StackTrace and include it in the log message. WeakPtr calls the SequenceChecker manually instead of using the macros, since it has an additional condition so we also add the StackTrace to the message there. The StackTrace logging slows things down a bit, enough that we'd slow bots down without good reason, so the logging is optional. You may use SequenceChecker::EnableStackLogging() to enable this logging, or the equivalent alias ThreadChecker::EnableStackLogging(). Example output with stack logging enabled: [890744:1:1201/174351.878197:FATAL:weak_ptr.cc(23)] Check failed: sequence_checker_.CalledOnValidSequence(&bound_at) || HasOneRef(). WeakPtrs must be invalidated on the same sequenced thread as where they are bound. This was bound at: #0 0x7fd813c8d369 base::debug::CollectStackTrace() #1 0x7fd813b94103 base::debug::StackTrace::StackTrace() #2 0x7fd813c70aef base::ThreadCheckerImpl::ThreadCheckerImpl() #3 0x7fd813c02155 base::SequenceCheckerImpl::CalledOnValidSequence() #4 0x7fd813ba4f55 base::ScopedValidateSequenceChecker::ScopedValidateSequenceChecker() #5 0x7fd813bbfa4f base::internal::WeakReference::IsValid() #6 0x7fd80acd0f36 blink::FrameWidgetInputHandlerImpl::FrameWidgetInputHandlerImpl() [...] Check failed at: #0 0x7fd813c8d369 base::debug::CollectStackTrace() #1 0x7fd813b94103 base::debug::StackTrace::StackTrace() #2 0x7fd813bb3c53 logging::LogMessage::~LogMessage() #3 0x7fd813bb45ae logging::LogMessage::~LogMessage() #4 0x7fd813bbf91c base::internal::WeakReference::Flag::Invalidate() #5 0x7fd813bbfc8c base::internal::WeakReferenceOwner::Invalidate() #6 0x7fd80ca6eda7 blink::WebFrameWidgetImpl::Close() #7 0x7fd80ff22359 content::RenderWidget::Close() #8 0x7fd80ff21eff content::RenderWidget::CloseForFrame() [...] R=thakis@chromium.org Change-Id: Id89c12eaedfba9788b3cd2624447c6a1616f0afa Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2568446 Commit-Queue: danakj <danakj@chromium.org> Reviewed-by: Gabriel Charette <gab@chromium.org> Reviewed-by: Nico Weber <thakis@chromium.org> Cr-Commit-Position: refs/heads/master@{#847805}
108 lines
3.5 KiB
C++
108 lines
3.5 KiB
C++
// Copyright (c) 2012 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 "base/sequence_checker_impl.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "base/check.h"
|
|
#include "base/debug/stack_trace.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/sequence_token.h"
|
|
#include "base/threading/thread_checker.h"
|
|
#include "base/threading/thread_checker_impl.h"
|
|
#include "base/threading/thread_local_storage.h"
|
|
|
|
namespace base {
|
|
|
|
// static
|
|
void SequenceCheckerImpl::EnableStackLogging() {
|
|
ThreadChecker::EnableStackLogging();
|
|
}
|
|
|
|
class SequenceCheckerImpl::Core {
|
|
public:
|
|
Core() : sequence_token_(SequenceToken::GetForCurrentThread()) {}
|
|
|
|
~Core() = default;
|
|
|
|
bool CalledOnValidSequence(
|
|
std::unique_ptr<debug::StackTrace>* out_bound_at) const {
|
|
// SequenceToken::GetForCurrentThread() accesses thread-local storage.
|
|
// During destruction the state of thread-local storage is not guaranteed to
|
|
// be in a consistent state. Further, task-runner only installs the
|
|
// SequenceToken when running a task. For this reason, |sequence_token_| is
|
|
// not checked during thread destruction.
|
|
if (!SequenceCheckerImpl::HasThreadLocalStorageBeenDestroyed() &&
|
|
sequence_token_.IsValid()) {
|
|
if (sequence_token_ != SequenceToken::GetForCurrentThread()) {
|
|
if (out_bound_at)
|
|
*out_bound_at = thread_checker_.GetBoundAt();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// SequenceChecker behaves as a ThreadChecker when it is not bound to a
|
|
// valid sequence token.
|
|
return thread_checker_.CalledOnValidThread(out_bound_at);
|
|
}
|
|
|
|
private:
|
|
SequenceToken sequence_token_{SequenceToken::GetForCurrentThread()};
|
|
|
|
// Used when |sequence_token_| is invalid, or during thread destruction.
|
|
ThreadCheckerImpl thread_checker_;
|
|
};
|
|
|
|
SequenceCheckerImpl::SequenceCheckerImpl() : core_(std::make_unique<Core>()) {}
|
|
SequenceCheckerImpl::~SequenceCheckerImpl() = default;
|
|
|
|
SequenceCheckerImpl::SequenceCheckerImpl(SequenceCheckerImpl&& other) {
|
|
// Verify that |other| is called on its associated sequence and bind it now if
|
|
// it is currently detached (even if this isn't a DCHECK build).
|
|
const bool other_called_on_valid_sequence = other.CalledOnValidSequence();
|
|
DCHECK(other_called_on_valid_sequence);
|
|
|
|
core_ = std::move(other.core_);
|
|
}
|
|
|
|
SequenceCheckerImpl& SequenceCheckerImpl::operator=(
|
|
SequenceCheckerImpl&& other) {
|
|
// If |this| is not in a detached state it needs to be bound to the current
|
|
// sequence.
|
|
DCHECK(CalledOnValidSequence());
|
|
|
|
// Verify that |other| is called on its associated sequence and bind it now if
|
|
// it is currently detached (even if this isn't a DCHECK build).
|
|
const bool other_called_on_valid_sequence = other.CalledOnValidSequence();
|
|
DCHECK(other_called_on_valid_sequence);
|
|
|
|
// Intentionally not using either |lock_| in this method to let TSAN catch
|
|
// racy assign.
|
|
TS_UNCHECKED_READ(core_) = std::move(TS_UNCHECKED_READ(other.core_));
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool SequenceCheckerImpl::CalledOnValidSequence(
|
|
std::unique_ptr<debug::StackTrace>* bound_at) const {
|
|
AutoLock auto_lock(lock_);
|
|
if (!core_)
|
|
core_ = std::make_unique<Core>();
|
|
return core_->CalledOnValidSequence(bound_at);
|
|
}
|
|
|
|
void SequenceCheckerImpl::DetachFromSequence() {
|
|
AutoLock auto_lock(lock_);
|
|
core_.reset();
|
|
}
|
|
|
|
// static
|
|
bool SequenceCheckerImpl::HasThreadLocalStorageBeenDestroyed() {
|
|
return ThreadLocalStorage::HasBeenDestroyed();
|
|
}
|
|
|
|
} // namespace base
|