/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* 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 "AllocationPolicy.h" #include "ImageContainer.h" #include "MediaInfo.h" #include "PDMFactory.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/SchedulerGroup.h" #ifdef MOZ_WIDGET_ANDROID # include "mozilla/jni/Utils.h" #endif namespace mozilla { using TrackType = TrackInfo::TrackType; class AllocPolicyImpl::AutoDeallocToken : public Token { public: explicit AutoDeallocToken(const RefPtr& aPolicy) : mPolicy(aPolicy) {} private: ~AutoDeallocToken() { mPolicy->Dealloc(); } RefPtr mPolicy; }; AllocPolicyImpl::AllocPolicyImpl(int aDecoderLimit) : mMaxDecoderLimit(aDecoderLimit), mMonitor("AllocPolicyImpl"), mDecoderLimit(aDecoderLimit) {} AllocPolicyImpl::~AllocPolicyImpl() { RejectAll(); } auto AllocPolicyImpl::Alloc() -> RefPtr { ReentrantMonitorAutoEnter mon(mMonitor); // No decoder limit set. if (mDecoderLimit < 0) { return Promise::CreateAndResolve(new Token(), __func__); } RefPtr p = new PromisePrivate(__func__); mPromises.push(p); ResolvePromise(mon); return p; } void AllocPolicyImpl::Dealloc() { ReentrantMonitorAutoEnter mon(mMonitor); ++mDecoderLimit; ResolvePromise(mon); } void AllocPolicyImpl::ResolvePromise(ReentrantMonitorAutoEnter& aProofOfLock) { MOZ_ASSERT(mDecoderLimit >= 0); if (mDecoderLimit > 0 && !mPromises.empty()) { --mDecoderLimit; RefPtr p = std::move(mPromises.front()); mPromises.pop(); p->Resolve(new AutoDeallocToken(this), __func__); } } void AllocPolicyImpl::RejectAll() { ReentrantMonitorAutoEnter mon(mMonitor); while (!mPromises.empty()) { RefPtr p = std::move(mPromises.front()); mPromises.pop(); p->Reject(true, __func__); } } static int32_t MediaDecoderLimitDefault() { #ifdef MOZ_WIDGET_ANDROID if (jni::GetAPIVersion() < 18) { // Older Android versions have broken support for multiple simultaneous // decoders, see bug 1278574. return 1; } #endif // Otherwise, set no decoder limit. return -1; } StaticMutex GlobalAllocPolicy::sMutex; NotNull GlobalAllocPolicy::Instance(TrackType aTrack) { StaticMutexAutoLock lock(sMutex); if (aTrack == TrackType::kAudioTrack) { static RefPtr sAudioPolicy = []() { SchedulerGroup::Dispatch( TaskCategory::Other, NS_NewRunnableFunction( "GlobalAllocPolicy::GlobalAllocPolicy:Audio", []() { ClearOnShutdown(&sAudioPolicy, ShutdownPhase::XPCOMShutdownThreads); })); return new AllocPolicyImpl(MediaDecoderLimitDefault()); }(); return WrapNotNull(sAudioPolicy.get()); } static RefPtr sVideoPolicy = []() { SchedulerGroup::Dispatch( TaskCategory::Other, NS_NewRunnableFunction( "GlobalAllocPolicy::GlobalAllocPolicy:Audio", []() { ClearOnShutdown(&sVideoPolicy, ShutdownPhase::XPCOMShutdownThreads); })); return new AllocPolicyImpl(MediaDecoderLimitDefault()); }(); return WrapNotNull(sVideoPolicy.get()); } class SingleAllocPolicy::AutoDeallocCombinedToken : public Token { public: AutoDeallocCombinedToken(already_AddRefed aSingleAllocPolicyToken, already_AddRefed aGlobalAllocPolicyToken) : mSingleToken(aSingleAllocPolicyToken), mGlobalToken(aGlobalAllocPolicyToken) {} private: // Release tokens allocated from GlobalAllocPolicy and LocalAllocPolicy // and process next token request if any. ~AutoDeallocCombinedToken() = default; const RefPtr mSingleToken; const RefPtr mGlobalToken; }; auto SingleAllocPolicy::Alloc() -> RefPtr { MOZ_DIAGNOSTIC_ASSERT(MaxDecoderLimit() == 1, "We can only handle at most one token out at a time."); RefPtr self = this; return AllocPolicyImpl::Alloc()->Then( mOwnerThread, __func__, [self](RefPtr aToken) { RefPtr localToken = std::move(aToken); RefPtr p = self->mPendingPromise.Ensure(__func__); GlobalAllocPolicy::Instance(self->mTrack) ->Alloc() ->Then( self->mOwnerThread, __func__, [self, localToken = std::move(localToken)]( RefPtr aToken) mutable { self->mTokenRequest.Complete(); RefPtr combinedToken = new AutoDeallocCombinedToken( localToken.forget(), aToken.forget()); self->mPendingPromise.Resolve(combinedToken, __func__); }, [self]() { self->mTokenRequest.Complete(); self->mPendingPromise.Reject(true, __func__); }) ->Track(self->mTokenRequest); return p; }, []() { return Promise::CreateAndReject(true, __func__); }); } SingleAllocPolicy::~SingleAllocPolicy() { mPendingPromise.RejectIfExists(true, __func__); mTokenRequest.DisconnectIfExists(); } void SingleAllocPolicy::Cancel() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); mPendingPromise.RejectIfExists(true, __func__); mTokenRequest.DisconnectIfExists(); RejectAll(); } AllocationWrapper::AllocationWrapper( already_AddRefed aDecoder, already_AddRefed aToken) : mDecoder(aDecoder), mToken(aToken) { DecoderDoctorLogger::LogConstructionAndBase( "AllocationWrapper", this, static_cast(this)); DecoderDoctorLogger::LinkParentAndChild("AllocationWrapper", this, "decoder", mDecoder.get()); } AllocationWrapper::~AllocationWrapper() { DecoderDoctorLogger::LogDestruction("AllocationWrapper", this); } RefPtr AllocationWrapper::Shutdown() { RefPtr decoder = std::move(mDecoder); RefPtr token = std::move(mToken); return decoder->Shutdown()->Then( GetCurrentSerialEventTarget(), __func__, [token]() { return ShutdownPromise::CreateAndResolve(true, __func__); }); } /* static */ RefPtr AllocationWrapper::CreateDecoder(const CreateDecoderParams& aParams, AllocPolicy* aPolicy) { RefPtr p = (aPolicy ? aPolicy : GlobalAllocPolicy::Instance(aParams.mType)) ->Alloc() ->Then( GetCurrentSerialEventTarget(), __func__, [params = CreateDecoderParamsForAsync(aParams)](RefPtr aToken) { // result may not always be updated by // PDMFactory::CreateDecoder either when the creation // succeeded or failed, as such it must be initialized to a // fatal error by default. MediaResult result = MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, nsPrintfCString("error creating %s decoder", TrackTypeToStr(params.mType))); RefPtr pdm = new PDMFactory(); RefPtr p = pdm->CreateDecoder(params)->Then( GetCurrentSerialEventTarget(), __func__, [aToken](RefPtr&& aDecoder) mutable { RefPtr wrapper = new AllocationWrapper(aDecoder.forget(), aToken.forget()); return AllocateDecoderPromise::CreateAndResolve( wrapper, __func__); }, [](const MediaResult& aError) { return AllocateDecoderPromise::CreateAndReject( aError, __func__); }); return p; }, []() { return AllocateDecoderPromise::CreateAndReject( MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Allocation policy expired"), __func__); }); return p; } } // namespace mozilla