/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// HandleWatcher::Impl // // The backing implementation of HandleWatcher is a PTP_WAIT, an OS-threadpool // wait-object. Windows doesn't actually create a new thread per wait-object; // OS-threadpool threads are assigned to wait-objects only when their associated // handle become signaled -- although explicit documentation of this fact is // somewhat obscurely placed. [1] // // Throughout this class, we use manual locking and unlocking guarded by Clang's // thread-safety warnings, rather than scope-based lock-guards. See `Replace()` // for an explanation and justification. // // [1]https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects#remarks class HandleWatcher::Impl final : public nsITargetShutdownTask {
NS_DECL_THREADSAFE_ISUPPORTS
public:
Impl() = default;
private:
~Impl() { MOZ_ASSERT(IsStopped()); }
struct Data { // The watched handle and its callback.
HANDLE handle;
RefPtr<nsIEventTarget> target;
nsCOMPtr<nsIRunnable> runnable;
// Handle to the threadpool wait-object.
WaitHandlePtr waitHandle; // A pointer to ourselves, notionally owned by the wait-object.
RefPtr<Impl> self;
// (We can't actually do this because a) it has annoying consequences in // C++20 thanks to P1008R1, and b) Clang just ignores it anyway.) // // ~Data() MOZ_EXCLUDES(mMutex) = default;
};
mozilla::Mutex mMutex{"HandleWatcher::Impl"};
Data mData MOZ_GUARDED_BY(mMutex) = {};
// Callback from OS threadpool wait-object. staticvoid CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE, void* ctx,
PTP_WAIT aWaitHandle,
TP_WAIT_RESULT aResult) { static_cast<Impl*>(ctx)->OnWaitCompleted(aWaitHandle, aResult);
}
mMutex.Lock(); // If this callback is no longer the active callback, skip out. // All cleanup is someone else's problem. if (aWaitHandle != mData.waitHandle.get()) {
MOZ_LOG(sHWLog, LogLevel::Debug,
("Recv'd already-stopped callback: HW %p | PTP_WAIT %p", this,
aWaitHandle));
mMutex.Unlock(); return;
}
// Take our self-pointer so that we release it on exit.
RefPtr<Impl> self = std::move(mData.self);
// This may fail if (for example) `mData.target` is being shut down, but we // have not yet received the shutdown callback.
mData.target->Dispatch(mData.runnable.forget());
Replace(Data{});
}
// returns `void`; presumably always succeeds given a successful // `::CreateThreadpoolWait()`
::SetThreadpoolWait(waitHandle.get(), aHandle, nullptr); // After this point, you must call `FlushWaitHandle(waitHandle.get())` // before destroying the wait handle. (Note that this must be done while // *not* holding `mMutex`!)
// Clear mData.target, since there's no need to unregister the shutdown task // anymore. Hold onto it until we release the mutex, though, to avoid any // reentrancy issues. // // This is more for internal consistency than safety: someone has to be // shutting `target` down, and that someone isn't us, so there's necessarily // another reference out there. (Although decrementing the refcount might // still have arbitrary effects if someone's been excessively clever with // nsISupports::Release...) autoconst oldTarget = std::move(mData.target);
Replace(Data{}); // (Static-assert that the mutex has indeed been released.)
([&]() MOZ_EXCLUDES(mMutex) {})();
}
private: // Throughout this class, we use manual locking and unlocking guarded by // Clang's thread-safety warnings, rather than scope-based lock-guards. This // is largely driven by `Replace()`, below, which performs both operations // which require the mutex to be held and operations which require it to not // be held, and therefore must explicitly sequence the mutex release. // // These explicit locks, unlocks, and annotations are both alien to C++ and // offensively tedious; but they _are_ still checked for state consistency at // scope boundaries. (The concerned reader is invited to test this by // deliberately removing an `mMutex.Unlock()` call from anywhere in the class // and viewing the resultant compiler diagnostics.) // // A more principled, or at least differently-principled, implementation might // create a scope-based lock-guard and pass it to `Replace()` to dispose of at // the proper time. Alas, it cannot be communicated to Clang's thread-safety // checker that such a guard is associated with `mMutex`. // void Replace(Data&& aData) MOZ_CAPABILITY_RELEASE(mMutex) { // either both handles are NULL, or neither is
MOZ_ASSERT(!!aData.handle == !!aData.waitHandle);
if (mData.target) {
mData.target->UnregisterShutdownTask(this);
}
// Extract the old data and insert the new -- but hold onto the old data for // now. (See [1] and [2], below.)
Data oldData = std::exchange(mData, std::move(aData));
//////////////////////////////////////////////////////////////////////////// // Release the mutex.
mMutex.Unlock(); ////////////////////////////////////////////////////////////////////////////
// [1] `oldData.self` will be unset if the old callback already ran (or if // there was no old callback in the first place). If it's set, though, we // need to explicitly clear out the wait-object first. if (oldData.self) {
MOZ_ASSERT(oldData.waitHandle);
FlushWaitHandle(oldData.waitHandle.get());
}
// [2] oldData also includes several other reference-counted pointers. It's // possible that these may be the last pointer to something, so releasing // them may have arbitrary side-effects -- like calling this->Stop(), which // will try to reacquire the mutex. // // Now that we've released the mutex, we can (implicitly) release them all // here.
}
// Either confirm as complete or cancel any callbacks on aWaitHandle. Block // until this is done. (See documentation for ::CloseThreadpoolWait().) void FlushWaitHandle(PTP_WAIT aWaitHandle) MOZ_EXCLUDES(mMutex) {
::SetThreadpoolWait(aWaitHandle, nullptr, nullptr); // This might block on `OnWaitCompleted()`, so we can't hold `mMutex` here.
::WaitForThreadpoolWaitCallbacks(aWaitHandle, TRUE); // ::CloseThreadpoolWait() itself is the caller's responsibility.
}
};
////// // HandleWatcher member function implementations
HandleWatcher::HandleWatcher() : mImpl{} {}
HandleWatcher::~HandleWatcher() { if (mImpl) {
MOZ_ASSERT(mImpl->IsStopped());
mImpl->Stop(); // just in case, in release
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.