Bug 1570701: Part 2 - Add asynchronous CreateInstance method to mscom::EnsureMTA; r=Jamie

We'd like to offer a way for developers to safely do asynchronous instantiations
of COM objects. This patch adds a static CreateInstance method to mscom::EnsureMTA
that facilitates this under certain conditions (as outlined in the comments).

To help ensure that this is as safe as possible, we return a MozPromise that,
once resolved, produces an AgileReference that may then be passed between
threads, and may then be resolved when a specific thread needs to access the
interface.

Differential Revision: https://phabricator.services.mozilla.com/D44519

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Aaron Klotz 2019-09-04 15:36:42 +00:00
Родитель b36ad40063
Коммит b1a47abb5e
2 изменённых файлов: 127 добавлений и 0 удалений

Просмотреть файл

@ -7,6 +7,7 @@
#include "mozilla/mscom/EnsureMTA.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/mscom/Utils.h"
#include "mozilla/StaticLocalPtr.h"
#include "mozilla/SystemGroup.h"
#include "nsThreadUtils.h"
@ -57,6 +58,67 @@ class BackgroundMTAData {
namespace mozilla {
namespace mscom {
/* static */
RefPtr<EnsureMTA::CreateInstanceAgileRefPromise>
EnsureMTA::CreateInstanceInternal(REFCLSID aClsid, REFIID aIid) {
MOZ_ASSERT(IsCurrentThreadExplicitMTA());
RefPtr<IUnknown> iface;
HRESULT hr = ::CoCreateInstance(aClsid, nullptr, CLSCTX_INPROC_SERVER, aIid,
getter_AddRefs(iface));
if (FAILED(hr)) {
return CreateInstanceAgileRefPromise::CreateAndReject(hr, __func__);
}
// We need to use the two argument constructor for AgileReference because our
// RefPtr is not parameterized on the specific interface being requested.
AgileReference agileRef(aIid, iface);
if (!agileRef) {
return CreateInstanceAgileRefPromise::CreateAndReject(agileRef.GetHResult(),
__func__);
}
return CreateInstanceAgileRefPromise::CreateAndResolve(std::move(agileRef),
__func__);
}
/* static */
RefPtr<EnsureMTA::CreateInstanceAgileRefPromise> EnsureMTA::CreateInstance(
REFCLSID aClsid, REFIID aIid) {
MOZ_ASSERT(IsCOMInitializedOnCurrentThread());
const bool isClassOk = IsClassThreadAwareInprocServer(aClsid);
MOZ_ASSERT(isClassOk,
"mozilla::mscom::EnsureMTA::CreateInstance is not "
"safe/performant/necessary to use with this CLSID. This CLSID "
"either does not support creation from within a multithreaded "
"apartment, or it is not an in-process server.");
if (!isClassOk) {
return CreateInstanceAgileRefPromise::CreateAndReject(CO_E_NOT_SUPPORTED,
__func__);
}
if (IsCurrentThreadExplicitMTA()) {
// It's safe to immediately call CreateInstanceInternal
return CreateInstanceInternal(aClsid, aIid);
}
// aClsid and aIid are references. Make local copies that we can put into the
// lambda in case the sources of aClsid or aIid are not static data
CLSID localClsid = aClsid;
IID localIid = aIid;
auto invoker = [localClsid,
localIid]() -> RefPtr<CreateInstanceAgileRefPromise> {
return CreateInstanceInternal(localClsid, localIid);
};
nsCOMPtr<nsIThread> mtaThread(GetMTAThread());
return InvokeAsync(mtaThread->SerialEventTarget(), __func__,
std::move(invoker));
}
/* static */
nsCOMPtr<nsIThread> EnsureMTA::GetMTAThread() {
static StaticLocalAutoPtr<BackgroundMTAData> sMTAData(

Просмотреть файл

@ -10,8 +10,11 @@
#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"
@ -107,7 +110,69 @@ class MOZ_STACK_CLASS EnsureMTA final {
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