Bug 1548492: Change remote canvas timeouts to check if other side is still open. r=jrmuizel
authorBob Owen <bobowencode@gmail.com>
Mon, 22 Jul 2019 13:12:07 +0000
changeset 483708 205bb2a0bc4582b72afb851e65d6968335ff82b5
parent 483707 c80e50cc1a7386e07e712317b385ff3f47297f7e
child 483709 0aded829fd7a9c0b0324c1bcf9c8df04a9c1d5f0
push id90548
push userbobowencode@gmail.com
push dateMon, 22 Jul 2019 13:14:05 +0000
treeherderautoland@0aded829fd7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1548492
milestone70.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 1548492: Change remote canvas timeouts to check if other side is still open. r=jrmuizel This also fixes the CanvasParent destruction on channel close and error, which was broken due to IPDL changes between rebases. Differential Revision: https://phabricator.services.mozilla.com/D37088
gfx/layers/CanvasDrawEventRecorder.cpp
gfx/layers/CanvasDrawEventRecorder.h
gfx/layers/CanvasTranslator.cpp
gfx/layers/CanvasTranslator.h
gfx/layers/ipc/CanvasChild.cpp
gfx/layers/ipc/CanvasParent.cpp
gfx/layers/ipc/CanvasParent.h
--- a/gfx/layers/CanvasDrawEventRecorder.cpp
+++ b/gfx/layers/CanvasDrawEventRecorder.cpp
@@ -9,34 +9,31 @@
 #include <string.h>
 
 namespace mozilla {
 namespace layers {
 
 static const int32_t kCheckpointEventType = -1;
 static const uint32_t kMaxSpinCount = 200;
 
-// TODO: These timeouts are long because failure in the middle of writing or
-// reading an event is probably going to be fatal to the GPU process. What we
-// really need to know is whether the other process has died.
-static const TimeDuration kWriterTimeout = TimeDuration::FromMilliseconds(1000);
-static const TimeDuration kReaderTimeout = TimeDuration::FromMilliseconds(5000);
+static const TimeDuration kTimeout = TimeDuration::FromMilliseconds(100);
+static const int32_t kTimeoutRetryCount = 50;
 
 static const uint32_t kCacheLineSize = 64;
 static const uint32_t kStreamSize = 64 * 1024;
 static const uint32_t kShmemSize = kStreamSize + (2 * kCacheLineSize);
 
 static_assert((static_cast<uint64_t>(UINT32_MAX) + 1) % kStreamSize == 0,
               "kStreamSize must be a power of two.");
 
 bool CanvasEventRingBuffer::InitWriter(
     base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aReadHandle,
     CrossProcessSemaphoreHandle* aReaderSem,
     CrossProcessSemaphoreHandle* aWriterSem,
-    const std::function<void()>& aResumeReaderCallback) {
+    UniquePtr<WriterServices> aWriterServices) {
   mSharedMemory = MakeAndAddRef<ipc::SharedMemoryBasic>();
   if (NS_WARN_IF(!mSharedMemory->Create(kShmemSize)) ||
       NS_WARN_IF(!mSharedMemory->Map(kShmemSize))) {
     return false;
   }
 
   if (NS_WARN_IF(!mSharedMemory->ShareToProcess(aOtherPid, aReadHandle))) {
     return false;
@@ -67,42 +64,46 @@ bool CanvasEventRingBuffer::InitWriter(
       CrossProcessSemaphore::Create("SharedMemoryStreamParent", 0));
   *aReaderSem = mReaderSemaphore->ShareToProcess(aOtherPid);
   mReaderSemaphore->CloseHandle();
   mWriterSemaphore.reset(
       CrossProcessSemaphore::Create("SharedMemoryStreamChild", 0));
   *aWriterSem = mWriterSemaphore->ShareToProcess(aOtherPid);
   mWriterSemaphore->CloseHandle();
 
-  mResumeReaderCallback = aResumeReaderCallback;
+  mWriterServices = std::move(aWriterServices);
 
   mGood = true;
   return true;
 }
 
 bool CanvasEventRingBuffer::InitReader(
     const ipc::SharedMemoryBasic::Handle& aReadHandle,
     const CrossProcessSemaphoreHandle& aReaderSem,
-    const CrossProcessSemaphoreHandle& aWriterSem) {
+    const CrossProcessSemaphoreHandle& aWriterSem,
+    UniquePtr<ReaderServices> aReaderServices) {
   mSharedMemory = MakeAndAddRef<ipc::SharedMemoryBasic>();
   if (NS_WARN_IF(!mSharedMemory->SetHandle(
           aReadHandle, ipc::SharedMemory::RightsReadWrite)) ||
       NS_WARN_IF(!mSharedMemory->Map(kShmemSize))) {
     return false;
   }
 
   mSharedMemory->CloseHandle();
 
   mBuf = static_cast<char*>(mSharedMemory->memory());
   mRead = reinterpret_cast<ReadFooter*>(mBuf + kStreamSize);
   mWrite = reinterpret_cast<WriteFooter*>(mBuf + kStreamSize + kCacheLineSize);
   mReaderSemaphore.reset(CrossProcessSemaphore::Create(aReaderSem));
   mReaderSemaphore->CloseHandle();
   mWriterSemaphore.reset(CrossProcessSemaphore::Create(aWriterSem));
   mWriterSemaphore->CloseHandle();
+
+  mReaderServices = std::move(aReaderServices);
+
   mGood = true;
   return true;
 }
 
 bool CanvasEventRingBuffer::WaitForAndRecalculateAvailableSpace() {
   uint32_t bufPos = mOurCount % kStreamSize;
   uint32_t maxToWrite = kStreamSize - bufPos;
   mAvailable = std::min(maxToWrite, WaitForBytesToWrite());
@@ -236,17 +237,17 @@ void CanvasEventRingBuffer::CheckAndSign
 
           MOZ_ASSERT(mRead->state == State::Stopped);
           continue;
         }
         return;
       case State::Stopped:
         if (mRead->count != mOurCount) {
           mRead->state = State::Processing;
-          mResumeReaderCallback();
+          mWriterServices->ResumeReader();
         }
         return;
       default:
         MOZ_ASSERT_UNREACHABLE("Invalid waiting state.");
         return;
     }
   } while (true);
 }
