Bug 1635956: Part 3 - Implement Shmem functionality in WebGL ProducerConsumerQueue r=jgilbert
authorDavid Parks <daparks@mozilla.com>
Tue, 02 Jun 2020 06:18:52 +0000
changeset 597510 801f886cf1b608a2cb697ffa02067ef1d42c9d1f
parent 597509 f004c5cbfce3f654ef590758096d3c83b0c53491
child 597511 f3a829091fef65cf49209ed374688cdd6d5753ed
push id13310
push userffxbld-merge
push dateMon, 29 Jun 2020 14:50:06 +0000
treeherdermozilla-beta@15a59a0afa5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgilbert
bugs1635956
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1635956: Part 3 - Implement Shmem functionality in WebGL ProducerConsumerQueue r=jgilbert Implements LookupSharedMemory and AllocShmem in ProducerConsumerQueue (and fixes a few minor build issues). To be used in construction of a ProducerConsumerQueue, an actor has to subclass IPcqActor. Differential Revision: https://phabricator.services.mozilla.com/D74974
dom/canvas/ProducerConsumerQueue.h
dom/canvas/WebGLChild.cpp
dom/canvas/WebGLChild.h
dom/canvas/WebGLParent.cpp
dom/canvas/WebGLParent.h
--- a/dom/canvas/ProducerConsumerQueue.h
+++ b/dom/canvas/ProducerConsumerQueue.h
@@ -8,50 +8,110 @@
 #ifndef mozilla_ipc_ProducerConsumerQueue_h
 #define mozilla_ipc_ProducerConsumerQueue_h 1
 
 #include <atomic>
 #include <tuple>
 #include <type_traits>
 #include <utility>
 #include <vector>
+#include "mozilla/WeakPtr.h"
 #include "mozilla/dom/QueueParamTraits.h"
 #include "CrossProcessSemaphore.h"
