/* -*- 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 "MediaShutdownManager.h" #include "MediaDecoder.h" #include "mozilla/Logging.h" #include "mozilla/media/MediaUtils.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "nsComponentManagerUtils.h" #include "nsIWritablePropertyBag2.h" namespace mozilla { #undef LOGW extern LazyLogModule gMediaDecoderLog; #define DECODER_LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg) #define LOGW(...) NS_WARNING(nsPrintfCString(__VA_ARGS__).get()) NS_IMPL_ISUPPORTS(MediaShutdownManager, nsIAsyncShutdownBlocker) MediaShutdownManager::MediaShutdownManager() { MOZ_ASSERT(NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(sInitPhase == NotInited); } MediaShutdownManager::~MediaShutdownManager() { MOZ_ASSERT(NS_IsMainThread()); } // Note that we don't use ClearOnShutdown() on this StaticRefPtr, as that // may interfere with our shutdown listener. StaticRefPtr<MediaShutdownManager> MediaShutdownManager::sInstance; MediaShutdownManager::InitPhase MediaShutdownManager::sInitPhase = MediaShutdownManager::NotInited; MediaShutdownManager& MediaShutdownManager::Instance() { MOZ_ASSERT(NS_IsMainThread()); #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED if (!sInstance) { MOZ_CRASH_UNSAFE_PRINTF("sInstance is null. sInitPhase=%d", int(sInitPhase)); } #endif return *sInstance; } void MediaShutdownManager::InitStatics() { MOZ_ASSERT(NS_IsMainThread()); if (sInitPhase != NotInited) { return; } sInstance = new MediaShutdownManager(); MOZ_DIAGNOSTIC_ASSERT(sInstance); nsCOMPtr<nsIAsyncShutdownClient> barrier = media::GetShutdownBarrier(); if (!barrier) { LOGW("Failed to get barrier, cannot add shutdown blocker!"); sInitPhase = InitFailed; return; } nsresult rv = barrier->AddBlocker(sInstance, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u"MediaShutdownManager shutdown"_ns); if (NS_FAILED(rv)) { LOGW("Failed to add shutdown blocker! rv=%x", uint32_t(rv)); sInitPhase = InitFailed; return; } sInitPhase = InitSucceeded; } void MediaShutdownManager::RemoveBlocker() { MOZ_ASSERT(NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(sInitPhase == XPCOMShutdownStarted); MOZ_ASSERT(mDecoders.Count() == 0); nsCOMPtr<nsIAsyncShutdownClient> barrier = media::GetShutdownBarrier(); // xpcom should still be available because we blocked shutdown by having a // blocker. Until it completely shuts down we should still be able to get // the barrier. MOZ_RELEASE_ASSERT( barrier, "Failed to get shutdown barrier, cannot remove shutdown blocker!"); barrier->RemoveBlocker(this); // Clear our singleton reference. This will probably delete // this instance, so don't deref |this| clearing sInstance. sInitPhase = XPCOMShutdownEnded; sInstance = nullptr; DECODER_LOG(LogLevel::Debug, ("MediaShutdownManager::BlockShutdown() end.")); } nsresult MediaShutdownManager::Register(MediaDecoder* aDecoder) { MOZ_ASSERT(NS_IsMainThread()); if (sInitPhase == InitFailed) { return NS_ERROR_NOT_INITIALIZED; } if (sInitPhase == XPCOMShutdownStarted) { return NS_ERROR_ABORT; } // Don't call Register() after you've Unregistered() all the decoders, // that's not going to work. MOZ_ASSERT(!mDecoders.Contains(aDecoder)); mDecoders.Insert(aDecoder); MOZ_ASSERT(mDecoders.Contains(aDecoder)); MOZ_ASSERT(mDecoders.Count() > 0); return NS_OK; } void MediaShutdownManager::Unregister(MediaDecoder* aDecoder) { MOZ_ASSERT(NS_IsMainThread()); if (!mDecoders.EnsureRemoved(aDecoder)) { return; } if (sInitPhase == XPCOMShutdownStarted && mDecoders.Count() == 0) { RemoveBlocker(); } } NS_IMETHODIMP MediaShutdownManager::GetName(nsAString& aName) { aName = u"MediaShutdownManager: shutdown"_ns; return NS_OK; } NS_IMETHODIMP MediaShutdownManager::GetState(nsIPropertyBag** aBagOut) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aBagOut); nsCOMPtr<nsIWritablePropertyBag2> propertyBag = do_CreateInstance("@mozilla.org/hash-property-bag;1"); if (NS_WARN_IF(!propertyBag)) { return NS_ERROR_OUT_OF_MEMORY; } nsresult rv = propertyBag->SetPropertyAsInt32( u"sInitPhase"_ns, static_cast<int32_t>(sInitPhase)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoCString decoderInfo; for (const auto& key : mDecoders) { // Grab the full extended type for the decoder. This can be used to help // indicate problems with specific decoders by associating type -> decoder. decoderInfo.Append(key->ContainerType().ExtendedType().OriginalString()); decoderInfo.Append(", "); } rv = propertyBag->SetPropertyAsACString(u"decoderInfo"_ns, decoderInfo); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } propertyBag.forget(aBagOut); return NS_OK; } NS_IMETHODIMP MediaShutdownManager::BlockShutdown(nsIAsyncShutdownClient*) { MOZ_ASSERT(NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(sInitPhase == InitSucceeded); MOZ_DIAGNOSTIC_ASSERT(sInstance); DECODER_LOG(LogLevel::Debug, ("MediaShutdownManager::BlockShutdown() start...")); // Set this flag to ensure no Register() is allowed when Shutdown() begins. sInitPhase = XPCOMShutdownStarted; auto oldCount = mDecoders.Count(); if (oldCount == 0) { RemoveBlocker(); return NS_OK; } // Iterate over the decoders and shut them down. for (const auto& key : mDecoders) { key->NotifyXPCOMShutdown(); // Check MediaDecoder::Shutdown doesn't call Unregister() synchronously in // order not to corrupt our hashtable traversal. MOZ_ASSERT(mDecoders.Count() == oldCount); } return NS_OK; } } // namespace mozilla // avoid redefined macro in unified build #undef LOGW