Bug 1545416 - Make the remote decoder shutdown async. r=mjf
authorAlex Chronopoulos <achronop@gmail.com>
Thu, 09 May 2019 16:38:54 +0000
changeset 532079 0d0548740e1e4649c9cc5d97b5d837071c35f07a
parent 532078 b97c44f9234fbfb400c4e578bf0052de06ea97a4
child 532080 45c3b589ed49c3aa2dab3baf6f8b3c2a513903ec
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmjf
bugs1545416
milestone68.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 1545416 - Make the remote decoder shutdown async. r=mjf Create a new IPDL message `ShutdownComplete`, direction from parent (RDD) to child (content), to inform the child when the decoder shutdown has been completed. The remote decoder child uses that message to resolve the shutdown promise. Differential Revision: https://phabricator.services.mozilla.com/D28340
dom/media/ipc/GpuDecoderModule.cpp
dom/media/ipc/IRemoteDecoderChild.h
dom/media/ipc/PRemoteDecoder.ipdl
dom/media/ipc/PVideoDecoder.ipdl
dom/media/ipc/RemoteDecoderChild.cpp
dom/media/ipc/RemoteDecoderChild.h
dom/media/ipc/RemoteDecoderModule.cpp
dom/media/ipc/RemoteDecoderParent.cpp
dom/media/ipc/RemoteMediaDataDecoder.cpp
dom/media/ipc/VideoDecoderChild.cpp
dom/media/ipc/VideoDecoderChild.h
dom/media/ipc/VideoDecoderParent.cpp
--- a/dom/media/ipc/GpuDecoderModule.cpp
+++ b/dom/media/ipc/GpuDecoderModule.cpp
@@ -46,20 +46,16 @@ static inline bool IsRemoteAcceleratedCo
 already_AddRefed<MediaDataDecoder> GpuDecoderModule::CreateVideoDecoder(
     const CreateDecoderParams& aParams) {
   if (!StaticPrefs::MediaGpuProcessDecoder() || !aParams.mKnowsCompositor ||
       !IsRemoteAcceleratedCompositor(aParams.mKnowsCompositor)) {
     return mWrapped->CreateVideoDecoder(aParams);
   }
 
   RefPtr<VideoDecoderChild> child = new VideoDecoderChild();
-  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
-      child, VideoDecoderManagerChild::GetManagerThread(),
-      VideoDecoderManagerChild::GetManagerAbstractThread());
-
   SynchronousTask task("InitIPDL");
   MediaResult result(NS_OK);
   VideoDecoderManagerChild::GetManagerThread()->Dispatch(
       NS_NewRunnableFunction(
           "dom::GpuDecoderModule::CreateVideoDecoder",
           [&, child]() {
             AutoCompleteTask complete(&task);
             result = child->InitIPDL(
@@ -71,12 +67,16 @@ already_AddRefed<MediaDataDecoder> GpuDe
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
   }
 
+  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
+      child, VideoDecoderManagerChild::GetManagerThread(),
+      VideoDecoderManagerChild::GetManagerAbstractThread());
+
   return object.forget();
 }
 
 }  // namespace mozilla
