Bug 1761357 [wpt PR 33355] - Fix #33204: Move Safari stable runs to Big Sur, a=testonly
[gecko.git] / ipc / mscom / AsyncInvoker.h
blobce25a4224b7df029457ac024b7c2f81e467d5848
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 #ifndef mozilla_mscom_AsyncInvoker_h
8 #define mozilla_mscom_AsyncInvoker_h
10 #include <objidl.h>
11 #include <windows.h>
13 #include <utility>
15 #include "mozilla/Assertions.h"
16 #include "mozilla/Attributes.h"
17 #include "mozilla/DebugOnly.h"
18 #include "mozilla/Maybe.h"
19 #include "mozilla/Mutex.h"
20 #include "mozilla/mscom/Aggregation.h"
21 #include "mozilla/mscom/Utils.h"
22 #include "nsISerialEventTarget.h"
23 #include "nsISupportsImpl.h"
24 #include "nsThreadUtils.h"
26 namespace mozilla {
27 namespace mscom {
28 namespace detail {
30 template <typename AsyncInterface>
31 class ForgettableAsyncCall : public ISynchronize {
32 public:
33 explicit ForgettableAsyncCall(ICallFactory* aCallFactory)
34 : mRefCnt(0), mAsyncCall(nullptr) {
35 StabilizedRefCount<Atomic<ULONG>> stabilizer(mRefCnt);
37 HRESULT hr =
38 aCallFactory->CreateCall(__uuidof(AsyncInterface), this, IID_IUnknown,
39 getter_AddRefs(mInnerUnk));
40 if (FAILED(hr)) {
41 return;
44 hr = mInnerUnk->QueryInterface(__uuidof(AsyncInterface),
45 reinterpret_cast<void**>(&mAsyncCall));
46 if (SUCCEEDED(hr)) {
47 // Don't hang onto a ref. Because mAsyncCall is aggregated, its refcount
48 // is this->mRefCnt, so we'd create a cycle!
49 mAsyncCall->Release();
53 AsyncInterface* GetInterface() const { return mAsyncCall; }
55 // IUnknown
56 STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) final {
57 if (aIid == IID_ISynchronize || aIid == IID_IUnknown) {
58 RefPtr<ISynchronize> ptr(this);
59 ptr.forget(aOutInterface);
60 return S_OK;
63 return mInnerUnk->QueryInterface(aIid, aOutInterface);
66 STDMETHODIMP_(ULONG) AddRef() final {
67 ULONG result = ++mRefCnt;
68 NS_LOG_ADDREF(this, result, "ForgettableAsyncCall", sizeof(*this));
69 return result;
72 STDMETHODIMP_(ULONG) Release() final {
73 ULONG result = --mRefCnt;
74 NS_LOG_RELEASE(this, result, "ForgettableAsyncCall");
75 if (!result) {
76 delete this;
78 return result;
81 // ISynchronize
82 STDMETHODIMP Wait(DWORD aFlags, DWORD aTimeoutMilliseconds) override {
83 return E_NOTIMPL;
86 STDMETHODIMP Signal() override {
87 // Even though this function is a no-op, we must return S_OK as opposed to
88 // E_NOTIMPL or else COM will consider the async call to have failed.
89 return S_OK;
92 STDMETHODIMP Reset() override {
93 // Even though this function is a no-op, we must return S_OK as opposed to
94 // E_NOTIMPL or else COM will consider the async call to have failed.
95 return S_OK;
98 protected:
99 virtual ~ForgettableAsyncCall() = default;
101 private:
102 Atomic<ULONG> mRefCnt;
103 RefPtr<IUnknown> mInnerUnk;
104 AsyncInterface* mAsyncCall; // weak reference
107 template <typename AsyncInterface>
108 class WaitableAsyncCall : public ForgettableAsyncCall<AsyncInterface> {
109 public:
110 explicit WaitableAsyncCall(ICallFactory* aCallFactory)
111 : ForgettableAsyncCall<AsyncInterface>(aCallFactory),
112 mEvent(::CreateEventW(nullptr, FALSE, FALSE, nullptr)) {}
114 STDMETHODIMP Wait(DWORD aFlags, DWORD aTimeoutMilliseconds) override {
115 const DWORD waitStart =
116 aTimeoutMilliseconds == INFINITE ? 0 : ::GetTickCount();
117 DWORD flags = aFlags;
118 if (XRE_IsContentProcess() && NS_IsMainThread()) {
119 flags |= COWAIT_ALERTABLE;
122 HRESULT hr;
123 DWORD signaledIdx;
125 DWORD elapsed = 0;
127 while (true) {
128 if (aTimeoutMilliseconds != INFINITE) {
129 elapsed = ::GetTickCount() - waitStart;
131 if (elapsed >= aTimeoutMilliseconds) {
132 return RPC_S_CALLPENDING;
135 ::SetLastError(ERROR_SUCCESS);
137 hr = ::CoWaitForMultipleHandles(flags, aTimeoutMilliseconds - elapsed, 1,
138 &mEvent, &signaledIdx);
139 if (hr == RPC_S_CALLPENDING || FAILED(hr)) {
140 return hr;
143 if (hr == S_OK && signaledIdx == 0) {
144 return hr;
149 STDMETHODIMP Signal() override {
150 if (!::SetEvent(mEvent)) {
151 return HRESULT_FROM_WIN32(::GetLastError());
153 return S_OK;
156 protected:
157 ~WaitableAsyncCall() {
158 if (mEvent) {
159 ::CloseHandle(mEvent);
163 private:
164 HANDLE mEvent;
167 template <typename AsyncInterface>
168 class EventDrivenAsyncCall : public ForgettableAsyncCall<AsyncInterface> {
169 public:
170 explicit EventDrivenAsyncCall(ICallFactory* aCallFactory)
171 : ForgettableAsyncCall<AsyncInterface>(aCallFactory) {}
173 bool HasCompletionRunnable() const { return !!mCompletionRunnable; }
175 void ClearCompletionRunnable() { mCompletionRunnable = nullptr; }
177 void SetCompletionRunnable(already_AddRefed<nsIRunnable> aRunnable) {
178 nsCOMPtr<nsIRunnable> innerRunnable(aRunnable);
179 MOZ_ASSERT(!!innerRunnable);
180 if (!innerRunnable) {
181 return;
184 // We need to retain a ref to ourselves to outlive the AsyncInvoker
185 // such that our callback can execute.
186 RefPtr<EventDrivenAsyncCall<AsyncInterface>> self(this);
188 mCompletionRunnable = NS_NewRunnableFunction(
189 "EventDrivenAsyncCall outer completion Runnable",
190 [innerRunnable = std::move(innerRunnable), self = std::move(self)]() {
191 innerRunnable->Run();
195 void SetEventTarget(nsISerialEventTarget* aTarget) { mEventTarget = aTarget; }
197 STDMETHODIMP Signal() override {
198 MOZ_ASSERT(!!mCompletionRunnable);
199 if (!mCompletionRunnable) {
200 return S_OK;
203 nsCOMPtr<nsISerialEventTarget> eventTarget(mEventTarget.forget());
204 if (!eventTarget) {
205 eventTarget = GetMainThreadSerialEventTarget();
208 DebugOnly<nsresult> rv =
209 eventTarget->Dispatch(mCompletionRunnable.forget(), NS_DISPATCH_NORMAL);
210 MOZ_ASSERT(NS_SUCCEEDED(rv));
211 return S_OK;
214 private:
215 nsCOMPtr<nsIRunnable> mCompletionRunnable;
216 nsCOMPtr<nsISerialEventTarget> mEventTarget;
219 template <typename AsyncInterface>
220 class FireAndForgetInvoker {
221 protected:
222 void OnBeginInvoke() {}
223 void OnSyncInvoke(HRESULT aHr) {}
224 void OnAsyncInvokeFailed() {}
226 typedef ForgettableAsyncCall<AsyncInterface> AsyncCallType;
228 RefPtr<ForgettableAsyncCall<AsyncInterface>> mAsyncCall;
231 template <typename AsyncInterface>
232 class WaitableInvoker {
233 public:
234 HRESULT Wait(DWORD aTimeout = INFINITE) const {
235 if (!mAsyncCall) {
236 // Nothing to wait for
237 return S_OK;
240 return mAsyncCall->Wait(0, aTimeout);
243 protected:
244 void OnBeginInvoke() {}
245 void OnSyncInvoke(HRESULT aHr) {}
246 void OnAsyncInvokeFailed() {}
248 typedef WaitableAsyncCall<AsyncInterface> AsyncCallType;
250 RefPtr<WaitableAsyncCall<AsyncInterface>> mAsyncCall;
253 template <typename AsyncInterface>
254 class EventDrivenInvoker {
255 public:
256 void SetCompletionRunnable(already_AddRefed<nsIRunnable> aRunnable) {
257 if (mAsyncCall) {
258 mAsyncCall->SetCompletionRunnable(std::move(aRunnable));
259 return;
262 mCompletionRunnable = aRunnable;
265 void SetAsyncEventTarget(nsISerialEventTarget* aTarget) {
266 if (mAsyncCall) {
267 mAsyncCall->SetEventTarget(aTarget);
271 protected:
272 void OnBeginInvoke() {
273 MOZ_RELEASE_ASSERT(
274 mCompletionRunnable ||
275 (mAsyncCall && mAsyncCall->HasCompletionRunnable()),
276 "You should have called SetCompletionRunnable before invoking!");
279 void OnSyncInvoke(HRESULT aHr) {
280 nsCOMPtr<nsIRunnable> completionRunnable(mCompletionRunnable.forget());
281 if (FAILED(aHr)) {
282 return;
285 completionRunnable->Run();
288 void OnAsyncInvokeFailed() {
289 MOZ_ASSERT(!!mAsyncCall);
290 mAsyncCall->ClearCompletionRunnable();
293 typedef EventDrivenAsyncCall<AsyncInterface> AsyncCallType;
295 RefPtr<EventDrivenAsyncCall<AsyncInterface>> mAsyncCall;
296 nsCOMPtr<nsIRunnable> mCompletionRunnable;
299 } // namespace detail
302 * This class is intended for "fire-and-forget" asynchronous invocations of COM
303 * interfaces. This requires that an interface be annotated with the
304 * |async_uuid| attribute in midl. We also require that there be no outparams
305 * in the desired asynchronous interface (otherwise that would break the
306 * desired "fire-and-forget" semantics).
308 * For example, let us suppose we have some IDL as such:
309 * [object, uuid(...), async_uuid(...)]
310 * interface IFoo : IUnknown
312 * HRESULT Bar([in] long baz);
315 * Then, given an IFoo, we may construct an AsyncInvoker<IFoo, AsyncIFoo>:
317 * IFoo* foo = ...;
318 * AsyncInvoker<IFoo, AsyncIFoo> myInvoker(foo);
319 * HRESULT hr = myInvoker.Invoke(&IFoo::Bar, &AsyncIFoo::Begin_Bar, 7);
321 * Alternatively you may use the ASYNC_INVOKER_FOR and ASYNC_INVOKE macros,
322 * which automatically deduce the name of the asynchronous interface from the
323 * name of the synchronous interface:
325 * ASYNC_INVOKER_FOR(IFoo) myInvoker(foo);
326 * HRESULT hr = ASYNC_INVOKE(myInvoker, Bar, 7);
328 * This class may also be used when a synchronous COM call must be made that
329 * might reenter the content process. In this case, use the WaitableAsyncInvoker
330 * variant, or the WAITABLE_ASYNC_INVOKER_FOR macro:
332 * WAITABLE_ASYNC_INVOKER_FOR(Ifoo) myInvoker(foo);
333 * HRESULT hr = ASYNC_INVOKE(myInvoker, Bar, 7);
334 * if (SUCCEEDED(hr)) {
335 * myInvoker.Wait(); // <-- Wait for the COM call to complete.
338 * In general you should avoid using the waitable version, but in some corner
339 * cases it is absolutely necessary in order to preserve correctness while
340 * avoiding deadlock.
342 * Finally, it is also possible to have the async invoker enqueue a runnable
343 * to the main thread when the async operation completes:
345 * EVENT_DRIVEN_ASYNC_INVOKER_FOR(Ifoo) myInvoker(foo);
346 * // myRunnable will be invoked on the main thread once the async operation
347 * // has completed. Note that we set this *before* we do the ASYNC_INVOKE!
348 * myInvoker.SetCompletionRunnable(myRunnable.forget());
349 * HRESULT hr = ASYNC_INVOKE(myInvoker, Bar, 7);
350 * // ...
352 template <typename SyncInterface, typename AsyncInterface,
353 template <typename Iface> class WaitPolicy =
354 detail::FireAndForgetInvoker>
355 class MOZ_RAII AsyncInvoker final : public WaitPolicy<AsyncInterface> {
356 using Base = WaitPolicy<AsyncInterface>;
358 public:
359 typedef SyncInterface SyncInterfaceT;
360 typedef AsyncInterface AsyncInterfaceT;
363 * @param aSyncObj The COM object on which to invoke the asynchronous event.
364 * If this object is not a proxy to the synchronous variant
365 * of AsyncInterface, then it will be invoked synchronously
366 * instead (because it is an in-process virtual method call).
367 * @param aIsProxy An optional hint as to whether or not aSyncObj is a proxy.
368 * If not specified, AsyncInvoker will automatically detect
369 * whether aSyncObj is a proxy, however there may be a
370 * performance penalty associated with that.
372 explicit AsyncInvoker(SyncInterface* aSyncObj,
373 const Maybe<bool>& aIsProxy = Nothing()) {
374 MOZ_ASSERT(aSyncObj);
376 RefPtr<ICallFactory> callFactory;
377 if ((aIsProxy.isSome() && !aIsProxy.value()) ||
378 FAILED(aSyncObj->QueryInterface(IID_ICallFactory,
379 getter_AddRefs(callFactory)))) {
380 mSyncObj = aSyncObj;
381 return;
384 this->mAsyncCall = new typename Base::AsyncCallType(callFactory);
388 * @brief Invoke a method on the object. Member function pointers are provided
389 * for both the sychronous and asynchronous variants of the interface.
390 * If this invoker's encapsulated COM object is a proxy, then Invoke
391 * will call the asynchronous member function. Otherwise the
392 * synchronous version must be used, as the invocation will simply be a
393 * virtual function call that executes in-process.
394 * @param aSyncMethod Pointer to the method that we would like to invoke on
395 * the synchronous interface.
396 * @param aAsyncMethod Pointer to the method that we would like to invoke on
397 * the asynchronous interface.
399 template <typename SyncMethod, typename AsyncMethod, typename... Args>
400 HRESULT Invoke(SyncMethod aSyncMethod, AsyncMethod aAsyncMethod,
401 Args&&... aArgs) {
402 this->OnBeginInvoke();
403 if (mSyncObj) {
404 HRESULT hr = (mSyncObj->*aSyncMethod)(std::forward<Args>(aArgs)...);
405 this->OnSyncInvoke(hr);
406 return hr;
409 MOZ_ASSERT(this->mAsyncCall);
410 if (!this->mAsyncCall) {
411 this->OnAsyncInvokeFailed();
412 return E_POINTER;
415 AsyncInterface* asyncInterface = this->mAsyncCall->GetInterface();
416 MOZ_ASSERT(asyncInterface);
417 if (!asyncInterface) {
418 this->OnAsyncInvokeFailed();
419 return E_POINTER;
422 HRESULT hr = (asyncInterface->*aAsyncMethod)(std::forward<Args>(aArgs)...);
423 if (FAILED(hr)) {
424 this->OnAsyncInvokeFailed();
427 return hr;
430 AsyncInvoker(const AsyncInvoker& aOther) = delete;
431 AsyncInvoker(AsyncInvoker&& aOther) = delete;
432 AsyncInvoker& operator=(const AsyncInvoker& aOther) = delete;
433 AsyncInvoker& operator=(AsyncInvoker&& aOther) = delete;
435 private:
436 RefPtr<SyncInterface> mSyncObj;
439 template <typename SyncInterface, typename AsyncInterface>
440 using WaitableAsyncInvoker =
441 AsyncInvoker<SyncInterface, AsyncInterface, detail::WaitableInvoker>;
443 template <typename SyncInterface, typename AsyncInterface>
444 using EventDrivenAsyncInvoker =
445 AsyncInvoker<SyncInterface, AsyncInterface, detail::EventDrivenInvoker>;
447 } // namespace mscom
448 } // namespace mozilla
450 #define ASYNC_INVOKER_FOR(SyncIface) \
451 mozilla::mscom::AsyncInvoker<SyncIface, Async##SyncIface>
453 #define WAITABLE_ASYNC_INVOKER_FOR(SyncIface) \
454 mozilla::mscom::WaitableAsyncInvoker<SyncIface, Async##SyncIface>
456 #define EVENT_DRIVEN_ASYNC_INVOKER_FOR(SyncIface) \
457 mozilla::mscom::EventDrivenAsyncInvoker<SyncIface, Async##SyncIface>
459 #define ASYNC_INVOKE(InvokerObj, SyncMethodName, ...) \
460 InvokerObj.Invoke( \
461 &decltype(InvokerObj)::SyncInterfaceT::SyncMethodName, \
462 &decltype(InvokerObj)::AsyncInterfaceT::Begin_##SyncMethodName, \
463 ##__VA_ARGS__)
465 #endif // mozilla_mscom_AsyncInvoker_h