Bug 1867190 - Initialise the PHC allocate delay later r=glandium
[gecko.git] / xpcom / threads / WinHandleWatcher.cpp
blobd4759939258cd150b1457e1ba5c403da6e3acd04
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <windows.h>
8 #include <threadpoolapiset.h>
10 #include "mozilla/AlreadyAddRefed.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/Mutex.h"
14 #include "mozilla/RefPtr.h"
15 #include "mozilla/ThreadSafety.h"
16 #include "mozilla/WinHandleWatcher.h"
18 #include "nsCOMPtr.h"
19 #include "nsIRunnable.h"
20 #include "nsISerialEventTarget.h"
21 #include "nsISupportsImpl.h"
22 #include "nsITargetShutdownTask.h"
23 #include "nsIWeakReferenceUtils.h"
24 #include "nsThreadUtils.h"
26 mozilla::LazyLogModule sHWLog("HandleWatcher");
28 namespace mozilla {
29 namespace details {
30 struct WaitHandleDeleter {
31 void operator()(PTP_WAIT waitHandle) {
32 MOZ_LOG(sHWLog, LogLevel::Debug, ("Closing PTP_WAIT %p", waitHandle));
33 ::CloseThreadpoolWait(waitHandle);
36 } // namespace details
37 using WaitHandlePtr = UniquePtr<TP_WAIT, details::WaitHandleDeleter>;
39 // HandleWatcher::Impl
41 // The backing implementation of HandleWatcher is a PTP_WAIT, an OS-threadpool
42 // wait-object. Windows doesn't actually create a new thread per wait-object;
43 // OS-threadpool threads are assigned to wait-objects only when their associated
44 // handle become signaled -- although explicit documentation of this fact is
45 // somewhat obscurely placed. [1]
47 // Throughout this class, we use manual locking and unlocking guarded by Clang's
48 // thread-safety warnings, rather than scope-based lock-guards. See `Replace()`
49 // for an explanation and justification.
51 // [1]https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects#remarks
52 class HandleWatcher::Impl final : public nsITargetShutdownTask {
53 NS_DECL_THREADSAFE_ISUPPORTS
55 public:
56 Impl() = default;
58 private:
59 ~Impl() { MOZ_ASSERT(IsStopped()); }
61 struct Data {
62 // The watched handle and its callback.
63 HANDLE handle;
64 RefPtr<nsIEventTarget> target;
65 nsCOMPtr<nsIRunnable> runnable;
67 // Handle to the threadpool wait-object.
68 WaitHandlePtr waitHandle;
69 // A pointer to ourselves, notionally owned by the wait-object.
70 RefPtr<Impl> self;
72 // (We can't actually do this because a) it has annoying consequences in
73 // C++20 thanks to P1008R1, and b) Clang just ignores it anyway.)
75 // ~Data() MOZ_EXCLUDES(mMutex) = default;
78 mozilla::Mutex mMutex{"HandleWatcher::Impl"};
79 Data mData MOZ_GUARDED_BY(mMutex) = {};
81 // Callback from OS threadpool wait-object.
82 static void CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE, void* ctx,
83 PTP_WAIT aWaitHandle,
84 TP_WAIT_RESULT aResult) {
85 static_cast<Impl*>(ctx)->OnWaitCompleted(aWaitHandle, aResult);
88 void OnWaitCompleted(PTP_WAIT aWaitHandle, TP_WAIT_RESULT aResult)
89 MOZ_EXCLUDES(mMutex) {
90 MOZ_ASSERT(aResult == WAIT_OBJECT_0);
92 mMutex.Lock();
93 // If this callback is no longer the active callback, skip out.
94 // All cleanup is someone else's problem.
95 if (aWaitHandle != mData.waitHandle.get()) {
96 MOZ_LOG(sHWLog, LogLevel::Debug,
97 ("Recv'd already-stopped callback: HW %p | PTP_WAIT %p", this,
98 aWaitHandle));
99 mMutex.Unlock();
100 return;
103 // Take our self-pointer so that we release it on exit.
104 RefPtr<Impl> self = std::move(mData.self);
106 MOZ_LOG(sHWLog, LogLevel::Info,
107 ("Recv'd callback: HW %p | handle %p | target %p | PTP_WAIT %p",
108 this, mData.handle, mData.target.get(), aWaitHandle));
110 // This may fail if (for example) `mData.target` is being shut down, but we
111 // have not yet received the shutdown callback.
112 mData.target->Dispatch(mData.runnable.forget());
113 Replace(Data{});
116 public:
117 static RefPtr<Impl> Create(HANDLE aHandle, nsIEventTarget* aTarget,
118 already_AddRefed<nsIRunnable> aRunnable) {
119 auto impl = MakeRefPtr<Impl>();
120 bool const ok [[maybe_unused]] =
121 impl->Watch(aHandle, aTarget, std::move(aRunnable));
122 MOZ_ASSERT(ok);
123 return impl;
126 private:
127 bool Watch(HANDLE aHandle, nsIEventTarget* aTarget,
128 already_AddRefed<nsIRunnable> aRunnable) MOZ_EXCLUDES(mMutex) {
129 MOZ_ASSERT(aHandle);
130 MOZ_ASSERT(aTarget);
132 RefPtr<nsIEventTarget> target(aTarget);
134 WaitHandlePtr waitHandle{
135 ::CreateThreadpoolWait(&WaitCallback, this, nullptr)};
136 if (!waitHandle) {
137 return false;
141 mMutex.Lock();
143 nsresult const ret = aTarget->RegisterShutdownTask(this);
144 if (NS_FAILED(ret)) {
145 mMutex.Unlock();
146 return false;
149 MOZ_LOG(sHWLog, LogLevel::Info,
150 ("Setting callback: HW %p | handle %p | target %p | PTP_WAIT %p",
151 this, aHandle, aTarget, waitHandle.get()));
153 // returns `void`; presumably always succeeds given a successful
154 // `::CreateThreadpoolWait()`
155 ::SetThreadpoolWait(waitHandle.get(), aHandle, nullptr);
156 // After this point, you must call `FlushWaitHandle(waitHandle.get())`
157 // before destroying the wait handle. (Note that this must be done while
158 // *not* holding `mMutex`!)
160 Replace(Data{.handle = aHandle,
161 .target = std::move(target),
162 .runnable = aRunnable,
163 .waitHandle = std::move(waitHandle),
164 .self = this});
167 return true;
170 void TargetShutdown() MOZ_EXCLUDES(mMutex) override final {
171 mMutex.Lock();
173 MOZ_LOG(sHWLog, LogLevel::Debug,
174 ("Target shutdown: HW %p | handle %p | target %p | PTP_WAIT %p",
175 this, mData.handle, mData.target.get(), mData.waitHandle.get()));
177 // Clear mData.target, since there's no need to unregister the shutdown task
178 // anymore. Hold onto it until we release the mutex, though, to avoid any
179 // reentrancy issues.
181 // This is more for internal consistency than safety: someone has to be
182 // shutting `target` down, and that someone isn't us, so there's necessarily
183 // another reference out there. (Although decrementing the refcount might
184 // still have arbitrary effects if someone's been excessively clever with
185 // nsISupports::Release...)
186 auto const oldTarget = std::move(mData.target);
187 Replace(Data{});
188 // (Static-assert that the mutex has indeed been released.)
189 ([&]() MOZ_EXCLUDES(mMutex) {})();
192 public:
193 void Stop() MOZ_EXCLUDES(mMutex) {
194 mMutex.Lock();
195 Replace(Data{});
198 bool IsStopped() MOZ_EXCLUDES(mMutex) {
199 mozilla::MutexAutoLock lock(mMutex);
200 return !mData.handle;
203 private:
204 // Throughout this class, we use manual locking and unlocking guarded by
205 // Clang's thread-safety warnings, rather than scope-based lock-guards. This
206 // is largely driven by `Replace()`, below, which performs both operations
207 // which require the mutex to be held and operations which require it to not
208 // be held, and therefore must explicitly sequence the mutex release.
210 // These explicit locks, unlocks, and annotations are both alien to C++ and
211 // offensively tedious; but they _are_ still checked for state consistency at
212 // scope boundaries. (The concerned reader is invited to test this by
213 // deliberately removing an `mMutex.Unlock()` call from anywhere in the class
214 // and viewing the resultant compiler diagnostics.)
216 // A more principled, or at least differently-principled, implementation might
217 // create a scope-based lock-guard and pass it to `Replace()` to dispose of at
218 // the proper time. Alas, it cannot be communicated to Clang's thread-safety
219 // checker that such a guard is associated with `mMutex`.
221 void Replace(Data&& aData) MOZ_CAPABILITY_RELEASE(mMutex) {
222 // either both handles are NULL, or neither is
223 MOZ_ASSERT(!!aData.handle == !!aData.waitHandle);
225 if (mData.handle) {
226 MOZ_LOG(sHWLog, LogLevel::Info,
227 ("Stop callback: HW %p | handle %p | target %p | PTP_WAIT %p",
228 this, mData.handle, mData.target.get(), mData.waitHandle.get()));
231 if (mData.target) {
232 mData.target->UnregisterShutdownTask(this);
235 // Extract the old data and insert the new -- but hold onto the old data for
236 // now. (See [1] and [2], below.)
237 Data oldData = std::exchange(mData, std::move(aData));
239 ////////////////////////////////////////////////////////////////////////////
240 // Release the mutex.
241 mMutex.Unlock();
242 ////////////////////////////////////////////////////////////////////////////
244 // [1] `oldData.self` will be unset if the old callback already ran (or if
245 // there was no old callback in the first place). If it's set, though, we
246 // need to explicitly clear out the wait-object first.
247 if (oldData.self) {
248 MOZ_ASSERT(oldData.waitHandle);
249 FlushWaitHandle(oldData.waitHandle.get());
252 // [2] oldData also includes several other reference-counted pointers. It's
253 // possible that these may be the last pointer to something, so releasing
254 // them may have arbitrary side-effects -- like calling this->Stop(), which
255 // will try to reacquire the mutex.
257 // Now that we've released the mutex, we can (implicitly) release them all
258 // here.
261 // Either confirm as complete or cancel any callbacks on aWaitHandle. Block
262 // until this is done. (See documentation for ::CloseThreadpoolWait().)
263 void FlushWaitHandle(PTP_WAIT aWaitHandle) MOZ_EXCLUDES(mMutex) {
264 ::SetThreadpoolWait(aWaitHandle, nullptr, nullptr);
265 // This might block on `OnWaitCompleted()`, so we can't hold `mMutex` here.
266 ::WaitForThreadpoolWaitCallbacks(aWaitHandle, TRUE);
267 // ::CloseThreadpoolWait() itself is the caller's responsibility.
271 NS_IMPL_ISUPPORTS(HandleWatcher::Impl, nsITargetShutdownTask)
273 //////
274 // HandleWatcher member function implementations
276 HandleWatcher::HandleWatcher() : mImpl{} {}
277 HandleWatcher::~HandleWatcher() {
278 if (mImpl) {
279 MOZ_ASSERT(mImpl->IsStopped());
280 mImpl->Stop(); // just in case, in release
284 HandleWatcher::HandleWatcher(HandleWatcher&&) noexcept = default;
285 HandleWatcher& HandleWatcher::operator=(HandleWatcher&&) noexcept = default;
287 void HandleWatcher::Watch(HANDLE aHandle, nsIEventTarget* aTarget,
288 already_AddRefed<nsIRunnable> aRunnable) {
289 auto impl = Impl::Create(aHandle, aTarget, std::move(aRunnable));
290 MOZ_ASSERT(impl);
292 if (mImpl) {
293 mImpl->Stop();
295 mImpl = std::move(impl);
298 void HandleWatcher::Stop() {
299 if (mImpl) {
300 mImpl->Stop();
304 bool HandleWatcher::IsStopped() { return !mImpl || mImpl->IsStopped(); }
306 } // namespace mozilla