--- a/dom/media/ipc/IRemoteDecoderChild.h
+++ b/dom/media/ipc/IRemoteDecoderChild.h
@@ -2,33 +2,34 @@
 /* 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 include_dom_media_ipc_IRemoteDecoderChild_h
 #define include_dom_media_ipc_IRemoteDecoderChild_h
 
 #include "PlatformDecoderModule.h"
+#include "mozilla/TaskQueue.h"
 
 namespace mozilla {
 
 // This interface mirrors the MediaDataDecoder plus a bit (DestroyIPDL)
 // to allow proxying to a remote decoder in RemoteDecoderModule or
 // GpuDecoderModule. RemoteAudioDecoderChild, RemoteVideoDecoderChild,
 // and VideoDecoderChild (for GPU) implement this interface.
 class IRemoteDecoderChild {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IRemoteDecoderChild);
 
   virtual RefPtr<MediaDataDecoder::InitPromise> Init() = 0;
   virtual RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) = 0;
   virtual RefPtr<MediaDataDecoder::DecodePromise> Drain() = 0;
   virtual RefPtr<MediaDataDecoder::FlushPromise> Flush() = 0;
-  virtual void Shutdown() = 0;
+  virtual RefPtr<ShutdownPromise> Shutdown() = 0;
   virtual bool IsHardwareAccelerated(nsACString& aFailureReason) const {
     return false;
   }
   virtual nsCString GetDescriptionName() const = 0;
   virtual void SetSeekThreshold(const media::TimeUnit& aTime) {}
   virtual MediaDataDecoder::ConversionRequired NeedsConversion() const {
     return MediaDataDecoder::ConversionRequired::kNeedNone;
   }
--- a/dom/media/ipc/PRemoteDecoder.ipdl
+++ b/dom/media/ipc/PRemoteDecoder.ipdl
@@ -60,16 +60,17 @@ parent:
 
 child:
   async InitComplete(TrackType trackType,
                      nsCString decoderDescription,
                      ConversionRequired conversion);
   async InitFailed(nsresult reason);
 
   async FlushComplete();
+  async ShutdownComplete();
 
   async Output(DecodedOutputIPDL data);
   async InputExhausted();
   async DrainComplete();
   async Error(nsresult error);
 };
 
 } // namespace mozilla
--- a/dom/media/ipc/PVideoDecoder.ipdl
+++ b/dom/media/ipc/PVideoDecoder.ipdl
@@ -40,16 +40,17 @@ parent:
 
   async __delete__();
 
 child:
   async InitComplete(nsCString decoderDescription, bool hardware, nsCString hardwareReason, uint32_t conversion);
   async InitFailed(nsresult reason);
 
   async FlushComplete();
+  async ShutdownComplete();
 
   // Each output includes a SurfaceDescriptorGPUVideo that represents the decoded
   // frame. This SurfaceDescriptor can be used on the Layers IPDL protocol, but
   // must be released explicitly using DeallocateSurfaceDescriptorGPUVideo
   // on the manager protocol.
   async Output(VideoDataIPDL data);
   async InputExhausted();
   async DrainComplete();
--- a/dom/media/ipc/RemoteDecoderChild.cpp
+++ b/dom/media/ipc/RemoteDecoderChild.cpp
@@ -32,16 +32,18 @@ mozilla::ipc::IPCResult RemoteDecoderChi
 }
 
 mozilla::ipc::IPCResult RemoteDecoderChild::RecvError(const nsresult& aError) {
   AssertOnManagerThread();
   mDecodedData = MediaDataDecoder::DecodedData();
   mDecodePromise.RejectIfExists(aError, __func__);
   mDrainPromise.RejectIfExists(aError, __func__);
   mFlushPromise.RejectIfExists(aError, __func__);
+  mShutdownSelfRef = nullptr;
+  mShutdownPromise.ResolveIfExists(true, __func__);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult RemoteDecoderChild::RecvInitComplete(
     const TrackInfo::TrackType& trackType, const nsCString& aDecoderDescription,
     const ConversionRequired& aConversion) {
   AssertOnManagerThread();
   mInitPromise.ResolveIfExists(trackType, __func__);
@@ -59,21 +61,40 @@ mozilla::ipc::IPCResult RemoteDecoderChi
 }
 
 mozilla::ipc::IPCResult RemoteDecoderChild::RecvFlushComplete() {
   AssertOnManagerThread();
   mFlushPromise.ResolveIfExists(true, __func__);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult RemoteDecoderChild::RecvShutdownComplete() {
+  AssertOnManagerThread();
+  MOZ_ASSERT(mShutdownSelfRef);
+  mShutdownSelfRef = nullptr;
+  mShutdownPromise.ResolveIfExists(true, __func__);
+  return IPC_OK();
+}
+
 void RemoteDecoderChild::ActorDestroy(ActorDestroyReason aWhy) {
+  MOZ_ASSERT(mCanSend);
+  // If the IPC channel is gone pending promises need to be resolved/rejected.
+  if (aWhy == AbnormalShutdown) {
+    MediaResult error(NS_ERROR_DOM_MEDIA_DECODE_ERR);
+    mDecodePromise.RejectIfExists(error, __func__);
+    mDrainPromise.RejectIfExists(error, __func__);
+    mFlushPromise.RejectIfExists(error, __func__);
+    mShutdownSelfRef = nullptr;
+    mShutdownPromise.ResolveIfExists(true, __func__);
+  }
   mCanSend = false;
 }
 
 void RemoteDecoderChild::DestroyIPDL() {
+  AssertOnManagerThread();
   if (mCanSend) {
     PRemoteDecoderChild::Send__delete__(this);
   }
 }
 
 void RemoteDecoderChild::IPDLActorDestroyed() { mIPDLSelfRef = nullptr; }
 
 // MediaDataDecoder methods
@@ -138,23 +159,27 @@ RefPtr<MediaDataDecoder::DecodePromise> 
   if (!mCanSend) {
     return MediaDataDecoder::DecodePromise::CreateAndReject(
         NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
   }
   SendDrain();
   return mDrainPromise.Ensure(__func__);
 }
 
-void RemoteDecoderChild::Shutdown() {
+RefPtr<ShutdownPromise> RemoteDecoderChild::Shutdown() {
   AssertOnManagerThread();
   mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  if (mCanSend) {
-    SendShutdown();
+  mInitialized = false;
+  if (!mCanSend) {
+    return ShutdownPromise::CreateAndResolve(true, __func__);
   }
-  mInitialized = false;
+  SendShutdown();
+  MOZ_ASSERT(!mShutdownSelfRef);
+  mShutdownSelfRef = this;
+  return mShutdownPromise.Ensure(__func__);
 }
 
 bool RemoteDecoderChild::IsHardwareAccelerated(
     nsACString& aFailureReason) const {
   AssertOnManagerThread();
   aFailureReason = mHardwareAcceleratedReason;
   return mIsHardwareAccelerated;
 }
--- a/dom/media/ipc/RemoteDecoderChild.h
+++ b/dom/media/ipc/RemoteDecoderChild.h
@@ -26,26 +26,27 @@ class RemoteDecoderChild : public PRemot
   IPCResult RecvInputExhausted();
   IPCResult RecvDrainComplete();
   IPCResult RecvError(const nsresult& aError);
   IPCResult RecvInitComplete(const TrackInfo::TrackType& trackType,
                              const nsCString& aDecoderDescription,
                              const ConversionRequired& aConversion);
   IPCResult RecvInitFailed(const nsresult& aReason);
   IPCResult RecvFlushComplete();
+  IPCResult RecvShutdownComplete();
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   // IRemoteDecoderChild
   RefPtr<MediaDataDecoder::InitPromise> Init() override;
   RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) override;
   RefPtr<MediaDataDecoder::DecodePromise> Drain() override;
   RefPtr<MediaDataDecoder::FlushPromise> Flush() override;
-  void Shutdown() override;
+  RefPtr<ShutdownPromise> Shutdown() override;
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
   nsCString GetDescriptionName() const override;
   void SetSeekThreshold(const media::TimeUnit& aTime) override;
   MediaDataDecoder::ConversionRequired NeedsConversion() const override;
   void DestroyIPDL() override;
 
   // Called from IPDL when our actor has been destroyed
   void IPDLActorDestroyed();
@@ -62,20 +63,24 @@ class RemoteDecoderChild : public PRemot
 
  private:
   RefPtr<nsIThread> mThread;
 
   MozPromiseHolder<MediaDataDecoder::InitPromise> mInitPromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDrainPromise;
   MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushPromise;
+  MozPromiseHolder<ShutdownPromise> mShutdownPromise;
 
   nsCString mHardwareAcceleratedReason;
   nsCString mDescription;
   bool mInitialized = false;
   bool mIsHardwareAccelerated = false;
   MediaDataDecoder::ConversionRequired mConversion =
       MediaDataDecoder::ConversionRequired::kNeedNone;
+  // Keep this instance alive during SendShutdown RecvShutdownComplete
+  // handshake.
+  RefPtr<RemoteDecoderChild> mShutdownSelfRef;
 };
 
 }  // namespace mozilla
 
 #endif  // include_dom_media_ipc_RemoteDecoderChild_h
--- a/dom/media/ipc/RemoteDecoderModule.cpp
+++ b/dom/media/ipc/RemoteDecoderModule.cpp
@@ -88,61 +88,61 @@ already_AddRefed<MediaDataDecoder> Remot
     const CreateDecoderParams& aParams) {
   LaunchRDDProcessIfNeeded();
 
   if (!mManagerThread) {
     return nullptr;
   }
 
   RefPtr<RemoteAudioDecoderChild> child = new RemoteAudioDecoderChild();
-  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
-      child, mManagerThread,
-      RemoteDecoderManagerChild::GetManagerAbstractThread());
-
   MediaResult result(NS_OK);
   RefPtr<Runnable> task = NS_NewRunnableFunction(
       "RemoteDecoderModule::CreateAudioDecoder", [&, child]() {
         result = child->InitIPDL(aParams.AudioConfig(), aParams.mOptions);
       });
   SyncRunnable::DispatchToThread(mManagerThread, task);
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
   }
 
+  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
+      child, mManagerThread,
+      RemoteDecoderManagerChild::GetManagerAbstractThread());
+
   return object.forget();
 }
 
 already_AddRefed<MediaDataDecoder> RemoteDecoderModule::CreateVideoDecoder(
     const CreateDecoderParams& aParams) {
   LaunchRDDProcessIfNeeded();
 
   if (!mManagerThread) {
     return nullptr;
   }
 
   RefPtr<RemoteVideoDecoderChild> child = new RemoteVideoDecoderChild();
-  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
-      child, mManagerThread,
-      RemoteDecoderManagerChild::GetManagerAbstractThread());
-
   MediaResult result(NS_OK);
   RefPtr<Runnable> task = NS_NewRunnableFunction(
       "RemoteDecoderModule::CreateVideoDecoder", [&, child]() {
         result = child->InitIPDL(aParams.VideoConfig(), aParams.mRate.mValue,
                                  aParams.mOptions);
       });
   SyncRunnable::DispatchToThread(mManagerThread, task);
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
   }
 
+  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
+      child, mManagerThread,
+      RemoteDecoderManagerChild::GetManagerAbstractThread());
+
   return object.forget();
 }
 
 }  // namespace mozilla
--- a/dom/media/ipc/RemoteDecoderParent.cpp
+++ b/dom/media/ipc/RemoteDecoderParent.cpp
@@ -134,17 +134,25 @@ mozilla::ipc::IPCResult RemoteDecoderPar
       [self](const MediaResult& aError) { self->Error(aError); });
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult RemoteDecoderParent::RecvShutdown() {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
-    mDecoder->Shutdown();
+    RefPtr<RemoteDecoderParent> self = this;
+    mDecoder->Shutdown()->Then(
+        mManagerTaskQueue, __func__,
+        [self](const ShutdownPromise::ResolveOrRejectValue& aValue) {
+          MOZ_ASSERT(aValue.IsResolve());
+          if (!self->mDestroyed) {
+            Unused << self->SendShutdownComplete();
+          }
+        });
   }
   mDecoder = nullptr;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult RemoteDecoderParent::RecvSetSeekThreshold(
     const TimeUnit& aTime) {
   MOZ_ASSERT(!mDestroyed);
--- a/dom/media/ipc/RemoteMediaDataDecoder.cpp
+++ b/dom/media/ipc/RemoteMediaDataDecoder.cpp
@@ -16,33 +16,18 @@ using base::Thread;
 RemoteMediaDataDecoder::RemoteMediaDataDecoder(
     IRemoteDecoderChild* aChild, nsIThread* aManagerThread,
     AbstractThread* aAbstractManagerThread)
     : mChild(aChild),
       mManagerThread(aManagerThread),
       mAbstractManagerThread(aAbstractManagerThread) {}
 
 RemoteMediaDataDecoder::~RemoteMediaDataDecoder() {
-  // We're about to be destroyed and drop our ref to
-  // *DecoderChild. Make sure we put a ref into the
-  // task queue for the *DecoderChild thread to keep
-  // it alive until we send the delete message.
-  RefPtr<IRemoteDecoderChild> child = mChild.forget();
-
-  RefPtr<Runnable> task = NS_NewRunnableFunction(
-      "dom::RemoteMediaDataDecoder::~RemoteMediaDataDecoder", [child]() {
-        MOZ_ASSERT(child);
-        child->DestroyIPDL();
-      });
-
-  // Drop our references to the child so that the last ref
-  // always gets released on the manager thread.
-  child = nullptr;
-
-  mManagerThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+  /* Shutdown method should have been called. */
+  MOZ_ASSERT(!mChild);
 }
 
 RefPtr<MediaDataDecoder::InitPromise> RemoteMediaDataDecoder::Init() {
   RefPtr<RemoteMediaDataDecoder> self = this;
   return InvokeAsync(mAbstractManagerThread, __func__,
                      [self]() { return self->mChild->Init(); })
       ->Then(
           mAbstractManagerThread, __func__,
@@ -76,20 +61,25 @@ RefPtr<MediaDataDecoder::FlushPromise> R
 RefPtr<MediaDataDecoder::DecodePromise> RemoteMediaDataDecoder::Drain() {
   RefPtr<RemoteMediaDataDecoder> self = this;
   return InvokeAsync(mAbstractManagerThread, __func__,
                      [self]() { return self->mChild->Drain(); });
 }
 
 RefPtr<ShutdownPromise> RemoteMediaDataDecoder::Shutdown() {
   RefPtr<RemoteMediaDataDecoder> self = this;
-  return InvokeAsync(mAbstractManagerThread, __func__, [self]() {
-    self->mChild->Shutdown();
-    return ShutdownPromise::CreateAndResolve(true, __func__);
-  });
+  return InvokeAsync(mAbstractManagerThread, __func__,
+                     [self]() { return self->mChild->Shutdown(); })
+      ->Then(mAbstractManagerThread, __func__,
+             [self](const ShutdownPromise::ResolveOrRejectValue& aValue) {
+               self->mChild->DestroyIPDL();
+               self->mChild = nullptr;
+               return ShutdownPromise::CreateAndResolveOrReject(aValue,
+                                                                __func__);
+             });
 }
 
 bool RemoteMediaDataDecoder::IsHardwareAccelerated(
     nsACString& aFailureReason) const {
   aFailureReason = mHardwareAcceleratedReason;
   return mIsHardwareAccelerated;
 }
 
--- a/dom/media/ipc/VideoDecoderChild.cpp
+++ b/dom/media/ipc/VideoDecoderChild.cpp
@@ -81,16 +81,18 @@ mozilla::ipc::IPCResult VideoDecoderChil
 }
 
 mozilla::ipc::IPCResult VideoDecoderChild::RecvError(const nsresult& aError) {
   AssertOnManagerThread();
   mDecodedData = MediaDataDecoder::DecodedData();
   mDecodePromise.RejectIfExists(aError, __func__);
   mDrainPromise.RejectIfExists(aError, __func__);
   mFlushPromise.RejectIfExists(aError, __func__);
+  mShutdownSelfRef = nullptr;
+  mShutdownPromise.ResolveIfExists(true, __func__);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VideoDecoderChild::RecvInitComplete(
     const nsCString& aDecoderDescription, const bool& aHardware,
     const nsCString& aHardwareReason, const uint32_t& aConversion) {
   AssertOnManagerThread();
   mInitPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
@@ -110,16 +112,24 @@ mozilla::ipc::IPCResult VideoDecoderChil
 }
 
 mozilla::ipc::IPCResult VideoDecoderChild::RecvFlushComplete() {
   AssertOnManagerThread();
   mFlushPromise.ResolveIfExists(true, __func__);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult VideoDecoderChild::RecvShutdownComplete() {
+  AssertOnManagerThread();
+  MOZ_ASSERT(mShutdownSelfRef);
+  mShutdownSelfRef = nullptr;
+  mShutdownPromise.ResolveIfExists(true, __func__);
+  return IPC_OK();
+}
+
 void VideoDecoderChild::ActorDestroy(ActorDestroyReason aWhy) {
   if (aWhy == AbnormalShutdown) {
     // GPU process crashed, record the time and send back to MFR for telemetry.
     mGPUCrashTime = TimeStamp::Now();
 
     // Defer reporting an error until we've recreated the manager so that
     // it'll be safe for MediaFormatReader to recreate decoders
     RefPtr<VideoDecoderChild> ref = this;
@@ -127,16 +137,18 @@ void VideoDecoderChild::ActorDestroy(Act
         NS_NewRunnableFunction("VideoDecoderChild::ActorDestroy", [=]() {
           MediaResult error(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER);
           error.SetGPUCrashTimeStamp(ref->mGPUCrashTime);
           if (ref->mInitialized) {
             mDecodedData = MediaDataDecoder::DecodedData();
             mDecodePromise.RejectIfExists(error, __func__);
             mDrainPromise.RejectIfExists(error, __func__);
             mFlushPromise.RejectIfExists(error, __func__);
+            mShutdownSelfRef = nullptr;
+            mShutdownPromise.ResolveIfExists(true, __func__);
             // Make sure the next request will be rejected accordingly if ever
             // called.
             mNeedNewDecoder = true;
           } else {
             ref->mInitPromise.RejectIfExists(error, __func__);
           }
         }));
   }
@@ -184,16 +196,17 @@ MediaResult VideoDecoderChild::InitIPDL(
     mCanSend = true;
   }
 
   return success ? MediaResult(NS_OK)
                  : MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, errorDescription);
 }
 
 void VideoDecoderChild::DestroyIPDL() {
+  AssertOnManagerThread();
   if (mCanSend) {
     PVideoDecoderChild::Send__delete__(this);
   }
 }
 
 void VideoDecoderChild::IPDLActorDestroyed() { mIPDLSelfRef = nullptr; }
 
 // MediaDataDecoder methods
@@ -272,23 +285,32 @@ RefPtr<MediaDataDecoder::DecodePromise> 
     return MediaDataDecoder::DecodePromise::CreateAndReject(error, __func__);
   }
   if (mCanSend) {
     SendDrain();
   }
   return mDrainPromise.Ensure(__func__);
 }
 
