diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp index fc0fe40d7cd7..20140416ecca 100644 --- a/dom/quota/ActorsParent.cpp +++ b/dom/quota/ActorsParent.cpp @@ -7,7 +7,6 @@ #include "ActorsParent.h" // Local includes -#include "Flatten.h" #include "InitializationTypes.h" #include "OriginScope.h" #include "QuotaCommon.h" @@ -951,15 +950,15 @@ class OriginInfo final { bool mDirectoryExists; }; -class OriginInfoAccessTimeComparator { +class OriginInfoLRUComparator { public: - bool Equals(const NotNull>& a, - const NotNull>& b) const { + bool Equals(const NotNull>& a, + const NotNull>& b) const { return a->LockedAccessTime() == b->LockedAccessTime(); } - bool LessThan(const NotNull>& a, - const NotNull>& b) const { + bool LessThan(const NotNull>& a, + const NotNull>& b) const { return a->LockedAccessTime() < b->LockedAccessTime(); } }; @@ -1024,11 +1023,14 @@ class GroupInfoPair { MOZ_COUNTED_DTOR(GroupInfoPair) private: - RefPtr LockedGetGroupInfo(PersistenceType aPersistenceType) { + already_AddRefed LockedGetGroupInfo( + PersistenceType aPersistenceType) { AssertCurrentThreadOwnsQuotaMutex(); MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); - return GetGroupInfoForPersistenceType(aPersistenceType); + RefPtr groupInfo = + GetGroupInfoForPersistenceType(aPersistenceType); + return groupInfo.forget(); } void LockedSetGroupInfo(PersistenceType aPersistenceType, @@ -3781,8 +3783,8 @@ uint64_t QuotaManager::CollectOriginsForEviction( static void GetInactiveOriginInfos( const nsTArray>>& aOriginInfos, const nsTArray>& aLocks, - OriginInfosFlatTraversable& aInactiveOriginInfos) { - for (const auto& originInfo : aOriginInfos) { + nsTArray>>& aInactiveOriginInfos) { + for (const NotNull>& originInfo : aOriginInfos) { MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); @@ -3801,8 +3803,8 @@ uint64_t QuotaManager::CollectOriginsForEviction( if (!match) { MOZ_ASSERT(!originInfo->mQuotaObjects.Count(), "Inactive origin shouldn't have open files!"); - aInactiveOriginInfos.InsertElementSorted( - originInfo, OriginInfoAccessTimeComparator()); + aInactiveOriginInfos.InsertElementSorted(originInfo, + OriginInfoLRUComparator()); } } } @@ -3830,7 +3832,7 @@ uint64_t QuotaManager::CollectOriginsForEviction( } } - OriginInfosFlatTraversable inactiveOrigins; + nsTArray>> inactiveOrigins; // Enumerate and process inactive origins. This must be protected by the // mutex. @@ -6437,8 +6439,8 @@ QuotaManager::EnsurePersistentOriginIsInitialized(const QuotaInfo& aQuotaInfo) { return std::pair(std::move(directory), created); }(); - if (auto& info = mOriginInitializationInfos.GetOrInsert(aQuotaInfo.mOrigin); - !info.mPersistentOriginAttempted) { + auto& info = mOriginInitializationInfos.GetOrInsert(aQuotaInfo.mOrigin); + if (!info.mPersistentOriginAttempted) { Telemetry::Accumulate(Telemetry::QM_FIRST_INITIALIZATION_ATTEMPT, kPersistentOriginTelemetryKey, static_cast(res.isOk())); @@ -6547,7 +6549,7 @@ nsresult QuotaManager::EnsureTemporaryStorageIsInitialized() { mTemporaryStorageLimit = GetTemporaryStorageLimit( /* aAvailableSpaceBytes */ diskSpaceAvailable + mTemporaryStorageUsage); - CleanupTemporaryStorage(); + CheckTemporaryStorageLimits(); return NS_OK; } @@ -7038,8 +7040,8 @@ void QuotaManager::LockedRemoveQuotaForOrigin( MOZ_ASSERT(pair); - if (RefPtr groupInfo = - pair->LockedGetGroupInfo(aPersistenceType)) { + RefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); + if (groupInfo) { groupInfo->LockedRemoveOriginInfo(aGroupAndOrigin.mOrigin); if (!groupInfo->LockedHasOriginInfos()) { @@ -7089,186 +7091,150 @@ already_AddRefed QuotaManager::LockedGetOriginInfo( return nullptr; } -template -void QuotaManager::MaybeInsertNonPersistedOriginInfos( - Iterator aDest, const RefPtr& aTemporaryGroupInfo, - const RefPtr& aDefaultGroupInfo) { - const auto copy = [&aDest](const GroupInfo& groupInfo) { - std::copy_if( - groupInfo.mOriginInfos.cbegin(), groupInfo.mOriginInfos.cend(), aDest, - [](const auto& originInfo) { return !originInfo->LockedPersisted(); }); - }; +void QuotaManager::CheckTemporaryStorageLimits() { + AssertIsOnIOThread(); - if (aTemporaryGroupInfo) { - MOZ_ASSERT(PERSISTENCE_TYPE_TEMPORARY == - aTemporaryGroupInfo->GetPersistenceType()); + const auto doomedOrigins = [this] { + const auto doomedOriginInfos = [this] { + nsTArray>> doomedOriginInfos; + MutexAutoLock lock(mQuotaMutex); - copy(*aTemporaryGroupInfo); - } - if (aDefaultGroupInfo) { - MOZ_ASSERT(PERSISTENCE_TYPE_DEFAULT == - aDefaultGroupInfo->GetPersistenceType()); + for (const auto& entry : mGroupInfoPairs) { + const auto& pair = entry.GetData(); - copy(*aDefaultGroupInfo); - } -} + MOZ_ASSERT(!entry.GetKey().IsEmpty()); + MOZ_ASSERT(pair); -template -QuotaManager::OriginInfosFlatTraversable -QuotaManager::CollectLRUOriginInfosUntil(Collect&& aCollect, Pred&& aPred) { - OriginInfosFlatTraversable originInfos; + uint64_t groupUsage = 0; - std::forward(aCollect)(MakeBackInserter(originInfos)); + RefPtr temporaryGroupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); + if (temporaryGroupInfo) { + groupUsage += temporaryGroupInfo->mUsage; + } - originInfos.Sort(OriginInfoAccessTimeComparator()); + RefPtr defaultGroupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); + if (defaultGroupInfo) { + groupUsage += defaultGroupInfo->mUsage; + } - const auto foundIt = std::find_if(originInfos.cbegin(), originInfos.cend(), - std::forward(aPred)); + if (groupUsage > 0) { + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager, "Shouldn't be null!"); - originInfos.TruncateLength(foundIt - originInfos.cbegin()); + if (groupUsage > quotaManager->GetGroupLimit()) { + nsTArray>> originInfos; + if (temporaryGroupInfo) { + originInfos.AppendElements(temporaryGroupInfo->mOriginInfos); + } + if (defaultGroupInfo) { + originInfos.AppendElements(defaultGroupInfo->mOriginInfos); + } + originInfos.Sort(OriginInfoLRUComparator()); - return originInfos; -} + for (uint32_t i = 0; i < originInfos.Length(); i++) { + const NotNull>& originInfo = originInfos[i]; + if (originInfo->LockedPersisted()) { + continue; + } -QuotaManager::OriginInfosNestedTraversable -QuotaManager::GetOriginInfosExceedingGroupLimit() const { - MutexAutoLock lock(mQuotaMutex); - - OriginInfosNestedTraversable originInfos; - - for (const auto& entry : mGroupInfoPairs) { - const auto& pair = entry.GetData(); - - MOZ_ASSERT(!entry.GetKey().IsEmpty()); - MOZ_ASSERT(pair); - - uint64_t groupUsage = 0; - - const RefPtr temporaryGroupInfo = - pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); - if (temporaryGroupInfo) { - groupUsage += temporaryGroupInfo->mUsage; - } - - const RefPtr defaultGroupInfo = - pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); - if (defaultGroupInfo) { - groupUsage += defaultGroupInfo->mUsage; - } - - if (groupUsage > 0) { - QuotaManager* quotaManager = QuotaManager::Get(); - MOZ_ASSERT(quotaManager, "Shouldn't be null!"); - - if (groupUsage > quotaManager->GetGroupLimit()) { - originInfos.AppendElement(CollectLRUOriginInfosUntil( - [&temporaryGroupInfo, &defaultGroupInfo](auto inserter) { - MaybeInsertNonPersistedOriginInfos( - std::move(inserter), temporaryGroupInfo, defaultGroupInfo); - }, - [&groupUsage, quotaManager](const auto& originInfo) { + doomedOriginInfos.AppendElement(originInfo); groupUsage -= originInfo->LockedUsage(); - return groupUsage <= quotaManager->GetGroupLimit(); - })); + if (groupUsage <= quotaManager->GetGroupLimit()) { + break; + } + } + } + } } - } - } - return originInfos; -} + uint64_t usage = std::accumulate( + doomedOriginInfos.cbegin(), doomedOriginInfos.cend(), uint64_t(0), + [](uint64_t oldValue, const auto& originInfo) { + return oldValue + originInfo->LockedUsage(); + }); -QuotaManager::OriginInfosNestedTraversable -QuotaManager::GetOriginInfosExceedingGlobalLimit() const { - MutexAutoLock lock(mQuotaMutex); + if (mTemporaryStorageUsage - usage > mTemporaryStorageLimit) { + nsTArray>> originInfos; - QuotaManager::OriginInfosNestedTraversable res; - res.AppendElement(CollectLRUOriginInfosUntil( - // XXX The lambda only needs to capture this, but due to Bug 1421435 it - // can't. - [&](auto inserter) { for (const auto& entry : mGroupInfoPairs) { const auto& pair = entry.GetData(); MOZ_ASSERT(!entry.GetKey().IsEmpty()); MOZ_ASSERT(pair); - MaybeInsertNonPersistedOriginInfos( - inserter, pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY), - pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT)); - } - }, - [temporaryStorageUsage = mTemporaryStorageUsage, - temporaryStorageLimit = mTemporaryStorageLimit, - doomedUsage = uint64_t{0}](const auto& originInfo) mutable { - if (temporaryStorageUsage - doomedUsage <= temporaryStorageLimit) { - return true; + RefPtr groupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); + if (groupInfo) { + originInfos.AppendElements(groupInfo->mOriginInfos); + } + + groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); + if (groupInfo) { + originInfos.AppendElements(groupInfo->mOriginInfos); + } } - doomedUsage += originInfo->LockedUsage(); - return false; - })); + originInfos.RemoveElementsBy( + [&doomedOriginInfos](const auto& originInfo) { + return doomedOriginInfos.Contains(originInfo) || + originInfo->LockedPersisted(); + }); - return res; -} + originInfos.Sort(OriginInfoLRUComparator()); -void QuotaManager::ClearOrigins( - const OriginInfosNestedTraversable& aDoomedOriginInfos) { - AssertIsOnIOThread(); + for (uint32_t i = 0; i < originInfos.Length(); i++) { + if (mTemporaryStorageUsage - usage <= mTemporaryStorageLimit) { + originInfos.TruncateLength(i); + break; + } - // XXX Does this need to be done a) in order and/or b) sequentially? - for (const auto& doomedOriginInfo : - Flatten(aDoomedOriginInfos)) { + usage += originInfos[i]->LockedUsage(); + } + + doomedOriginInfos.AppendElements(originInfos); + } + + return doomedOriginInfos; + }(); + + for (const auto& doomedOriginInfo : doomedOriginInfos) { #ifdef DEBUG - { - MutexAutoLock lock(mQuotaMutex); - MOZ_ASSERT(!doomedOriginInfo->LockedPersisted()); - } + { + MutexAutoLock lock(mQuotaMutex); + MOZ_ASSERT(!doomedOriginInfo->LockedPersisted()); + } #endif - DeleteFilesForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType, - doomedOriginInfo->mOrigin); - } - - struct OriginParams { - nsCString mOrigin; - PersistenceType mPersistenceType; - }; - - nsTArray clearedOrigins; - - { - MutexAutoLock lock(mQuotaMutex); - - for (const auto& doomedOriginInfo : - Flatten(aDoomedOriginInfos)) { - // LockedRemoveQuotaForOrigin might remove the group info; - // OriginInfo::mGroupInfo is only a raw pointer, so we need to store the - // information for calling OriginClearCompleted below in a separate array. - clearedOrigins.AppendElement( - OriginParams{doomedOriginInfo->mOrigin, - doomedOriginInfo->mGroupInfo->mPersistenceType}); - - LockedRemoveQuotaForOrigin( - doomedOriginInfo->mGroupInfo->mPersistenceType, - {doomedOriginInfo->mGroupInfo->mGroup, doomedOriginInfo->mOrigin}); + DeleteFilesForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType, + doomedOriginInfo->mOrigin); } - } - for (const auto& clearedOrigin : clearedOrigins) { - OriginClearCompleted(clearedOrigin.mPersistenceType, clearedOrigin.mOrigin, + nsTArray doomedOrigins; + { + MutexAutoLock lock(mQuotaMutex); + + for (const auto& doomedOriginInfo : doomedOriginInfos) { + PersistenceType persistenceType = + doomedOriginInfo->mGroupInfo->mPersistenceType; + const GroupAndOrigin groupAndOrigin = { + doomedOriginInfo->mGroupInfo->mGroup, doomedOriginInfo->mOrigin}; + LockedRemoveQuotaForOrigin(persistenceType, groupAndOrigin); + + doomedOrigins.EmplaceBack( + OriginParams(persistenceType, groupAndOrigin.mOrigin)); + } + } + + return doomedOrigins; + }(); + + for (const OriginParams& doomedOrigin : doomedOrigins) { + OriginClearCompleted(doomedOrigin.mPersistenceType, doomedOrigin.mOrigin, Nullable()); } -} - -void QuotaManager::CleanupTemporaryStorage() { - AssertIsOnIOThread(); - - // Evicting origins that exceed their group limit also affects the global - // temporary storage usage, so these steps have to be taken sequentially. - // Combining them doesn't seem worth the added complexity. - ClearOrigins(GetOriginInfosExceedingGroupLimit()); - ClearOrigins(GetOriginInfosExceedingGlobalLimit()); if (mTemporaryStorageUsage > mTemporaryStorageLimit) { // If disk space is still low after origin clear, notify storage pressure. @@ -7343,8 +7309,8 @@ bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) { int64_t QuotaManager::GenerateDirectoryLockId() { const int64_t directorylockId = mNextDirectoryLockId; - if (CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1; - result.isValid()) { + CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1; + if (result.isValid()) { mNextDirectoryLockId = result.value(); } else { NS_WARNING("Quota manager has run out of ids for directory locks!"); diff --git a/dom/quota/Flatten.h b/dom/quota/Flatten.h deleted file mode 100644 index 2eb29c6010c2..000000000000 --- a/dom/quota/Flatten.h +++ /dev/null @@ -1,118 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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 DOM_QUOTA_FLATTEN_H_ -#define DOM_QUOTA_FLATTEN_H_ - -#include -#include -#include - -// XXX This should be moved to MFBT. - -namespace mozilla::dom::quota { - -namespace detail { - -using std::begin; -using std::end; - -template -auto Flatten(NestedRange&& aRange) -> std::enable_if_t< - std::is_same_v()))::value_type>>, - std::conditional_t, - std::decay_t, NestedRange>> { - return std::forward(aRange); -} - -template -struct FlatIter { - using OuterIterator = - decltype(begin(std::declval&>())); - using InnerIterator = - decltype(begin(*begin(std::declval&>()))); - - explicit FlatIter(const NestedRange& aRange, OuterIterator aIter) - : mOuterIter{std::move(aIter)}, mOuterEnd{end(aRange)} { - InitInner(); - } - - const T& operator*() const { return *mInnerIter; } - - FlatIter& operator++() { - ++mInnerIter; - if (mInnerIter == mInnerEnd) { - ++mOuterIter; - InitInner(); - } - return *this; - } - - bool operator!=(const FlatIter& aOther) const { - return mOuterIter != aOther.mOuterIter || - (mOuterIter != mOuterEnd && mInnerIter != aOther.mInnerIter); - } - - private: - void InitInner() { - while (mOuterIter != mOuterEnd) { - const typename OuterIterator::value_type& innerRange = *mOuterIter; - - mInnerIter = begin(innerRange); - mInnerEnd = end(innerRange); - - if (mInnerIter != mInnerEnd) { - break; - } - - ++mOuterIter; - } - } - - OuterIterator mOuterIter; - const OuterIterator mOuterEnd; - - InnerIterator mInnerIter; - InnerIterator mInnerEnd; -}; - -template -struct FlatRange { - explicit FlatRange(NestedRange aRange) : mRange{std::move(aRange)} {} - - auto begin() const { - using std::begin; - return FlatIter{mRange, begin(mRange)}; - } - auto end() const { - using std::end; - return FlatIter{mRange, end(mRange)}; - } - - private: - NestedRange mRange; -}; - -template -auto Flatten(NestedRange&& aRange) -> std::enable_if_t< - !std::is_same_v< - T, std::decay_t&>()))::value_type>>, - FlatRange> { - return FlatRange{std::forward(aRange)}; -} - -} // namespace detail - -template -auto Flatten(NestedRange&& aRange) -> decltype(auto) { - return detail::Flatten(std::forward(aRange)); -} - -} // namespace mozilla::dom::quota - -#endif diff --git a/dom/quota/QuotaManager.h b/dom/quota/QuotaManager.h index 97f2d7332ab3..b41cd8b3a634 100644 --- a/dom/quota/QuotaManager.h +++ b/dom/quota/QuotaManager.h @@ -114,6 +114,14 @@ class NS_NO_VTABLE OpenDirectoryListener : public RefCountedObject { virtual ~OpenDirectoryListener() = default; }; +struct OriginParams { + OriginParams(PersistenceType aPersistenceType, const nsACString& aOrigin) + : mOrigin(aOrigin), mPersistenceType(aPersistenceType) {} + + nsCString mOrigin; + PersistenceType mPersistenceType; +}; + class QuotaManager final : public BackgroundThreadObject { friend class DirectoryLockImpl; friend class GroupInfo; @@ -558,19 +566,7 @@ class QuotaManager final : public BackgroundThreadObject { int64_t aAccessTime, bool aPersisted, nsIFile* aDirectory); - using OriginInfosFlatTraversable = - nsTArray>>; - - using OriginInfosNestedTraversable = - nsTArray>>>; - - OriginInfosNestedTraversable GetOriginInfosExceedingGroupLimit() const; - - OriginInfosNestedTraversable GetOriginInfosExceedingGlobalLimit() const; - - void ClearOrigins(const OriginInfosNestedTraversable& aDoomedOriginInfos); - - void CleanupTemporaryStorage(); + void CheckTemporaryStorageLimits(); void DeleteFilesForOrigin(PersistenceType aPersistenceType, const nsACString& aOrigin); @@ -594,15 +590,6 @@ class QuotaManager final : public BackgroundThreadObject { void MaybeRecordShutdownStep(Maybe aClientType, const nsACString& aStepDescription); - template - static void MaybeInsertNonPersistedOriginInfos( - Iterator aDest, const RefPtr& aTemporaryGroupInfo, - const RefPtr& aDefaultGroupInfo); - - template - static OriginInfosFlatTraversable CollectLRUOriginInfosUntil( - Collect&& aCollect, Pred&& aPred); - // Thread on which IO is performed. LazyInitializedOnceNotNull> mIOThread; @@ -618,7 +605,7 @@ class QuotaManager final : public BackgroundThreadObject { // Accesses to mQuotaManagerShutdownSteps must be protected by mQuotaMutex. nsCString mQuotaManagerShutdownSteps; - mutable mozilla::Mutex mQuotaMutex; + mozilla::Mutex mQuotaMutex; nsClassHashtable mGroupInfoPairs; diff --git a/dom/quota/test/gtest/TestFlatten.cpp b/dom/quota/test/gtest/TestFlatten.cpp deleted file mode 100644 index 89e99792b9b3..000000000000 --- a/dom/quota/test/gtest/TestFlatten.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -#include "Flatten.h" - -#include "gtest/gtest.h" - -#include "mozilla/Unused.h" -#include "nsTArray.h" - -namespace mozilla::dom::quota { - -TEST(Flatten, FlatEmpty) -{ - for (const auto& item : Flatten(nsTArray{})) { - Unused << item; - FAIL(); - } -} - -TEST(Flatten, NestedOuterEmpty) -{ - for (const auto& item : Flatten(nsTArray>{})) { - Unused << item; - FAIL(); - } -} - -TEST(Flatten, NestedInnerEmpty) -{ - for (const auto& item : - Flatten(nsTArray>{CopyableTArray{}})) { - Unused << item; - FAIL(); - } -} - -TEST(Flatten, NestedInnerSingular) -{ - nsTArray flattened; - for (const auto& item : - Flatten(nsTArray>{CopyableTArray{1}})) { - flattened.AppendElement(item); - } - - EXPECT_EQ(nsTArray{1}, flattened); -} - -TEST(Flatten, NestedInnerSingulars) -{ - nsTArray flattened; - for (const auto& item : Flatten(nsTArray>{ - CopyableTArray{1}, CopyableTArray{2}})) { - flattened.AppendElement(item); - } - - EXPECT_EQ((nsTArray{{1, 2}}), flattened); -} - -TEST(Flatten, NestedInnerNonSingulars) -{ - nsTArray flattened; - for (const auto& item : Flatten(nsTArray>{ - CopyableTArray{1, 2}, CopyableTArray{3, 4}})) { - flattened.AppendElement(item); - } - - EXPECT_EQ((nsTArray{{1, 2, 3, 4}}), flattened); -} - -} // namespace mozilla::dom::quota diff --git a/dom/quota/test/gtest/moz.build b/dom/quota/test/gtest/moz.build index f91a5a867e2a..d79c10651275 100644 --- a/dom/quota/test/gtest/moz.build +++ b/dom/quota/test/gtest/moz.build @@ -7,7 +7,6 @@ UNIFIED_SOURCES = [ "TestCheckedUnsafePtr.cpp", "TestEncryptedStream.cpp", - "TestFlatten.cpp", "TestQuotaCommon.cpp", "TestQuotaManager.cpp", "TestUsageInfo.cpp", diff --git a/mfbt/NotNull.h b/mfbt/NotNull.h index b434bb64ae1b..bbb4796ec30e 100644 --- a/mfbt/NotNull.h +++ b/mfbt/NotNull.h @@ -77,10 +77,7 @@ struct CopyablePtr { T mPtr; template - explicit CopyablePtr(U&& aPtr) : mPtr{std::forward(aPtr)} {} - - template - explicit CopyablePtr(CopyablePtr aPtr) : mPtr{std::move(aPtr.mPtr)} {} + explicit CopyablePtr(U aPtr) : mPtr{std::move(aPtr)} {} }; } // namespace detail