Bug 1263224: Add support for thread-safe weak references for COM-based objects; r?jimm draft
authorAaron Klotz <aklotz@mozilla.com>
Tue, 19 Jul 2016 13:36:55 -0600
changeset 393336 5f5bd38f4d3bd68bfd1d0c39ee922d378ee41c1d
parent 393335 ba95eef4285f1cebb575b0c071e52399416a27de
child 393337 452d849ba4812ebf1111c1bddfa49fc49613e498
push id24290
push useraklotz@mozilla.com
push dateWed, 27 Jul 2016 18:19:09 +0000
reviewersjimm
bugs1263224
milestone50.0a1
Bug 1263224: Add support for thread-safe weak references for COM-based objects; r?jimm MozReview-Commit-ID: JxVMyDhNPs4
ipc/mscom/WeakRef.cpp
ipc/mscom/WeakRef.h
ipc/mscom/moz.build
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/WeakRef.cpp
@@ -0,0 +1,193 @@
+/* -*- 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/. */
+
+#define INITGUID
+#include "mozilla/mscom/WeakRef.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+#include "nsWindowsHelpers.h"
+
+namespace mozilla {
+namespace mscom {
+
+WeakReferenceSupport::WeakReferenceSupport(Flags aFlags)
+  : mRefCnt(1)
+  , mFlags(aFlags)
+{
+  ::InitializeCriticalSectionAndSpinCount(&mCS, 4000);
+}
+
+WeakReferenceSupport::~WeakReferenceSupport()
+{
+  MOZ_ASSERT(mWeakRefs.IsEmpty());
+  ::DeleteCriticalSection(&mCS);
+}
+
+HRESULT
+WeakReferenceSupport::QueryInterface(REFIID riid, void** ppv)
+{
+  AutoCriticalSection lock(&mCS);
+  RefPtr<IUnknown> punk;
+  if (!ppv) {
+    return E_INVALIDARG;
+  }
+  *ppv = nullptr;
+
+  // Raise the refcount for stabilization purposes during aggregation
+  RefPtr<IUnknown> kungFuDeathGrip(static_cast<IUnknown*>(this));
+
+  if (riid == IID_IUnknown || riid == IID_IWeakReferenceSource) {
+    punk = static_cast<IUnknown*>(this);
+  } else {
+    HRESULT hr = ThreadSafeQueryInterface(riid, getter_AddRefs(punk));
+    if (FAILED(hr)) {
+      return hr;
+    }
+  }
+
+  if (!punk) {
+    return E_NOINTERFACE;
+  }
+
+  punk.forget(ppv);
+  return S_OK;
+}
+
+ULONG
+WeakReferenceSupport::AddRef()
+{
+  AutoCriticalSection lock(&mCS);
+  return ++mRefCnt;
+}
+
+ULONG
+WeakReferenceSupport::Release()
+{
+  ULONG newRefCnt;
+  { // Scope for lock
+    AutoCriticalSection lock(&mCS);
+    newRefCnt = --mRefCnt;
+    if (newRefCnt == 0) {
+      ClearWeakRefs();
+    }
+  }
+  if (newRefCnt == 0) {
+    if (mFlags != Flags::eDestroyOnMainThread || NS_IsMainThread()) {
+      delete this;
+    } else {
+      // It is possible for the last Release() call to happen off-main-thread.
+      // If so, we need to dispatch an event to delete ourselves.
+      mozilla::DebugOnly<nsresult> rv =
+        NS_DispatchToMainThread(NS_NewRunnableFunction([this]() -> void
+        {
+          delete this;
+        }));
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
+  }
+  return newRefCnt;
+}
+
+void
+WeakReferenceSupport::ClearWeakRefs()
+{
+  for (uint32_t i = 0, len = mWeakRefs.Length(); i < len; ++i) {
+    mWeakRefs[i]->Clear();
+    mWeakRefs[i]->Release();
+  }
+  mWeakRefs.Clear();
+}
+
+HRESULT
+WeakReferenceSupport::GetWeakReference(IWeakReference** aOutWeakRef)
+{
+  if (!aOutWeakRef) {
+    return E_INVALIDARG;
+  }
+  *aOutWeakRef = nullptr;
+
+  AutoCriticalSection lock(&mCS);
+  RefPtr<WeakRef> weakRef = MakeAndAddRef<WeakRef>(this);
+
+  HRESULT hr = weakRef->QueryInterface(IID_IWeakReference, (void**)aOutWeakRef);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  mWeakRefs.AppendElement(weakRef.get());
+  weakRef->AddRef();
+  return S_OK;
+}
+
+WeakRef::WeakRef(WeakReferenceSupport* aSupport)
+  : mRefCnt(1)
+  , mMutex("mozilla::mscom::WeakRef::mMutex")
+  , mSupport(aSupport)
+{
+  MOZ_ASSERT(aSupport);
+}
+
+HRESULT
+WeakRef::QueryInterface(REFIID riid, void** ppv)
+{
+  IUnknown* punk = nullptr;
+  if (!ppv) {
+    return E_INVALIDARG;
+  }
+
+  if (riid == IID_IUnknown || riid == IID_IWeakReference) {
+    punk = static_cast<IUnknown*>(this);
+  }
+
+  *ppv = punk;
+  if (!punk) {
+    return E_NOINTERFACE;
+  }
+
+  punk->AddRef();
+  return S_OK;
+}
+
+ULONG
+WeakRef::AddRef()
+{
+  return (ULONG) InterlockedIncrement((LONG*)&mRefCnt);
+}
+
+ULONG
+WeakRef::Release()
+{
+  ULONG newRefCnt = (ULONG) InterlockedDecrement((LONG*)&mRefCnt);
+  if (newRefCnt == 0) {
+    delete this;
+  }
+  return newRefCnt;
+}
+
+HRESULT
+WeakRef::Resolve(REFIID aIid, void** aOutStrongReference)
+{
+  MutexAutoLock lock(mMutex);
+  if (!mSupport) {
+    return E_FAIL;
+  }
+  return mSupport->QueryInterface(aIid, aOutStrongReference);
+}
+
+void
+WeakRef::Clear()
+{
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mSupport);
+  mSupport = nullptr;
+}
+
+} // namespace mscom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/WeakRef.h
@@ -0,0 +1,104 @@
+/* -*- 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_WeakRef_h
+#define mozilla_mscom_WeakRef_h
+
+#include <guiddef.h>
+#include <Unknwn.h>
+
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+
+/**
+ * Thread-safe weak references for COM that works pre-Windows 8 and do not
+ * require WinRT.
+ */
+
+namespace mozilla {
+namespace mscom {
+
+// {F841AEFA-064C-49A4-B73D-EBD14A90F012}
+DEFINE_GUID(IID_IWeakReference,
+0xf841aefa, 0x64c, 0x49a4, 0xb7, 0x3d, 0xeb, 0xd1, 0x4a, 0x90, 0xf0, 0x12);
+
+struct IWeakReference : public IUnknown
+{
+  virtual STDMETHODIMP Resolve(REFIID aIid, void** aOutStringReference) = 0;
+};
+
+// {87611F0C-9BBB-4F78-9D43-CAC5AD432CA1}
+DEFINE_GUID(IID_IWeakReferenceSource,
+0x87611f0c, 0x9bbb, 0x4f78, 0x9d, 0x43, 0xca, 0xc5, 0xad, 0x43, 0x2c, 0xa1);
+
+struct IWeakReferenceSource : public IUnknown
+{
+  virtual STDMETHODIMP GetWeakReference(IWeakReference** aOutWeakRef) = 0;
+};
+
+class WeakRef;
+
+class WeakReferenceSupport : public IWeakReferenceSource
+{
+public:
+  enum class Flags
+  {
+    eNone = 0,
+    eDestroyOnMainThread = 1
+  };
+
+  // IUnknown
+  STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
+  STDMETHODIMP_(ULONG) AddRef() override;
+  STDMETHODIMP_(ULONG) Release() override;
+
+  // IWeakReferenceSource
+  STDMETHODIMP GetWeakReference(IWeakReference** aOutWeakRef) override;
+
+protected:
+  explicit WeakReferenceSupport(Flags aFlags);
+  virtual ~WeakReferenceSupport();
+
+  virtual HRESULT ThreadSafeQueryInterface(REFIID aIid,
+                                           IUnknown** aOutInterface) = 0;
+
+private:
+  void ClearWeakRefs();
+
+private:
+  // Using a raw CRITICAL_SECTION here because it can be reentered
+  CRITICAL_SECTION    mCS;
+  ULONG               mRefCnt;
+  nsTArray<WeakRef*>  mWeakRefs;
+  Flags               mFlags;
+};
+
+class WeakRef : public IWeakReference
+{
+public:
+  // IUnknown
+  STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
+  STDMETHODIMP_(ULONG) AddRef() override;
+  STDMETHODIMP_(ULONG) Release() override;
+
+  // IWeakReference
+  STDMETHODIMP Resolve(REFIID aIid, void** aOutStrongReference) override;
+
+  explicit WeakRef(WeakReferenceSupport* aSupport);
+
+  void Clear();
+
+private:
+  ULONG                 mRefCnt;
+  mozilla::Mutex        mMutex; // Protects mSupport
+  WeakReferenceSupport* mSupport;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_WeakRef_h
+
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -10,21 +10,23 @@ EXPORTS.mozilla.mscom += [
     'EnsureMTA.h',
     'InterceptorLog.h',
     'MainThreadInvoker.h',
     'MainThreadRuntime.h',
     'ProxyStream.h',
     'Ptr.h',
     'Registration.h',
     'Utils.h',
+    'WeakRef.h',
 ]
 
 SOURCES += [
     'Registration.cpp',
     'Utils.cpp',
+    'WeakRef.cpp',
 ]
 
 UNIFIED_SOURCES += [
     'EnsureMTA.cpp',
     'InterceptorLog.cpp',
     'MainThreadInvoker.cpp',
     'MainThreadRuntime.cpp',
     'ProxyStream.cpp',