@@ -263,50 +264,58 @@ bool CanvasEventRingBuffer::StopIfEmpty(
     mRead->state = State::Processing;
     return false;
   }
 
   mRead->state = State::Stopped;
   return true;
 }
 
-bool CanvasEventRingBuffer::WaitForDataToRead(TimeDuration aTimeout) {
+bool CanvasEventRingBuffer::WaitForDataToRead(TimeDuration aTimeout,
+                                              int32_t aRetryCount) {
   uint32_t spinCount = kMaxSpinCount;
   do {
     if (HasDataToRead()) {
       return true;
     }
   } while (--spinCount != 0);
 
   // Double-check that the writer isn't waiting.
   CheckAndSignalWriter();
   mRead->state = State::AboutToWait;
   if (HasDataToRead()) {
     mRead->state = State::Processing;
     return true;
   }
 
   mRead->state = State::Waiting;
-  if (!mReaderSemaphore->Wait(Some(aTimeout))) {
-    // We have to use compareExchange here because the writer can change our
-    // state if we are waiting.
-    if (!mRead->state.compareExchange(State::Waiting, State::Stopped)) {
+  do {
+    if (mReaderSemaphore->Wait(Some(aTimeout))) {
       MOZ_RELEASE_ASSERT(HasDataToRead());
-      MOZ_RELEASE_ASSERT(mRead->state == State::Processing);
-      // The writer has just signaled us, so consume it before returning
-      MOZ_ALWAYS_TRUE(mReaderSemaphore->Wait());
       return true;
     }
 
-    return false;
+    if (mReaderServices->WriterClosed()) {
+      // Something has gone wrong on the writing side, just return false so
+      // that we can hopefully recover.
+      return false;
+    }
+  } while (aRetryCount-- > 0);
+
+  // We have to use compareExchange here because the writer can change our
+  // state if we are waiting. signaled
+  if (!mRead->state.compareExchange(State::Waiting, State::Stopped)) {
+    MOZ_RELEASE_ASSERT(HasDataToRead());
+    MOZ_RELEASE_ASSERT(mRead->state == State::Processing);
+    // The writer has just signaled us, so consume it before returning
+    MOZ_ALWAYS_TRUE(mReaderSemaphore->Wait());
+    return true;
   }
 
-  MOZ_RELEASE_ASSERT(HasDataToRead());
-
-  return true;
+  return false;
 }
 
 int32_t CanvasEventRingBuffer::ReadNextEvent() {
   int32_t nextEvent;
   ReadElement(*this, nextEvent);
   while (nextEvent == kCheckpointEventType) {
     ReadElement(*this, nextEvent);
   }
@@ -314,19 +323,18 @@ int32_t CanvasEventRingBuffer::ReadNextE
   return nextEvent;
 }
 
 uint32_t CanvasEventRingBuffer::CreateCheckpoint() {
   WriteElement(*this, kCheckpointEventType);
   return mOurCount;
 }
 
-bool CanvasEventRingBuffer::WaitForCheckpoint(uint32_t aCheckpoint,
-                                              TimeDuration aTimeout) {
-  return WaitForReadCount(aCheckpoint, aTimeout);
+bool CanvasEventRingBuffer::WaitForCheckpoint(uint32_t aCheckpoint) {
+  return WaitForReadCount(aCheckpoint, kTimeout, kTimeoutRetryCount);
 }
 
 void CanvasEventRingBuffer::CheckAndSignalWriter() {
   do {
     switch (mWrite->state) {
       case State::Processing:
         return;
       case State::AboutToWait:
@@ -342,17 +350,18 @@ void CanvasEventRingBuffer::CheckAndSign
       default:
         MOZ_ASSERT_UNREACHABLE("Invalid waiting state.");
         return;
     }
   } while (true);
 }
 
 bool CanvasEventRingBuffer::WaitForReadCount(uint32_t aReadCount,
-                                             TimeDuration aTimeout) {
+                                             TimeDuration aTimeout,
+                                             int32_t aRetryCount) {
   uint32_t requiredDifference = mOurCount - aReadCount;
   uint32_t spinCount = kMaxSpinCount;
   do {
     if (mOurCount - mRead->count <= requiredDifference) {
       return true;
     }
   } while (--spinCount != 0);
 
@@ -362,40 +371,45 @@ bool CanvasEventRingBuffer::WaitForReadC
   if (mOurCount - mRead->count <= requiredDifference) {
     mWrite->state = State::Processing;
     return true;
   }
 
   mWrite->requiredDifference = requiredDifference;
   mWrite->state = State::Waiting;
 
-  uint32_t lastReadCount = mRead->count;
-  while (!mWriterSemaphore->Wait(Some(aTimeout))) {
-    if (NS_WARN_IF(mRead->count == lastReadCount)) {
+  do {
+    if (mWriterSemaphore->Wait(Some(aTimeout))) {
+      MOZ_ASSERT(mOurCount - mRead->count <= requiredDifference);
+      return true;
+    }
+
+    if (mWriterServices->ReaderClosed()) {
+      // Something has gone wrong on the reading side, just return false so
+      // that we can hopefully recover.
       return false;
     }
-    lastReadCount = mRead->count;
-  }
+  } while (aRetryCount-- > 0);
 
-  MOZ_ASSERT(mOurCount - mRead->count <= requiredDifference);
-  return true;
+  return false;
 }
 
 uint32_t CanvasEventRingBuffer::WaitForBytesToWrite() {
   uint32_t streamFullReadCount = mOurCount - kStreamSize;
-  if (!WaitForReadCount(streamFullReadCount + 1, kWriterTimeout)) {
+  if (!WaitForReadCount(streamFullReadCount + 1, kTimeout,
+                        kTimeoutRetryCount)) {
     mGood = false;
     return 0;
   }
 
   return mRead->count - streamFullReadCount;
 }
 
 uint32_t CanvasEventRingBuffer::WaitForBytesToRead() {
-  if (!WaitForDataToRead(kReaderTimeout)) {
+  if (!WaitForDataToRead(kTimeout, kTimeoutRetryCount)) {
     return 0;
   }
 
   return mWrite->count - mOurCount;
 }
 
 void CanvasEventRingBuffer::ReturnWrite(const char* aData, size_t aSize) {
   uint32_t writeCount = mRead->returnCount;
--- a/gfx/layers/CanvasDrawEventRecorder.h
+++ b/gfx/layers/CanvasDrawEventRecorder.h
@@ -11,48 +11,86 @@
 #include "mozilla/ipc/CrossProcessSemaphore.h"
 #include "mozilla/ipc/SharedMemoryBasic.h"
 
 namespace mozilla {
 namespace layers {
 
 class CanvasEventRingBuffer final : public gfx::EventRingBuffer {
  public:
+  /**
+   * WriterServices allows consumers of CanvasEventRingBuffer to provide
+   * functions required by the write side of a CanvasEventRingBuffer without
+   * introducing unnecessary dependencies on IPC code.
+   */
+  class WriterServices {
+   public:
+    virtual ~WriterServices() = default;
+
+    /**
+     * @returns true if the reader of the CanvasEventRingBuffer has permanently
+     *          stopped processing, otherwise returns false.
+     */
+    virtual bool ReaderClosed() = 0;
+
+    /**
+     * Causes the reader to resume processing when it is in a stopped state.
+     */
+    virtual void ResumeReader() = 0;
+  };
+
+  /**
+   * ReaderServices allows consumers of CanvasEventRingBuffer to provide
+   * functions required by the read side of a CanvasEventRingBuffer without
+   * introducing unnecessary dependencies on IPC code.
+   */
+  class ReaderServices {
+   public:
+    virtual ~ReaderServices() = default;
+
+    /**
+     * @returns true if the writer of the CanvasEventRingBuffer has permanently
+     *          stopped processing, otherwise returns false.
+     */
+    virtual bool WriterClosed() = 0;
+  };
+
   CanvasEventRingBuffer() {}
 
   /**
    * Initialize the write side of a CanvasEventRingBuffer returning handles to
    * the shared memory for the buffer and the two semaphores for waiting in the
    * reader and the writer.
    *
    * @param aOtherPid process ID to share the handles to
    * @param aReadHandle handle to the shared memory for the buffer
    * @param aReaderSem reading blocked semaphore
    * @param aWriterSem writing blocked semaphore
-   * @param aResumeReaderCallback callback to start the reader when it is has
-   *                             stopped
+   * @param aWriterServices provides functions required by the writer
    * @returns true if initialization succeeds
    */
   bool InitWriter(base::ProcessId aOtherPid,
                   ipc::SharedMemoryBasic::Handle* aReadHandle,
                   CrossProcessSemaphoreHandle* aReaderSem,
                   CrossProcessSemaphoreHandle* aWriterSem,
-                  const std::function<void()>& aResumeReaderCallback);
+                  UniquePtr<WriterServices> aWriterServices);
 
   /**
    * Initialize the read side of a CanvasEventRingBuffer.
    *
    * @param aReadHandle handle to the shared memory for the buffer
    * @param aReaderSem reading blocked semaphore
    * @param aWriterSem writing blocked semaphore
+   * @param aReaderServices provides functions required by the reader
    * @returns true if initialization succeeds
    */
   bool InitReader(const ipc::SharedMemoryBasic::Handle& aReadHandle,
                   const CrossProcessSemaphoreHandle& aReaderSem,
-                  const CrossProcessSemaphoreHandle& aWriterSem);
+                  const CrossProcessSemaphoreHandle& aWriterSem,
+                  UniquePtr<ReaderServices> aReaderServices);
 
   bool good() const final { return mGood; }
 
   void write(const char* const aData, const size_t aSize) final;
 
   bool HasDataToRead();
 
   /*
@@ -61,21 +99,25 @@ class CanvasEventRingBuffer final : publ
    * translation at a later point. If it returns false the writer will start the
    * translation again when more data is written.
    *
    * @returns true if stopped
    */
   bool StopIfEmpty();
 
   /*
-   * Waits for the given TimeDuration for data to become available.
+   * Waits for data to become available. This will wait for aTimeout duration
+   * aRetryCount number of times, checking to see if the other side is closed in
+   * between each one.
    *
+   * @param aTimeout duration to wait
+   * @param aRetryCount number of times to retry
    * @returns true if data is available to read.
    */
-  bool WaitForDataToRead(TimeDuration aTimeout);
+  bool WaitForDataToRead(TimeDuration aTimeout, int32_t aRetryCount);
 
   int32_t ReadNextEvent();
 
   void read(char* const aOut, const size_t aSize) final;
 
   /**
    * Writes a checkpoint event to the buffer.
    *
@@ -83,20 +125,20 @@ class CanvasEventRingBuffer final : publ
    */
   uint32_t CreateCheckpoint();
 
   /**
    * Waits until the given checkpoint has been read from the buffer.
    *
    * @params aCheckpoint the checkpoint to wait for
    * @params aTimeout duration to wait while reader is not active
-   * @returns true if the checkpoint was reached, false if the read count has
-   *          not changed in the aTimeout duration.
+   * @returns true if the checkpoint was reached, false if the reader is closed
+   *          or we timeout.
    */
-  bool WaitForCheckpoint(uint32_t aCheckpoint, TimeDuration aTimeout);
+  bool WaitForCheckpoint(uint32_t aCheckpoint);
 
   /**
    * Used to send data back to the writer. This is done through the same shared
    * memory so the writer must wait and read the response after it has submitted
    * the event that uses this.
    *
    * @param aData the data to be written back to the writer
    * @param aSize the number of chars to write
@@ -150,17 +192,18 @@ class CanvasEventRingBuffer final : publ
     Atomic<State, ReleaseAcquire> state;
   };
 
   CanvasEventRingBuffer(const CanvasEventRingBuffer&) = delete;
   void operator=(const CanvasEventRingBuffer&) = delete;
 
   void IncrementWriteCountBy(uint32_t aCount);
 
-  bool WaitForReadCount(uint32_t aReadCount, TimeDuration aTimeout);
+  bool WaitForReadCount(uint32_t aReadCount, TimeDuration aTimeout,
+                        int32_t aRetryCount);
 
   bool WaitForAndRecalculateAvailableData();
 
   void UpdateReadTotalsBy(uint32_t aCount);
   void IncrementReadCountBy(uint32_t aCount);
 
   void CheckAndSignalReader();
 
@@ -168,35 +211,36 @@ class CanvasEventRingBuffer final : publ
 
   uint32_t WaitForBytesToWrite();
 
   uint32_t WaitForBytesToRead();
 
   RefPtr<ipc::SharedMemoryBasic> mSharedMemory;
   UniquePtr<CrossProcessSemaphore> mReaderSemaphore;
   UniquePtr<CrossProcessSemaphore> mWriterSemaphore;
-  std::function<void()> mResumeReaderCallback;
+  UniquePtr<WriterServices> mWriterServices;
+  UniquePtr<ReaderServices> mReaderServices;
   char* mBuf = nullptr;
   uint32_t mOurCount = 0;
   WriteFooter* mWrite = nullptr;
   ReadFooter* mRead = nullptr;
   bool mGood = false;
 };
 
 class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate {
  public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(CanvasDrawEventRecorder, final)
   explicit CanvasDrawEventRecorder(){};
 
   bool Init(base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aHandle,
             CrossProcessSemaphoreHandle* aReaderSem,
             CrossProcessSemaphoreHandle* aWriterSem,
-            const std::function<void()>& aResumeReaderCallback) {
+            UniquePtr<CanvasEventRingBuffer::WriterServices> aWriterServices) {
     return mOutputStream.InitWriter(aOtherPid, aHandle, aReaderSem, aWriterSem,
-                                    aResumeReaderCallback);
+                                    std::move(aWriterServices));
   }
 
   void RecordEvent(const gfx::RecordedEvent& aEvent) final {
     if (!mOutputStream.good()) {
       return;
     }
 
     aEvent.RecordToStream(mOutputStream);
@@ -209,22 +253,21 @@ class CanvasDrawEventRecorder final : pu
   }
 
   uint32_t CreateCheckpoint() { return mOutputStream.CreateCheckpoint(); }
 
   /**
    * Waits until the given checkpoint has been read by the translator.
    *
    * @params aCheckpoint the checkpoint to wait for
-   * @params aTimeout duration to wait while translator is not actively reading
-   * @returns true if the checkpoint was reached, false if the translator has
-   *          not been active in the aTimeout duration.
+   * @returns true if the checkpoint was reached, false if the reader is closed
+   *          or we timeout.
    */
-  bool WaitForCheckpoint(uint32_t aCheckpoint, TimeDuration aTimeout) {
-    return mOutputStream.WaitForCheckpoint(aCheckpoint, aTimeout);
+  bool WaitForCheckpoint(uint32_t aCheckpoint) {
+    return mOutputStream.WaitForCheckpoint(aCheckpoint);
   }
 
  private:
   CanvasEventRingBuffer mOutputStream;
 };
 
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/CanvasTranslator.cpp
+++ b/gfx/layers/CanvasTranslator.cpp
@@ -49,34 +49,38 @@ static TextureData* CreateTextureData(Te
   return textureData;
 }
 
 /* static */
 UniquePtr<CanvasTranslator> CanvasTranslator::Create(
     const TextureType& aTextureType,
     const ipc::SharedMemoryBasic::Handle& aReadHandle,
     const CrossProcessSemaphoreHandle& aReaderSem,
-    const CrossProcessSemaphoreHandle& aWriterSem) {
+    const CrossProcessSemaphoreHandle& aWriterSem,
+    UniquePtr<CanvasEventRingBuffer::ReaderServices> aReaderServices) {
   TextureData* textureData = CreateTextureData(aTextureType, gfx::IntSize(1, 1),
                                                gfx::SurfaceFormat::B8G8R8A8);
   textureData->Lock(OpenMode::OPEN_READ_WRITE);
   RefPtr<gfx::DrawTarget> dt = textureData->BorrowDrawTarget();
-  return UniquePtr<CanvasTranslator>(new CanvasTranslator(
-      aTextureType, textureData, dt, aReadHandle, aReaderSem, aWriterSem));
+  return UniquePtr<CanvasTranslator>(
+      new CanvasTranslator(aTextureType, textureData, dt, aReadHandle,
+                           aReaderSem, aWriterSem, std::move(aReaderServices)));
 }
 
 CanvasTranslator::CanvasTranslator(
     const TextureType& aTextureType, TextureData* aTextureData,
     gfx::DrawTarget* aDT, const ipc::SharedMemoryBasic::Handle& aReadHandle,
     const CrossProcessSemaphoreHandle& aReaderSem,
-    const CrossProcessSemaphoreHandle& aWriterSem)
+    const CrossProcessSemaphoreHandle& aWriterSem,
+    UniquePtr<CanvasEventRingBuffer::ReaderServices> aReaderServices)
     : gfx::InlineTranslator(aDT),
       mTextureType(aTextureType),
       mReferenceTextureData(aTextureData) {
-  mStream.InitReader(aReadHandle, aReaderSem, aWriterSem);
+  mStream.InitReader(aReadHandle, aReaderSem, aWriterSem,
+                     std::move(aReaderServices));
 }
 
 CanvasTranslator::~CanvasTranslator() { mReferenceTextureData->Unlock(); }
 
 bool CanvasTranslator::TranslateRecording() {
   int32_t eventType = mStream.ReadNextEvent();
   while (mStream.good()) {
     bool success = RecordedEvent::DoWithEventFromStream(
@@ -100,17 +104,17 @@ bool CanvasTranslator::TranslateRecordin
     if (!mIsInTransaction) {
       return mStream.StopIfEmpty();
     }
 
     if (!mStream.HasDataToRead()) {
       // We're going to wait for the next event, so take the opportunity to
       // flush the rendering.
       Flush();
-      if (!mStream.WaitForDataToRead(kReadEventTimeout)) {
+      if (!mStream.WaitForDataToRead(kReadEventTimeout, 0)) {
         return true;
       }
     }
 
     eventType = mStream.ReadNextEvent();
   }
 
   mIsValid = false;
--- a/gfx/layers/CanvasTranslator.h
+++ b/gfx/layers/CanvasTranslator.h
@@ -28,23 +28,25 @@ class CanvasTranslator final : public gf
    * Create a canvas translator for a particular TextureType, which translates
    * events from a CanvasEventRingBuffer.
    *
    * @param aTextureType the TextureType the translator will create
    * @param aReadHandle handle to the shared memory for the
    * CanvasEventRingBuffer
    * @param aReaderSem reading blocked semaphore for the CanvasEventRingBuffer
    * @param aWriterSem writing blocked semaphore for the CanvasEventRingBuffer
+   * @param aReaderServices provides functions required by the reader
    * @returns the new CanvasTranslator
    */
   static UniquePtr<CanvasTranslator> Create(
       const TextureType& aTextureType,
       const ipc::SharedMemoryBasic::Handle& aReadHandle,
       const CrossProcessSemaphoreHandle& aReaderSem,
-      const CrossProcessSemaphoreHandle& aWriterSem);
+      const CrossProcessSemaphoreHandle& aWriterSem,
+      UniquePtr<CanvasEventRingBuffer::ReaderServices> aReaderServices);
 
   ~CanvasTranslator();
 
   bool IsValid() { return mIsValid; }
 
   /**
    * Translates events until no more are available or the end of a transaction
    * If this returns false the caller of this is responsible for re-calling
@@ -178,21 +180,23 @@ class CanvasTranslator final : public gf
    *
    * @param aSurface must match the surface from the SetPreparedMap call
    * @returns the ScopedMap if aSurface matches otherwise nullptr
    */
   UniquePtr<gfx::DataSourceSurface::ScopedMap> GetPreparedMap(
       gfx::ReferencePtr aSurface);
 
  private:
-  CanvasTranslator(const TextureType& aTextureType, TextureData* aTextureData,
-                   gfx::DrawTarget* aDrawTarget,
-                   const ipc::SharedMemoryBasic::Handle& aReadHandle,
-                   const CrossProcessSemaphoreHandle& aReaderSem,
-                   const CrossProcessSemaphoreHandle& aWriterSem);
+  CanvasTranslator(
+      const TextureType& aTextureType, TextureData* aTextureData,
+      gfx::DrawTarget* aDrawTarget,
+      const ipc::SharedMemoryBasic::Handle& aReadHandle,
+      const CrossProcessSemaphoreHandle& aReaderSem,
+      const CrossProcessSemaphoreHandle& aWriterSem,
+      UniquePtr<CanvasEventRingBuffer::ReaderServices> aReaderServices);
 
   void AddSurfaceDescriptor(gfx::ReferencePtr aRefPtr,
                             TextureData* atextureData);
 
   bool HandleExtensionEvent(int32_t aType);
 
   CanvasEventRingBuffer mStream;
   TextureType mTextureType = TextureType::Unknown;
--- a/gfx/layers/ipc/CanvasChild.cpp
+++ b/gfx/layers/ipc/CanvasChild.cpp
@@ -12,38 +12,51 @@
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/gfx/Point.h"
 #include "mozilla/layers/CanvasDrawEventRecorder.h"
 #include "RecordedCanvasEventImpl.h"
 
 namespace mozilla {
 namespace layers {
 
-static const TimeDuration kLockWaitTimeout =
-    TimeDuration::FromMilliseconds(100);
-static const TimeDuration kGetDataTimeout = TimeDuration::FromMilliseconds(500);
+class RingBufferWriterServices final
+    : public CanvasEventRingBuffer::WriterServices {
+ public:
+  explicit RingBufferWriterServices(RefPtr<CanvasChild> aCanvasChild)
+      : mCanvasChild(std::move(aCanvasChild)) {}
+
+  ~RingBufferWriterServices() final = default;
+
+  bool ReaderClosed() final {
+    return mCanvasChild->GetIPCChannel()->Unsound_IsClosed();
+  }
+
+  void ResumeReader() final { mCanvasChild->ResumeTranslation(); }
+
+ private:
+  RefPtr<CanvasChild> mCanvasChild;
+};
 
 CanvasChild::CanvasChild(Endpoint<PCanvasChild>&& aEndpoint) {
   aEndpoint.Bind(this);
   mCanSend = true;
 }
 
 CanvasChild::~CanvasChild() {}
 
 void CanvasChild::EnsureRecorder(TextureType aTextureType) {
   if (!mRecorder) {
     MOZ_ASSERT(mTextureType == TextureType::Unknown);
     mTextureType = aTextureType;
     mRecorder = MakeAndAddRef<CanvasDrawEventRecorder>();
     SharedMemoryBasic::Handle handle;
     CrossProcessSemaphoreHandle readerSem;
     CrossProcessSemaphoreHandle writerSem;
-    RefPtr<CanvasChild> thisRef = this;
     mRecorder->Init(OtherPid(), &handle, &readerSem, &writerSem,
-                    [cc = std::move(thisRef)] { cc->ResumeTranslation(); });
+                    MakeUnique<RingBufferWriterServices>(this));
 
     if (mCanSend) {
       Unused << SendCreateTranslator(mTextureType, handle, readerSem,
                                      writerSem);
     }
   }
 
   MOZ_RELEASE_ASSERT(mTextureType == aTextureType,
@@ -69,18 +82,17 @@ void CanvasChild::Destroy() { Close(); }
 void CanvasChild::OnTextureWriteLock() {
   mHasOutstandingWriteLock = true;
   mLastWriteLockCheckpoint = mRecorder->CreateCheckpoint();
 }
 
 void CanvasChild::OnTextureForwarded() {
   if (mHasOutstandingWriteLock) {
     mRecorder->RecordEvent(RecordedCanvasFlush());
-    if (!mRecorder->WaitForCheckpoint(mLastWriteLockCheckpoint,
-                                      kLockWaitTimeout)) {
+    if (!mRecorder->WaitForCheckpoint(mLastWriteLockCheckpoint)) {
       gfxWarning() << "Timed out waiting for last write lock to be processed.";
     }
 
     mHasOutstandingWriteLock = false;
   }
 }
 
 void CanvasChild::EnsureBeginTransaction() {
@@ -132,17 +144,17 @@ already_AddRefed<gfx::DataSourceSurface>
                                                       dataFormatWidth);
   if (!dataSurface) {
     gfxWarning() << "Failed to create DataSourceSurface.";
     return nullptr;
   }
   gfx::DataSourceSurface::ScopedMap map(dataSurface,
                                         gfx::DataSourceSurface::READ_WRITE);
   char* dest = reinterpret_cast<char*>(map.GetData());
-  if (!mRecorder->WaitForCheckpoint(checkpoint, kGetDataTimeout)) {
+  if (!mRecorder->WaitForCheckpoint(checkpoint)) {
     gfxWarning() << "Timed out preparing data for DataSourceSurface.";
     return dataSurface.forget();
   }
 
   mRecorder->RecordEvent(RecordedGetDataForSurface(aSurface));
   mRecorder->ReturnRead(dest, ssSize.height * dataFormatWidth);
 
   return dataSurface.forget();
--- a/gfx/layers/ipc/CanvasParent.cpp
+++ b/gfx/layers/ipc/CanvasParent.cpp
@@ -19,16 +19,32 @@
 
 bool NS_IsInCanvasThread() {
   return mozilla::layers::CanvasParent::IsInCanvasThread();
 }
 
 namespace mozilla {
 namespace layers {
 
+class RingBufferReaderServices final
+    : public CanvasEventRingBuffer::ReaderServices {
+ public:
+  explicit RingBufferReaderServices(RefPtr<CanvasParent> aCanvasParent)
+      : mCanvasParent(std::move(aCanvasParent)) {}
+
+  ~RingBufferReaderServices() final = default;
+
+  bool WriterClosed() final {
+    return mCanvasParent->GetIPCChannel()->Unsound_IsClosed();
+  }
+
+ private:
+  RefPtr<CanvasParent> mCanvasParent;
+};
+
 static base::Thread* sCanvasThread = nullptr;
 static StaticRefPtr<nsIThreadPool> sCanvasWorkers;
 static bool sShuttingDown = false;
 
 static MessageLoop* CanvasPlaybackLoop() {
   if (!sCanvasThread && !sShuttingDown) {
     MOZ_ASSERT(NS_IsInCompositorThread());
     base::Thread* canvasThread = new base::Thread("Canvas");
@@ -107,18 +123,19 @@ void CanvasParent::Bind(Endpoint<PCanvas
   mSelfRef = this;
 }
 
 mozilla::ipc::IPCResult CanvasParent::RecvCreateTranslator(
     const TextureType& aTextureType,
     const ipc::SharedMemoryBasic::Handle& aReadHandle,
     const CrossProcessSemaphoreHandle& aReaderSem,
     const CrossProcessSemaphoreHandle& aWriterSem) {
-  mTranslator = CanvasTranslator::Create(aTextureType, aReadHandle, aReaderSem,
-                                         aWriterSem);
+  mTranslator = CanvasTranslator::Create(
+      aTextureType, aReadHandle, aReaderSem, aWriterSem,
+      MakeUnique<RingBufferReaderServices>(this));
   return RecvResumeTranslation();
 }
 
 ipc::IPCResult CanvasParent::RecvResumeTranslation() {
   MOZ_ASSERT(mTranslator);
 
   if (!mTranslator->IsValid()) {
     return IPC_FAIL(this, "Canvas Translation failed.");
@@ -132,28 +149,47 @@ ipc::IPCResult CanvasParent::RecvResumeT
 void CanvasParent::PostStartTranslationTask(uint32_t aDispatchFlags) {
   if (sShuttingDown) {
     return;
   }
 
   RefPtr<nsIThreadPool> canvasWorkers = GetCanvasWorkers();
   RefPtr<Runnable> runnable = NewRunnableMethod(
       "CanvasParent::StartTranslation", this, &CanvasParent::StartTranslation);
+  mTranslating = true;
   canvasWorkers->Dispatch(runnable.forget(), aDispatchFlags);
 }
 
 void CanvasParent::StartTranslation() {
-  if (!mTranslator->TranslateRecording()) {
+  if (!mTranslator->TranslateRecording() &&
+      !GetIPCChannel()->Unsound_IsClosed()) {
     PostStartTranslationTask(nsIThread::DISPATCH_AT_END);
+    return;
   }
+
+  mTranslating = false;
 }
 
 UniquePtr<SurfaceDescriptor>
 CanvasParent::LookupSurfaceDescriptorForClientDrawTarget(
     const uintptr_t aDrawTarget) {
   return mTranslator->WaitForSurfaceDescriptor(
       reinterpret_cast<void*>(aDrawTarget));
 }
 
-void CanvasParent::DeallocPCanvasParent() { mSelfRef = nullptr; }
+void CanvasParent::ActorDestroy(ActorDestroyReason why) {
+  while (mTranslating) {
+  }
+
+  if (mTranslator) {
+    mTranslator = nullptr;
+  }
+}
+
+void CanvasParent::ActorDealloc() {
+  MOZ_DIAGNOSTIC_ASSERT(!mTranslating);
+  MOZ_DIAGNOSTIC_ASSERT(!mTranslator);
+
+  mSelfRef = nullptr;
+}
 
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/ipc/CanvasParent.h
+++ b/gfx/layers/ipc/CanvasParent.h
@@ -56,19 +56,19 @@ class CanvasParent final : public PCanva
       const CrossProcessSemaphoreHandle& aWriterSem);
 
   /**
    * Used to tell the CanvasTranslator to start translating again after it has
    * stopped due to a timeout waiting for events.
    */
   ipc::IPCResult RecvResumeTranslation();
 
-  void ActorDestroy(ActorDestroyReason why) final {}
+  void ActorDestroy(ActorDestroyReason why) final;
 
-  void DeallocPCanvasParent();
+  void ActorDealloc() final;
 
   /**
    * Used by the compositor thread to get the SurfaceDescriptor associated with
    * the DrawTarget from another process.
    *
    * @param aDrawTarget the key to find the TextureData
    * @returns the SurfaceDescriptor associated with the key
    */
@@ -84,14 +84,15 @@ class CanvasParent final : public PCanva
   void Bind(Endpoint<PCanvasParent>&& aEndpoint);
 
   void PostStartTranslationTask(uint32_t aDispatchFlags);
 
   void StartTranslation();
 
   RefPtr<CanvasParent> mSelfRef;
   UniquePtr<CanvasTranslator> mTranslator;
+  volatile bool mTranslating = false;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif  // mozilla_layers_CanvasParent_h