gecko-dev/dom/media/gmp/GMPVideoDecoderParent.cpp
Chris Pearce 0901ad8256 Bug 1315850 - Ask the GMPService for the GMP thread in GMPParent::ChildTerminated. r=gerald
When we shutdown the browser while the GMPService is active we can end up
leaking a GMPParent, GeckoMediaPluginServiceParent, and a Runnable. I tracked
this down to the runnable dispatched to the GMP thread in
GMPParent::ChildTerminated(). The dispatch of this runnable is failing as we
are dispatching the runnable to a reference of the GMP thread which we have
previously acquired, but that thread is now shutdown. So the dispatch fails,
and if you look in nsThread::DispatchInternal() you'll see that we deliberately
leak the runnable if dispatch fails! The runnable leaking means that the
references it holds to the GMPParent and the GMP service parent leak.

The solution in this patch is to not cache a reference to the GMP thread on the
GMPParent; instead we re-request the GMP thread from the GMPService when we
want it. This means that in the case where the browser is shutting down,
GMPParent::GMPThread() will return null, and we'll not leak the runnable. We'll
then follow the (hacky) shutdown path added in bug 1163239.

We also need to change GMPParent::GMPThread() and GMPContentParent::GMPThread()
to return a reference to the GMP thread with a refcount held on it, in order
to ensure we don't race with the GMP service shutting down the GMP thread
while we're trying to dispatch to in on shutdown.

MozReview-Commit-ID: CXv9VZqTRzY

--HG--
extra : rebase_source : e507e48ee633cad8911287fb7296bbb1679a7bcb
2017-03-24 13:38:00 +13:00

