summaryrefslogtreecommitdiffstats
path: root/ipc/mscom/EnsureMTA.h
blob: dde2af9d491a3e0bc6d9756e7a93f5997cf6727a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/* -*- 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/. */

#ifndef mozilla_mscom_EnsureMTA_h
#define mozilla_mscom_EnsureMTA_h

#include "MainThreadUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Unused.h"
#include "mozilla/mscom/AgileReference.h"
#include "mozilla/mscom/Utils.h"
#include "mozilla/RefPtr.h"
#include "nsCOMPtr.h"
#include "nsIThread.h"
#include "nsThreadUtils.h"
#include "nsWindowsHelpers.h"

#include <windows.h>

namespace mozilla {
namespace mscom {
namespace detail {

// Forward declarations
template <typename T>
struct MTADelete;

template <typename T>
struct MTARelease;

template <typename T>
struct MTAReleaseInChildProcess;

struct PreservedStreamDeleter;

}  // namespace detail

// This class is OK to use as a temporary on the stack.
class MOZ_STACK_CLASS EnsureMTA final {
 public:
  /**
   * This constructor just ensures that the MTA thread is up and running.
   */
  EnsureMTA() {
    nsCOMPtr<nsIThread> thread = GetMTAThread();
    MOZ_ASSERT(thread);
    Unused << thread;
  }

  enum class Option {
    Default,
    // Forcibly dispatch to the thread returned by GetMTAThread(), even if the
    // current thread is already inside a MTA.
    ForceDispatch,
  };

  /**
   * Synchronously run |aClosure| on a thread living in the COM multithreaded
   * apartment. If the current thread lives inside the COM MTA, then it runs
   * |aClosure| immediately unless |aOpt| == Option::ForceDispatch.
   */
  template <typename FuncT>
  explicit EnsureMTA(FuncT&& aClosure, Option aOpt = Option::Default) {
    if (aOpt != Option::ForceDispatch && IsCurrentThreadMTA()) {
      // We're already on the MTA, we can run aClosure directly
      aClosure();
      return;
    }

    // In this case we need to run aClosure on a background thread in the MTA
    nsCOMPtr<nsIThread> thread = GetMTAThread();
    MOZ_ASSERT(thread);
    if (!thread) {
      return;
    }

    // Note that we might reenter the EnsureMTA constructor while we wait on
    // this event due to APC dispatch, therefore we need a unique event object
    // for each entry. If perf becomes an issue then we will want to maintain
    // an array of events where the Nth event is unique to the Nth reentry.
    nsAutoHandle event(::CreateEventW(nullptr, FALSE, FALSE, nullptr));
    if (!event) {
      return;
    }

    HANDLE eventHandle = event.get();

    auto eventSetter = [&aClosure, eventHandle]() -> void {
      aClosure();
      ::SetEvent(eventHandle);
    };

    nsresult rv = thread->Dispatch(
        NS_NewRunnableFunction("EnsureMTA", std::move(eventSetter)),
        NS_DISPATCH_NORMAL);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    if (NS_FAILED(rv)) {
      return;
    }

    DWORD waitResult;
    while ((waitResult = ::WaitForSingleObjectEx(event, INFINITE, TRUE)) ==
           WAIT_IO_COMPLETION) {
    }
    MOZ_ASSERT(waitResult == WAIT_OBJECT_0);
  }

  using CreateInstanceAgileRefPromise =
      MozPromise<AgileReference, HRESULT, false>;

  /**
   *       *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
   *
   * Asynchronously instantiate a new COM object from a MTA thread, unless the
   * current thread is already living inside the multithreaded apartment, in
   * which case the object is immediately instantiated.
   *
   * This function only supports the most common configurations for creating
   * a new object, so it only supports in-process servers. Furthermore, this
   * function does not support aggregation (ie. the |pUnkOuter| parameter to
   * CoCreateInstance).
   *
   * Given that attempting to instantiate an Apartment-threaded COM object
   * inside the MTA results in a *loss* of performance, we assert when that
   * situation arises.
   *
   * The resulting promise, once resolved, provides an AgileReference that may
   * be passed between any COM-initialized thread in the current process.
   *
   *       *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
   *
   * WARNING:
   * Some COM objects do not support creation in the multithreaded apartment,
   * in which case this function is not available as an option. In this case,
   * the promise will always be rejected. In debug builds we will assert.
   *
   *       *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
   *
   * WARNING:
   * Any in-process COM objects whose interfaces accept HWNDs are probably
   * *not* safe to instantiate in the multithreaded apartment! Even if this
   * function succeeds when creating such an object, you *MUST NOT* do so, as
   * these failures might not become apparent until your code is running out in
   * the wild on the release channel!
   *
   *       *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
   *
   * WARNING:
   * When you obtain an interface from the AgileReference, it may or may not be
   * a proxy to the real object. This depends entirely on the implementation of
   * the underlying class and the multithreading capabilities that the class
   * declares to the COM runtime. If the interface is proxied, it might be
   * expensive to invoke methods on that interface! *Always* test the
   * performance of your method calls when calling interfaces that are resolved
   * via this function!
   *
   *       *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
   *
   * (Despite this myriad of warnings, it is still *much* safer to use this
   * function to asynchronously create COM objects than it is to roll your own!)
   *
   *       *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! ***
   */
  static RefPtr<CreateInstanceAgileRefPromise> CreateInstance(REFCLSID aClsid,
                                                              REFIID aIid);

 private:
  static RefPtr<CreateInstanceAgileRefPromise> CreateInstanceInternal(
      REFCLSID aClsid, REFIID aIid);

  static nsCOMPtr<nsIThread> GetMTAThread();

  // The following function is private in order to force any consumers to be
  // declared as friends of EnsureMTA. The intention is to prevent
  // AsyncOperation from becoming some kind of free-for-all mechanism for
  // asynchronously executing work on a background thread.
  template <typename FuncT>
  static void AsyncOperation(FuncT&& aClosure) {
    if (IsCurrentThreadMTA()) {
      aClosure();
      return;
    }

    nsCOMPtr<nsIThread> thread(GetMTAThread());
    MOZ_ASSERT(thread);
    if (!thread) {
      return;
    }

    DebugOnly<nsresult> rv = thread->Dispatch(
        NS_NewRunnableFunction("mscom::EnsureMTA::AsyncOperation",
                               std::move(aClosure)),
        NS_DISPATCH_NORMAL);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }

  template <typename T>
  friend struct mozilla::mscom::detail::MTADelete;

  template <typename T>
  friend struct mozilla::mscom::detail::MTARelease;

  template <typename T>
  friend struct mozilla::mscom::detail::MTAReleaseInChildProcess;

  friend struct mozilla::mscom::detail::PreservedStreamDeleter;
};

}  // namespace mscom
}  // namespace mozilla

#endif  // mozilla_mscom_EnsureMTA_h