+#include "nsThreadUtils.h"
 
 namespace IPC {
 template <typename T>
 struct ParamTraits;
 }  // namespace IPC
 
 namespace mozilla {
 namespace webgl {
 
 using IPC::PcqTypeInfo;
 using IPC::PcqTypeInfoID;
+using mozilla::ipc::IProtocol;
+using mozilla::ipc::Shmem;
 
 extern LazyLogModule gPCQLog;
 #define PCQ_LOG_(lvl, ...) MOZ_LOG(mozilla::webgl::gPCQLog, lvl, (__VA_ARGS__))
 #define PCQ_LOGD(...) PCQ_LOG_(LogLevel::Debug, __VA_ARGS__)
 #define PCQ_LOGE(...) PCQ_LOG_(LogLevel::Error, __VA_ARGS__)
 
 class ProducerConsumerQuue;
 class PcqProducer;
 class PcqConsumer;
 
+/**
+ * PcqActor is an actor base-class that is used as a static map that
+ * provides casting from an IProtocol to a PcqActor.  PcqActors delegate
+ * all needed IProtocol operations and also support weak references.
+ * Actors used to construct a PCQ must implement this class.
+ * Example:
+ * class MyActorParent : public PMyActorParent, public PcqActor {
+ *   MyActorParent() : PcqActor(this) {}
+ *   // ...
+ * }
+ * Implementations of abstract methods will typically just forward to IProtocol.
+ */
+class PcqActor : public SupportsWeakPtr<PcqActor> {
+  // The IProtocol part of `this`.
+  IProtocol* mProtocol;
+
+  inline static std::unordered_map<IProtocol*, PcqActor*> sMap;
+
+  static bool IsActorThread() {
+    static nsIThread* sActorThread = [] { return NS_GetCurrentThread(); }();
+    return sActorThread == NS_GetCurrentThread();
+  }
+
+ protected:
+  explicit PcqActor(IProtocol* aProtocol) : mProtocol(aProtocol) {
+    MOZ_ASSERT(IsActorThread());
+    sMap.insert({mProtocol, this});
+  }
+  ~PcqActor() {
+    MOZ_ASSERT(IsActorThread());
+    sMap.erase(mProtocol);
+  }
+
+ public:
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(PcqActor)
+
+  Shmem::SharedMemory* LookupSharedMemory(int32_t aId) {
+    return mProtocol->LookupSharedMemory(aId);
+  }
+  int32_t Id() const { return mProtocol->Id(); }
+  base::ProcessId OtherPid() const { return mProtocol->OtherPid(); }
+  bool AllocShmem(size_t aSize,
+                  mozilla::ipc::SharedMemory::SharedMemoryType aShmType,
+                  mozilla::ipc::Shmem* aShmem) {
+    return mProtocol->AllocShmem(aSize, aShmType, aShmem);
+  }
+
+  static PcqActor* LookupProtocol(IProtocol* aProtocol) {
+    MOZ_ASSERT(IsActorThread());
+    auto it = sMap.find(aProtocol);
+    return (it != sMap.end()) ? it->second : nullptr;
+  }
+};
+
 }  // namespace webgl
 
 // NB: detail is in mozilla instead of mozilla::webgl because many points in
 // existing code get confused if mozilla::detail and mozilla::webgl::detail
 // exist.
 namespace detail {
 using IPC::PcqTypeInfo;
 using IPC::PcqTypeInfoID;
 
+using mozilla::ipc::IProtocol;
 using mozilla::ipc::Shmem;
 using mozilla::webgl::IsSuccess;
+using mozilla::webgl::PcqActor;
 using mozilla::webgl::ProducerConsumerQueue;
 using mozilla::webgl::QueueStatus;
 
 constexpr size_t GetCacheLineSize() { return 64; }
 
 // NB: The header may end up consuming fewer bytes than this.  This value
 // guarantees that we can always byte-align the header contents.
 constexpr size_t GetMaxHeaderSize() {
@@ -179,40 +239,36 @@ class PcqBase {
 
   // This does no synchronization so the information may be stale.
   bool IsFull() { return IsFull(GetReadRelaxed(), GetWriteRelaxed()); }
 
  protected:
   friend struct mozilla::ipc::IPDLParamTraits<PcqBase>;
   friend ProducerConsumerQueue;
 
-  PcqBase()
-      : mOtherPid(0),
-        mQueue(nullptr),
-        mQueueBufferSize(0),
-        mUserReservedMemory(nullptr),
-        mUserReservedSize(0),
-        mRead(nullptr),
-        mWrite(nullptr) {}
+  PcqBase() = default;
 
-  PcqBase(Shmem& aShmem, base::ProcessId aOtherPid, size_t aQueueSize,
+  PcqBase(Shmem& aShmem, IProtocol* aProtocol, size_t aQueueSize,
           RefPtr<PcqRCSemaphore> aMaybeNotEmptySem,
           RefPtr<PcqRCSemaphore> aMaybeNotFullSem) {
-    Set(aShmem, aOtherPid, aQueueSize, aMaybeNotEmptySem, aMaybeNotFullSem);
+    Set(aShmem, aProtocol, aQueueSize, aMaybeNotEmptySem, aMaybeNotFullSem);
   }
 
   PcqBase(const PcqBase&) = delete;
   PcqBase(PcqBase&&) = default;
   PcqBase& operator=(const PcqBase&) = delete;
   PcqBase& operator=(PcqBase&&) = default;
 
-  void Set(Shmem& aShmem, base::ProcessId aOtherPid, size_t aQueueSize,
+  void Set(Shmem& aShmem, IProtocol* aProtocol, size_t aQueueSize,
            RefPtr<PcqRCSemaphore> aMaybeNotEmptySem,
            RefPtr<PcqRCSemaphore> aMaybeNotFullSem) {
-    mOtherPid = aOtherPid;
+    mActor = PcqActor::LookupProtocol(aProtocol);
+    MOZ_RELEASE_ASSERT(mActor);
+
+    mOtherPid = mActor->OtherPid();
     mShmem = aShmem;
     mQueue = aShmem.get<uint8_t>();
 
     // NB: The buffer needs one extra byte for the queue contents
     mQueueBufferSize = aQueueSize + 1;
 
     // Recall that the Shmem contents are laid out like this:
     // -----------------------------------------------------------------------
@@ -292,30 +348,33 @@ class PcqBase {
   /**
    * The QueueBufferSize is the number of bytes in the buffer that the queue
    * uses for storage.
    * This is usually the right method to use when calculating read/write head
    * positions.
    */
   size_t QueueBufferSize() { return mQueueBufferSize; }
 
-  // PID of process on the other end.  Both ends may run on the same process.
-  base::ProcessId mOtherPid;
+  // Actor used for making Shmems.
+  WeakPtr<PcqActor> mActor;
 
-  uint8_t* mQueue;
-  size_t mQueueBufferSize;
+  // PID of process on the other end.  Both ends may run on the same process.
+  base::ProcessId mOtherPid = 0;
+
+  uint8_t* mQueue = nullptr;
+  size_t mQueueBufferSize = 0;
 
   // Pointer to memory reserved for use by the user, or null if none
-  uint8_t* mUserReservedMemory;
-  size_t mUserReservedSize;
+  uint8_t* mUserReservedMemory = nullptr;
+  size_t mUserReservedSize = 0;
 
   // These std::atomics are in shared memory so DO NOT DELETE THEM!  We should,
   // however, call their destructors.
-  std::atomic_size_t* mRead;
-  std::atomic_size_t* mWrite;
+  std::atomic_size_t* mRead = nullptr;
+  std::atomic_size_t* mWrite = nullptr;
 
   // The Shmem contents are laid out like this:
   // -----------------------------------------------------------------------
   // queue contents | align1 | mRead | align2 | mWrite | align3 | User Data
   // -----------------------------------------------------------------------
   // where align1 is chosen so that mRead is properly aligned for a
   // std_atomic_size_t and is on a cache line separate from the queue contents
   // align2 and align3 is chosen to separate mRead/mWrite and mWrite/User Data
@@ -446,37 +505,51 @@ class PcqProducer : public detail::PcqBa
 
   template <typename... Args>
   QueueStatus TryTypedInsert(Args&&... aArgs) {
     return TryInsert(PcqTypedArg<Args>(aArgs)...);
   }
 
   QueueStatus AllocShmem(mozilla::ipc::Shmem* aShmem, size_t aBufferSize,
                          const void* aBuffer = nullptr) {
-    MOZ_CRASH("TODO:");
+    if (!mActor) {
+      return QueueStatus::kFatalError;
+    }
+
+    if (!mActor->AllocShmem(
+            aBufferSize,
+            mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC, aShmem)) {
+      return QueueStatus::kOOMError;
+    }
+
+    if (aBuffer) {
+      memcpy(aShmem->get<uint8_t>(), aBuffer, aBufferSize);
+    }
+    return QueueStatus::kSuccess;
   }
 
  protected:
   friend ProducerConsumerQueue;
   friend ProducerView<PcqProducer>;
 
   template <typename Arg, typename... Args>
-  QueueStatus TryInsertHelper(ProducerView<PcqProducer>& aView, const Arg& aArg,
-                              const Args&... aArgs) {
-    QueueStatus status = TryInsertItem(aView, aArg);
+  QueueStatus TryInsertHelper(ProducerView<PcqProducer>& aView, Arg&& aArg,
+                              Args&&... aArgs) {
+    QueueStatus status = TryInsertItem(aView, std::forward<Arg>(aArg));
     return IsSuccess(status) ? TryInsertHelper(aView, aArgs...) : status;
   }
 
   QueueStatus TryInsertHelper(ProducerView<PcqProducer>&) {
     return QueueStatus::kSuccess;
   }
 
   template <typename Arg>
-  QueueStatus TryInsertItem(ProducerView<PcqProducer>& aView, const Arg& aArg) {
-    return QueueParamTraits<typename RemoveCVR<Arg>::Type>::Write(aView, aArg);
+  QueueStatus TryInsertItem(ProducerView<PcqProducer>& aView, Arg&& aArg) {
+    return QueueParamTraits<typename RemoveCVR<Arg>::Type>::Write(
+        aView, std::forward<Arg>(aArg));
   }
 
   template <typename... Args>
   QueueStatus TryWaitInsertImpl(bool aRecursed,
                                 const Maybe<TimeDuration>& aDuration,
                                 Args&&... aArgs) {
     // Wait up to aDuration for the not-full semaphore to be signaled.
     // If we run out of time then quit.
@@ -523,20 +596,20 @@ class PcqProducer : public detail::PcqBa
 
   // Currently, the PCQ requires any parameters expected to need more than
   // 1/16 the total number of bytes in the command queue to use their own
   // SharedMemory.
   bool NeedsSharedMemory(size_t aRequested) {
     return (Size() / 16) < aRequested;
   }
 
-  PcqProducer(Shmem& aShmem, base::ProcessId aOtherPid, size_t aQueueSize,
+  PcqProducer(Shmem& aShmem, IProtocol* aProtocol, size_t aQueueSize,
               RefPtr<detail::PcqRCSemaphore> aMaybeNotEmptySem,
               RefPtr<detail::PcqRCSemaphore> aMaybeNotFullSem)
-      : PcqBase(aShmem, aOtherPid, aQueueSize, aMaybeNotEmptySem,
+      : PcqBase(aShmem, aProtocol, aQueueSize, aMaybeNotEmptySem,
                 aMaybeNotFullSem) {
     // Since they are shared, this initializes mRead/mWrite in the PcqConsumer
     // as well.
     *mRead = 0;
     *mWrite = 0;
   }
 
   PcqProducer(const PcqProducer&) = delete;
@@ -662,17 +735,20 @@ class PcqConsumer : public detail::PcqBa
               ? TryWaitRemove<Args...>(aDuration)
               : TryWaitRemove<Args...>(Some(aDuration.value() - (now - start)));
     }
 
     return status;
   }
 
   mozilla::ipc::Shmem::SharedMemory* LookupSharedMemory(uint32_t aId) {
-    MOZ_CRASH("TODO:");
+    if (!mActor) {
+      return nullptr;
+    }
+    return mActor->LookupSharedMemory(aId);
   }
 
  protected:
   friend ProducerConsumerQueue;
   friend ConsumerView<PcqConsumer>;
 
   // PeekOrRemoveOperation takes a read pointer and a write index.
   using PeekOrRemoveOperation =
@@ -842,20 +918,20 @@ class PcqConsumer : public detail::PcqBa
 
   // Currently, the PCQ requires any parameters expected to need more than
   // 1/16 the total number of bytes in the command queue to use their own
   // SharedMemory.
   bool NeedsSharedMemory(size_t aRequested) {
     return (Size() / 16) < aRequested;
   }
 
-  PcqConsumer(Shmem& aShmem, base::ProcessId aOtherPid, size_t aQueueSize,
+  PcqConsumer(Shmem& aShmem, IProtocol* aProtocol, size_t aQueueSize,
               RefPtr<detail::PcqRCSemaphore> aMaybeNotEmptySem,
               RefPtr<detail::PcqRCSemaphore> aMaybeNotFullSem)
-      : PcqBase(aShmem, aOtherPid, aQueueSize, aMaybeNotEmptySem,
+      : PcqBase(aShmem, aProtocol, aQueueSize, aMaybeNotEmptySem,
                 aMaybeNotFullSem) {}
 
   PcqConsumer(const PcqConsumer&) = delete;
   PcqConsumer& operator=(const PcqConsumer&) = delete;
 };
 
 using mozilla::detail::GetCacheLineSize;
 using mozilla::detail::GetMaxHeaderSize;
@@ -876,75 +952,41 @@ class ProducerConsumerQueue {
    * allocate additional shared memory for internal accounting (see
    * GetMaxHeaderSize) and that Shmem sizes are a multiple of the operating
    * system's page sizes.
    *
    * aAdditionalBytes of shared memory will also be allocated.
    * Clients may use this shared memory for their own purposes.
    * See GetUserReservedMemory() and GetUserReservedMemorySize()
    */
-  static UniquePtr<ProducerConsumerQueue> Create(
-      mozilla::ipc::IProtocol* aProtocol, size_t aQueueSize,
-      size_t aAdditionalBytes = 0) {
+  static UniquePtr<ProducerConsumerQueue> Create(IProtocol* aProtocol,
+                                                 size_t aQueueSize,
+                                                 size_t aAdditionalBytes = 0) {
     MOZ_ASSERT(aProtocol);
+    // Protocol must subclass PcqActor
+    MOZ_ASSERT(PcqActor::LookupProtocol(aProtocol));
     Shmem shmem;
 
     // NB: We need one extra byte for the queue contents (hence the "+1").
     uint32_t totalShmemSize =
         aQueueSize + 1 + GetMaxHeaderSize() + aAdditionalBytes;
 
     if (!aProtocol->AllocUnsafeShmem(
             totalShmemSize, mozilla::ipc::SharedMemory::TYPE_BASIC, &shmem)) {
       return nullptr;
     }
 
-    UniquePtr<ProducerConsumerQueue> ret =
-        Create(shmem, aProtocol->OtherPid(), aQueueSize);
-    if (!ret) {
-      return ret;
-    }
-
-    // The system may have reserved more bytes than the user asked for.
-    // Make sure they aren't given access to the extra.
-    MOZ_ASSERT(ret->mProducer->mUserReservedSize >= aAdditionalBytes);
-    ret->mProducer->mUserReservedSize = aAdditionalBytes;
-    ret->mConsumer->mUserReservedSize = aAdditionalBytes;
-    if (aAdditionalBytes == 0) {
-      ret->mProducer->mUserReservedMemory = nullptr;
-      ret->mConsumer->mUserReservedMemory = nullptr;
-    }
-    return ret;
-  }
-
-  /**
-   * Create a queue that is backed by aShmem, which must be:
-   * (1) unsafe
-   * (2) made for use with aOtherPid (may be this process' PID)
-   * (3) large enough to hold the queue contents and the shared meta-data of
-   *     the queue (see GetMaxHeaderSize).  Any room left over will be available
-   *     as user reserved memory.
-   *     See GetUserReservedMemory() and GetUserReservedMemorySize()
-   */
-  static UniquePtr<ProducerConsumerQueue> Create(Shmem& aShmem,
-                                                 base::ProcessId aOtherPid,
-                                                 size_t aQueueSize) {
-    uint32_t totalShmemSize = aShmem.Size<uint8_t>();
-
     // NB: We need one extra byte for the queue contents (hence the "+1").
-    if ((!aShmem.IsWritable()) || (!aShmem.IsReadable()) ||
+    if ((!shmem.IsWritable()) || (!shmem.IsReadable()) ||
         ((GetMaxHeaderSize() + aQueueSize + 1) > totalShmemSize)) {
       return nullptr;
     }
 
-    auto notempty = MakeRefPtr<detail::PcqRCSemaphore>(
-        CrossProcessSemaphore::Create("webgl-notempty", 0));
-    auto notfull = MakeRefPtr<detail::PcqRCSemaphore>(
-        CrossProcessSemaphore::Create("webgl-notfull", 1));
-    return WrapUnique(new ProducerConsumerQueue(aShmem, aOtherPid, aQueueSize,
-                                                notempty, notfull));
+    return WrapUnique(new ProducerConsumerQueue(shmem, aProtocol, aQueueSize,
+                                                aAdditionalBytes));
   }
 
   /**
    * The queue needs a few bytes for 2 shared counters.  It takes these from the
    * underlying Shmem.  This will still work if the cache line size is incorrect
    * for some architecture but operations may be less efficient.
    */
   static constexpr size_t GetMaxHeaderSize() {
@@ -960,75 +1002,94 @@ class ProducerConsumerQueue {
 
   using Producer = PcqProducer;
   using Consumer = PcqConsumer;
 
   UniquePtr<Producer> TakeProducer() { return std::move(mProducer); }
   UniquePtr<Consumer> TakeConsumer() { return std::move(mConsumer); }
 
  private:
-  ProducerConsumerQueue(Shmem& aShmem, base::ProcessId aOtherPid,
-                        size_t aQueueSize,
-                        RefPtr<detail::PcqRCSemaphore>& aMaybeNotEmptySem,
-                        RefPtr<detail::PcqRCSemaphore>& aMaybeNotFullSem)
-      : mProducer(
-            WrapUnique(new Producer(aShmem, aOtherPid, aQueueSize,
-                                    aMaybeNotEmptySem, aMaybeNotFullSem))),
-        mConsumer(
-            WrapUnique(new Consumer(aShmem, aOtherPid, aQueueSize,
-                                    aMaybeNotEmptySem, aMaybeNotFullSem))) {
+  ProducerConsumerQueue(Shmem& aShmem, IProtocol* aProtocol, size_t aQueueSize,
+                        size_t aAdditionalBytes) {
+    auto notempty = MakeRefPtr<detail::PcqRCSemaphore>(
+        CrossProcessSemaphore::Create("webgl-notempty", 0));
+    auto notfull = MakeRefPtr<detail::PcqRCSemaphore>(
+        CrossProcessSemaphore::Create("webgl-notfull", 1));
+
+    mProducer = WrapUnique(
+        new Producer(aShmem, aProtocol, aQueueSize, notempty, notfull));
+    mConsumer = WrapUnique(
+        new Consumer(aShmem, aProtocol, aQueueSize, notempty, notfull));
+
+    // The system may have reserved more bytes than the user asked for.
+    // Make sure they aren't given access to the extra.
+    MOZ_ASSERT(mProducer->mUserReservedSize >= aAdditionalBytes);
+    mProducer->mUserReservedSize = aAdditionalBytes;
+    mConsumer->mUserReservedSize = aAdditionalBytes;
+    if (aAdditionalBytes == 0) {
+      mProducer->mUserReservedMemory = nullptr;
+      mConsumer->mUserReservedMemory = nullptr;
+    }
+
     PCQ_LOGD(
         "Constructed PCQ (%p).  Shmem Size = %zu. Queue Size = %zu.  "
         "Other process ID: %08x.",
-        this, aShmem.Size<uint8_t>(), aQueueSize, (uint32_t)aOtherPid);
+        this, aShmem.Size<uint8_t>(), aQueueSize,
+        (uint32_t)aProtocol->OtherPid());
   }
 
   UniquePtr<Producer> mProducer;
   UniquePtr<Consumer> mConsumer;
 };
 
 }  // namespace webgl
 
 namespace ipc {
 
 template <>
 struct IPDLParamTraits<mozilla::detail::PcqBase> {
   typedef mozilla::detail::PcqBase paramType;
 
   static void Write(IPC::Message* aMsg, IProtocol* aActor, paramType& aParam) {
+    // Must be sent using the queue's underlying actor, which must still exist!
+    MOZ_RELEASE_ASSERT(aParam.mActor && aActor->Id() == aParam.mActor->Id());
+    WriteIPDLParam(aMsg, aActor, aParam.mActor->Id());
     WriteIPDLParam(aMsg, aActor, aParam.QueueSize());
     WriteIPDLParam(aMsg, aActor, std::move(aParam.mShmem));
 
     // May not currently share a PcqProducer or PcqConsumer with a process that
     // it's Shmem is not related to.
     MOZ_ASSERT(aActor->OtherPid() == aParam.mOtherPid);
     WriteIPDLParam(
         aMsg, aActor,
         aParam.mMaybeNotEmptySem->ShareToProcess(aActor->OtherPid()));
 
     WriteIPDLParam(aMsg, aActor,
                    aParam.mMaybeNotFullSem->ShareToProcess(aActor->OtherPid()));
   }
 
   static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
                    IProtocol* aActor, paramType* aResult) {
+    int32_t iProtocolId;
     size_t queueSize;
     Shmem shmem;
     CrossProcessSemaphoreHandle notEmptyHandle;
     CrossProcessSemaphoreHandle notFullHandle;
 
-    if (!ReadIPDLParam(aMsg, aIter, aActor, &queueSize) ||
+    if (!ReadIPDLParam(aMsg, aIter, aActor, &iProtocolId) ||
+        (iProtocolId != aActor->Id()) ||
+        !ReadIPDLParam(aMsg, aIter, aActor, &queueSize) ||
         !ReadIPDLParam(aMsg, aIter, aActor, &shmem) ||
         !ReadIPDLParam(aMsg, aIter, aActor, &notEmptyHandle) ||
         !ReadIPDLParam(aMsg, aIter, aActor, &notFullHandle)) {
       return false;
     }
 
     MOZ_ASSERT(IsHandleValid(notEmptyHandle) && IsHandleValid(notFullHandle));
-    aResult->Set(shmem, aActor->OtherPid(), queueSize,
+    aResult->Set(shmem, aActor, queueSize,
                  MakeRefPtr<detail::PcqRCSemaphore>(
                      CrossProcessSemaphore::Create(notEmptyHandle)),
                  MakeRefPtr<detail::PcqRCSemaphore>(
                      CrossProcessSemaphore::Create(notFullHandle)));
     return true;
   }
 
   static void Log(const paramType& aParam, std::wstring* aLog) {
--- a/dom/canvas/WebGLChild.cpp
+++ b/dom/canvas/WebGLChild.cpp
@@ -6,17 +6,18 @@
 #include "WebGLChild.h"
 
 #include "ClientWebGLContext.h"
 #include "WebGLMethodDispatcher.h"
 
 namespace mozilla {
 namespace dom {
 
-WebGLChild::WebGLChild(ClientWebGLContext& context) : mContext(context) {}
+WebGLChild::WebGLChild(ClientWebGLContext& context)
+    : PcqActor(this), mContext(context) {}
 
 WebGLChild::~WebGLChild() { (void)Send__delete__(this); }
 
 mozilla::ipc::IPCResult WebGLChild::RecvJsWarning(
     const std::string& text) const {
   mContext.JsWarning(text);
   return IPC_OK();
 }
--- a/dom/canvas/WebGLChild.h
+++ b/dom/canvas/WebGLChild.h
@@ -15,17 +15,18 @@ namespace mozilla {
 
 class ClientWebGLContext;
 
 namespace dom {
 
 class WebGLChild final : public PWebGLChild,
                          public SyncProducerActor<WebGLChild>,
                          public AsyncConsumerActor<WebGLChild>,
-                         public SupportsWeakPtr<WebGLChild> {
+                         public SupportsWeakPtr<WebGLChild>,
+                         public mozilla::webgl::PcqActor {
  public:
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLChild)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebGLChild, override);
   using OtherSideActor = WebGLParent;
 
   ClientWebGLContext& mContext;
 
   explicit WebGLChild(ClientWebGLContext&);
--- a/dom/canvas/WebGLParent.cpp
+++ b/dom/canvas/WebGLParent.cpp
@@ -37,17 +37,17 @@ mozilla::ipc::IPCResult WebGLParent::Rec
 
   if (!BeginCommandQueueDrain()) {
     return IPC_FAIL(this, "Failed to start WebGL command queue drain");
   }
 
   return IPC_OK();
 }
 
-WebGLParent::WebGLParent() = default;
+WebGLParent::WebGLParent() : PcqActor(this) {}
 WebGLParent::~WebGLParent() = default;
 
 bool WebGLParent::BeginCommandQueueDrain() {
   if (mRunCommandsRunnable) {
     // already running
     return true;
   }
 
--- a/dom/canvas/WebGLParent.h
+++ b/dom/canvas/WebGLParent.h
@@ -19,17 +19,18 @@ namespace layers {
 class SharedSurfaceTextureClient;
 }
 
 namespace dom {
 
 class WebGLParent : public PWebGLParent,
                     public AsyncProducerActor<WebGLParent>,
                     public SyncConsumerActor<WebGLParent>,
-                    public SupportsWeakPtr<WebGLParent> {
+                    public SupportsWeakPtr<WebGLParent>,
+                    public mozilla::webgl::PcqActor {
   friend PWebGLParent;
 
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebGLParent, override);
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLParent)
   using OtherSideActor = WebGLChild;
 
   mozilla::ipc::IPCResult RecvInitialize(