-void VideoDecoderChild::Shutdown() {
+RefPtr<ShutdownPromise> VideoDecoderChild::Shutdown() {
   AssertOnManagerThread();
   mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  if (mCanSend) {
-    SendShutdown();
+  mInitialized = false;
+  if (mNeedNewDecoder) {
+    MediaResult error(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER);
+    error.SetGPUCrashTimeStamp(mGPUCrashTime);
+    return ShutdownPromise::CreateAndResolve(true, __func__);
   }
-  mInitialized = false;
+  if (!mCanSend) {
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  }
+  SendShutdown();
+  MOZ_ASSERT(!mShutdownSelfRef);
+  mShutdownSelfRef = this;
+  return mShutdownPromise.Ensure(__func__);
 }
 
 bool VideoDecoderChild::IsHardwareAccelerated(
     nsACString& aFailureReason) const {
   AssertOnManagerThread();
   aFailureReason = mHardwareAcceleratedReason;
   return mIsHardwareAccelerated;
 }
--- a/dom/media/ipc/VideoDecoderChild.h
+++ b/dom/media/ipc/VideoDecoderChild.h
@@ -31,25 +31,26 @@ class VideoDecoderChild final : public P
   IPCResult RecvDrainComplete();
   IPCResult RecvError(const nsresult& aError);
   IPCResult RecvInitComplete(const nsCString& aDecoderDescription,
                              const bool& aHardware,
                              const nsCString& aHardwareReason,
                              const uint32_t& aConversion);
   IPCResult RecvInitFailed(const nsresult& aReason);
   IPCResult RecvFlushComplete();
+  IPCResult RecvShutdownComplete();
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   RefPtr<MediaDataDecoder::InitPromise> Init() override;
   RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) override;
   RefPtr<MediaDataDecoder::DecodePromise> Drain() override;
   RefPtr<MediaDataDecoder::FlushPromise> Flush() override;
-  void Shutdown() override;
+  RefPtr<ShutdownPromise> Shutdown() override;
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
   nsCString GetDescriptionName() const override;
   void SetSeekThreshold(const media::TimeUnit& aTime) override;
   MediaDataDecoder::ConversionRequired NeedsConversion() const override;
   void DestroyIPDL() override;
 
   MOZ_IS_CLASS_INIT
   MediaResult InitIPDL(const VideoInfo& aVideoInfo, float aFramerate,
@@ -68,29 +69,33 @@ class VideoDecoderChild final : public P
 
   RefPtr<VideoDecoderChild> mIPDLSelfRef;
   RefPtr<nsIThread> mThread;
 
   MozPromiseHolder<MediaDataDecoder::InitPromise> mInitPromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDrainPromise;
   MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushPromise;
+  MozPromiseHolder<ShutdownPromise> mShutdownPromise;
 
   nsCString mHardwareAcceleratedReason;
   nsCString mDescription;
   bool mCanSend;
   bool mInitialized;
   bool mIsHardwareAccelerated;
   MediaDataDecoder::ConversionRequired mConversion;
 
   // Set to true if the actor got destroyed and we haven't yet notified the
   // caller.
   bool mNeedNewDecoder;
   MediaDataDecoder::DecodedData mDecodedData;
 
   nsCString mBlacklistedD3D11Driver;
   nsCString mBlacklistedD3D9Driver;
   TimeStamp mGPUCrashTime;
+  // Keep this instance alive during SendShutdown RecvShutdownComplete
+  // handshake.
+  RefPtr<VideoDecoderChild> mShutdownSelfRef;
 };
 
 }  // namespace mozilla
 
 #endif  // include_ipc_VideoDecoderChild_h