537 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "GMPVideoDecoderParent.h"
#include "mozilla/Logging.h"
#include "mozilla/SizePrintfMacros.h"
#include "mozilla/Unused.h"
#include "nsAutoRef.h"
#include "nsThreadUtils.h"
#include "GMPUtils.h"
#include "GMPVideoEncodedFrameImpl.h"
#include "GMPVideoi420FrameImpl.h"
#include "GMPContentParent.h"
#include "GMPMessageUtils.h"
#include "mozilla/gmp/GMPTypes.h"
#include "nsPrintfCString.h"
namespace mozilla {
#ifdef LOG
#undef LOG
#endif
extern LogModule* GetGMPLog();
#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
#define LOGE(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Error, msg)
#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
namespace gmp {
// States:
// Initial: mIsOpen == false
// on InitDecode success -> Open
// on Shutdown -> Dead
// Open: mIsOpen == true
// on Close -> Dead
// on ActorDestroy -> Dead
// on Shutdown -> Dead
// Dead: mIsOpen == false
GMPVideoDecoderParent::GMPVideoDecoderParent(GMPContentParent* aPlugin)
: GMPSharedMemManager(aPlugin)
, mIsOpen(false)
, mShuttingDown(false)
, mActorDestroyed(false)
, mIsAwaitingResetComplete(false)
, mIsAwaitingDrainComplete(false)
, mPlugin(aPlugin)
, mCallback(nullptr)
, mVideoHost(this)
, mPluginId(aPlugin->GetPluginId())
, mFrameCount(0)
{
MOZ_ASSERT(mPlugin);
}
GMPVideoDecoderParent::~GMPVideoDecoderParent()
{
}
GMPVideoHostImpl&
GMPVideoDecoderParent::Host()
{
return mVideoHost;
}
// Note: may be called via Terminated()
void
GMPVideoDecoderParent::Close()
{
LOGD(("GMPVideoDecoderParent[%p]::Close()", this));
MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
// Ensure if we've received a Close while waiting for a ResetComplete
// or DrainComplete notification, we'll unblock the caller before processing
// the close. This seems unlikely to happen, but better to be careful.
UnblockResetAndDrain();
// Consumer is done with us; we can shut down. No more callbacks should
// be made to mCallback. Note: do this before Shutdown()!
mCallback = nullptr;
// Let Shutdown mark us as dead so it knows if we had been alive
// In case this is the last reference
RefPtr<GMPVideoDecoderParent> kungfudeathgrip(this);
Release();
Shutdown();
}
nsresult
GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings,
const nsTArray<uint8_t>& aCodecSpecific,
GMPVideoDecoderCallbackProxy* aCallback,
int32_t aCoreCount)
{
LOGD(("GMPVideoDecoderParent[%p]::InitDecode()", this));
if (mActorDestroyed) {
NS_WARNING("Trying to use a destroyed GMP video decoder!");
return NS_ERROR_FAILURE;
}
if (mIsOpen) {
NS_WARNING("Trying to re-init an in-use GMP video decoder!");
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
if (!aCallback) {
return NS_ERROR_FAILURE;
}
mCallback = aCallback;
if (!SendInitDecode(aCodecSettings, aCodecSpecific, aCoreCount)) {
return NS_ERROR_FAILURE;
}
mIsOpen = true;
// Async IPC, we don't have access to a return value.
return NS_OK;
}
static nsCString
CryptoInfo(const GMPUniquePtr<GMPVideoEncodedFrame>& aInputFrame)
{
const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
if (!crypto) {
return EmptyCString();
}
return nsPrintfCString(" kid=%s",
ToHexString(crypto->KeyId(), crypto->KeyIdSize()).get());
}
nsresult
GMPVideoDecoderParent::Decode(GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame,
bool aMissingFrames,
const nsTArray<uint8_t>& aCodecSpecificInfo,
int64_t aRenderTimeMs)
{
LOGV(("GMPVideoDecoderParent[%p]::Decode() timestamp=%" PRId64 " keyframe=%d%s",
this, aInputFrame->TimeStamp(),
aInputFrame->FrameType() == kGMPKeyFrame,
CryptoInfo(aInputFrame).get()));
if (!mIsOpen) {
LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; dead GMPVideoDecoder", this));
NS_WARNING("Trying to use an dead GMP video decoder");
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
GMPUniquePtr<GMPVideoEncodedFrameImpl> inputFrameImpl(
static_cast<GMPVideoEncodedFrameImpl*>(aInputFrame.release()));
// Very rough kill-switch if the plugin stops processing. If it's merely
// hung and continues, we'll come back to life eventually.
// 3* is because we're using 3 buffers per frame for i420 data for now.
if ((NumInUse(GMPSharedMem::kGMPFrameData) > 3*GMPSharedMem::kGMPBufLimit) ||
(NumInUse(GMPSharedMem::kGMPEncodedData) > GMPSharedMem::kGMPBufLimit)) {
LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; shmem buffer limit hit frame=%d encoded=%d",
this, NumInUse(GMPSharedMem::kGMPFrameData), NumInUse(GMPSharedMem::kGMPEncodedData)));
return NS_ERROR_FAILURE;
}
GMPVideoEncodedFrameData frameData;
inputFrameImpl->RelinquishFrameData(frameData);
if (!SendDecode(frameData,
aMissingFrames,
aCodecSpecificInfo,
aRenderTimeMs)) {
LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; SendDecode() failure.", this));
return NS_ERROR_FAILURE;
}
mFrameCount++;
// Async IPC, we don't have access to a return value.
return NS_OK;
}
nsresult
GMPVideoDecoderParent::Reset()
{
LOGD(("GMPVideoDecoderParent[%p]::Reset()", this));
if (!mIsOpen) {
NS_WARNING("Trying to use an dead GMP video decoder");
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
if (!SendReset()) {
return NS_ERROR_FAILURE;
}
mIsAwaitingResetComplete = true;
RefPtr<GMPVideoDecoderParent> self(this);
nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([self]() -> void
{
LOGD(("GMPVideoDecoderParent[%p]::ResetCompleteTimeout() timed out waiting for ResetComplete", self.get()));
self->mResetCompleteTimeout = nullptr;
LogToBrowserConsole(NS_LITERAL_STRING("GMPVideoDecoderParent timed out waiting for ResetComplete()"));
});
CancelResetCompleteTimeout();
nsCOMPtr<nsIThread> thread = mPlugin->GMPThread();
mResetCompleteTimeout = SimpleTimer::Create(task, 5000, thread);
// Async IPC, we don't have access to a return value.
return NS_OK;
}
void
GMPVideoDecoderParent::CancelResetCompleteTimeout()
{
if (mResetCompleteTimeout) {
mResetCompleteTimeout->Cancel();
mResetCompleteTimeout = nullptr;
}
}
nsresult
GMPVideoDecoderParent::Drain()
{
LOGD(("GMPVideoDecoderParent[%p]::Drain() frameCount=%d", this, mFrameCount));
if (!mIsOpen) {
NS_WARNING("Trying to use an dead GMP video decoder");
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
if (!SendDrain()) {
return NS_ERROR_FAILURE;
}
mIsAwaitingDrainComplete = true;
// Async IPC, we don't have access to a return value.
return NS_OK;
}
const nsCString&
GMPVideoDecoderParent::GetDisplayName() const
{
if (!mIsOpen) {
NS_WARNING("Trying to use an dead GMP video decoder");
}
MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
return mPlugin->GetDisplayName();
}
// Note: Consider keeping ActorDestroy sync'd up when making changes here.
nsresult
GMPVideoDecoderParent::Shutdown()
{
LOGD(("GMPVideoDecoderParent[%p]::Shutdown()", this));
MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
if (mShuttingDown) {
return NS_OK;
}
mShuttingDown = true;
// Ensure if we've received a shutdown while waiting for a ResetComplete
// or DrainComplete notification, we'll unblock the caller before processing
// the shutdown.
UnblockResetAndDrain();
// Notify client we're gone! Won't occur after Close()
if (mCallback) {
mCallback->Terminated();
mCallback = nullptr;
}
mIsOpen = false;
if (!mActorDestroyed) {
Unused << SendDecodingComplete();
}
return NS_OK;
}
// Note: Keep this sync'd up with Shutdown
void
GMPVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
{
LOGD(("GMPVideoDecoderParent[%p]::ActorDestroy reason=%d", this, aWhy));
mIsOpen = false;
mActorDestroyed = true;
// Ensure if we've received a destroy while waiting for a ResetComplete
// or DrainComplete notification, we'll unblock the caller before processing
// the error.
UnblockResetAndDrain();
if (mCallback) {
// May call Close() (and Shutdown()) immediately or with a delay
mCallback->Terminated();
mCallback = nullptr;
}
if (mPlugin) {
// Ignore any return code. It is OK for this to fail without killing the process.
mPlugin->VideoDecoderDestroyed(this);
mPlugin = nullptr;
}
mVideoHost.ActorDestroyed();
MaybeDisconnect(aWhy == AbnormalShutdown);
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame)
{
--mFrameCount;
LOGV(("GMPVideoDecoderParent[%p]::RecvDecoded() timestamp=%" PRId64 " frameCount=%d",
this, aDecodedFrame.mTimestamp(), mFrameCount));
if (!mCallback) {
return IPC_FAIL_NO_REASON(this);
}
if (!GMPVideoi420FrameImpl::CheckFrameData(aDecodedFrame)) {
LOGE(("GMPVideoDecoderParent[%p]::RecvDecoded() "
"timestamp=%" PRId64 " decoded frame corrupt, ignoring",
this, aDecodedFrame.mTimestamp()));
return IPC_FAIL_NO_REASON(this);
}
auto f = new GMPVideoi420FrameImpl(aDecodedFrame, &mVideoHost);
// Ignore any return code. It is OK for this to fail without killing the process.
mCallback->Decoded(f);
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId)
{
if (!mCallback) {
return IPC_FAIL_NO_REASON(this);
}
// Ignore any return code. It is OK for this to fail without killing the process.
mCallback->ReceivedDecodedReferenceFrame(aPictureId);
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::RecvReceivedDecodedFrame(const uint64_t& aPictureId)
{
if (!mCallback) {
return IPC_FAIL_NO_REASON(this);
}
// Ignore any return code. It is OK for this to fail without killing the process.
mCallback->ReceivedDecodedFrame(aPictureId);
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::RecvInputDataExhausted()
{
LOGV(("GMPVideoDecoderParent[%p]::RecvInputDataExhausted()", this));
if (!mCallback) {
return IPC_FAIL_NO_REASON(this);
}
// Ignore any return code. It is OK for this to fail without killing the process.
mCallback->InputDataExhausted();
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::RecvDrainComplete()
{
LOGD(("GMPVideoDecoderParent[%p]::RecvDrainComplete() frameCount=%d", this, mFrameCount));
nsAutoString msg;
msg.AppendLiteral("GMPVideoDecoderParent::RecvDrainComplete() outstanding frames=");
msg.AppendInt(mFrameCount);
LogToBrowserConsole(msg);
if (!mCallback) {
// We anticipate shutting down in the middle of a drain in the
// `UnblockResetAndDrain` method, which is called when we shutdown, so
// everything is sunny.
return IPC_OK();
}
if (!mIsAwaitingDrainComplete) {
return IPC_OK();
}
mIsAwaitingDrainComplete = false;
// Ignore any return code. It is OK for this to fail without killing the process.
mCallback->DrainComplete();
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::RecvResetComplete()
{
LOGD(("GMPVideoDecoderParent[%p]::RecvResetComplete()", this));
CancelResetCompleteTimeout();
if (!mCallback) {
// We anticipate shutting down in the middle of a reset in the
// `UnblockResetAndDrain` method, which is called when we shutdown, so
// everything is good if we reach here.
return IPC_OK();
}
if (!mIsAwaitingResetComplete) {
return IPC_OK();
}
mIsAwaitingResetComplete = false;
mFrameCount = 0;
// Ignore any return code. It is OK for this to fail without killing the process.
mCallback->ResetComplete();
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::RecvError(const GMPErr& aError)
{
LOGD(("GMPVideoDecoderParent[%p]::RecvError(error=%d)", this, aError));
if (!mCallback) {
return IPC_FAIL_NO_REASON(this);
}
// Ensure if we've received an error while waiting for a ResetComplete
// or DrainComplete notification, we'll unblock the caller before processing
// the error.
UnblockResetAndDrain();
// Ignore any return code. It is OK for this to fail without killing the process.
mCallback->Error(aError);
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::RecvShutdown()
{
LOGD(("GMPVideoDecoderParent[%p]::RecvShutdown()", this));
Shutdown();
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::RecvParentShmemForPool(Shmem&& aEncodedBuffer)
{
if (aEncodedBuffer.IsWritable()) {
mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData,
aEncodedBuffer);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::AnswerNeedShmem(const uint32_t& aFrameBufferSize,
Shmem* aMem)
{
ipc::Shmem mem;
if (!mVideoHost.SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPFrameData,
aFrameBufferSize,
ipc::SharedMemory::TYPE_BASIC, &mem))
{
LOGE(("%s: Failed to get a shared mem buffer for Child! size %u",
__FUNCTION__, aFrameBufferSize));
return IPC_FAIL_NO_REASON(this);
}
*aMem = mem;
mem = ipc::Shmem();
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPVideoDecoderParent::Recv__delete__()
{
LOGD(("GMPVideoDecoderParent[%p]::Recv__delete__()", this));
if (mPlugin) {
// Ignore any return code. It is OK for this to fail without killing the process.
mPlugin->VideoDecoderDestroyed(this);
mPlugin = nullptr;
}
return IPC_OK();
}
void
GMPVideoDecoderParent::UnblockResetAndDrain()
{
LOGD(("GMPVideoDecoderParent[%p]::UnblockResetAndDrain() "
"awaitingResetComplete=%d awaitingDrainComplete=%d",
this, mIsAwaitingResetComplete, mIsAwaitingDrainComplete));
if (!mCallback) {
MOZ_ASSERT(!mIsAwaitingResetComplete);
MOZ_ASSERT(!mIsAwaitingDrainComplete);
return;
}
if (mIsAwaitingResetComplete) {
mIsAwaitingResetComplete = false;
mCallback->ResetComplete();
}
if (mIsAwaitingDrainComplete) {
mIsAwaitingDrainComplete = false;
mCallback->DrainComplete();
}
CancelResetCompleteTimeout();
}
} // namespace gmp
} // namespace mozilla