--- a/dom/media/ipc/VideoDecoderParent.cpp
+++ b/dom/media/ipc/VideoDecoderParent.cpp
@@ -96,17 +96,16 @@ VideoDecoderParent::VideoDecoderParent(
 }
 
 VideoDecoderParent::~VideoDecoderParent() {
   MOZ_COUNT_DTOR(VideoDecoderParent);
 }
 
 void VideoDecoderParent::Destroy() {
   MOZ_ASSERT(OnManagerThread());
-  mDecodeTaskQueue->AwaitShutdownAndIdle();
   mDestroyed = true;
   mIPDLSelfRef = nullptr;
 }
 
 mozilla::ipc::IPCResult VideoDecoderParent::RecvInit() {
   MOZ_ASSERT(OnManagerThread());
   RefPtr<VideoDecoderParent> self = this;
   mDecoder->Init()->Then(
@@ -239,17 +238,25 @@ mozilla::ipc::IPCResult VideoDecoderPare
       [self](const MediaResult& aError) { self->Error(aError); });
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VideoDecoderParent::RecvShutdown() {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
-    mDecoder->Shutdown();
+    RefPtr<VideoDecoderParent> self = this;
+    mDecoder->Shutdown()->Then(
+        mManagerTaskQueue, __func__,
+        [self](const ShutdownPromise::ResolveOrRejectValue& aValue) {
+          MOZ_ASSERT(aValue.IsResolve());
+          if (!self->mDestroyed) {
+            Unused << self->SendShutdownComplete();
+          }
+        });
   }
   mDecoder = nullptr;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VideoDecoderParent::RecvSetSeekThreshold(
     const TimeUnit& aTime) {
   MOZ_ASSERT(!mDestroyed);
@@ -260,19 +267,16 @@ mozilla::ipc::IPCResult VideoDecoderPare
 
 void VideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
     mDecoder->Shutdown();
     mDecoder = nullptr;
   }
-  if (mDecodeTaskQueue) {
-    mDecodeTaskQueue->BeginShutdown();
-  }
 }
 
 void VideoDecoderParent::Error(const MediaResult& aError) {
   MOZ_ASSERT(OnManagerThread());
   if (!mDestroyed) {
     Unused << SendError(aError);
   }
 }