blob: 71256410f9ec24f8600b4a07dc75c226470cb441 [file] [edit]
/*
* Copyright (C) 2011-2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <wtf/MemoryPressureHandler.h>
#include <algorithm>
#include <atomic>
#include <functional>
#include <wtf/Logging.h>
#include <wtf/MathExtras.h>
#include <wtf/MemoryFootprint.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RAMSize.h>
#if PLATFORM(COCOA)
#include <wtf/darwin/DispatchExtras.h>
#endif
namespace WTF {
WTF_EXPORT_PRIVATE bool MemoryPressureHandler::ReliefLogger::s_loggingEnabled = false;
#if PLATFORM(IOS_FAMILY)
static const double s_conservativeThresholdFraction = 0.5;
static const double s_strictThresholdFraction = 0.65;
#else
static const double s_conservativeThresholdFraction = 0.33;
static const double s_strictThresholdFraction = 0.5;
#endif
static const std::optional<double> s_killThresholdFraction;
static const Seconds s_pollInterval = 30_s;
static std::atomic<bool> s_hasCreatedMemoryPressureHandler { true };
MemoryPressureHandler& MemoryPressureHandler::singleton()
{
static NeverDestroyed<MemoryPressureHandler> memoryPressureHandler;
return memoryPressureHandler;
}
static MemoryPressureHandler* memoryPressureHandlerIfExists()
{
return s_hasCreatedMemoryPressureHandler.load() ? &MemoryPressureHandler::singleton() : nullptr;
}
MemoryPressureHandler::MemoryPressureHandler()
#if (OS(LINUX) || OS(FREEBSD) || OS(HAIKU) || OS(QNX)) && !OS(ANDROID)
: m_holdOffTimer(RunLoop::mainSingleton(), "MemoryPressureHandler::HoldOffTimer"_s, this, &MemoryPressureHandler::holdOffTimerFired)
#elif OS(WINDOWS)
: m_windowsMeasurementTimer(RunLoop::mainSingleton(), "MemoryPressureHandler::WindowsMeasurementTimer"_s, this, &MemoryPressureHandler::windowsMeasurementTimerFired)
#endif
{
#if PLATFORM(COCOA)
setDispatchQueue(mainDispatchQueueSingleton());
#endif
}
void MemoryPressureHandler::setMemoryFootprintPollIntervalForTesting(Seconds pollInterval)
{
m_configuration.pollInterval = pollInterval;
}
void MemoryPressureHandler::setShouldUsePeriodicMemoryMonitor(bool use)
{
if (use) {
m_measurementTimer = makeUnique<RunLoop::Timer>(RunLoop::mainSingleton(), "MemoryPressureHandler::MeasurementTimer"_s, this, &MemoryPressureHandler::measurementTimerFired);
m_measurementTimer->startRepeating(m_configuration.pollInterval);
} else
m_measurementTimer = nullptr;
}
#if !RELEASE_LOG_DISABLED
static ASCIILiteral toString(MemoryUsagePolicy policy)
{
switch (policy) {
case MemoryUsagePolicy::Unrestricted: return "Unrestricted"_s;
case MemoryUsagePolicy::Conservative: return "Conservative"_s;
case MemoryUsagePolicy::Strict: return "Strict"_s;
}
ASSERT_NOT_REACHED();
return ""_s;
}
#endif
static size_t thresholdForMemoryKillOfActiveProcess(unsigned tabCount)
{
#if CPU(ADDRESS64)
size_t baseThreshold = ramSize() > 16 * GB ? 15 * GB : 7 * GB;
return baseThreshold + tabCount * GB;
#else
UNUSED_PARAM(tabCount);
return std::min(3 * GB, static_cast<size_t>(truncateDoubleToUint64(ramSize() * 0.9)));
#endif
}
static size_t thresholdForMemoryKillOfInactiveProcess(unsigned tabCount)
{
#if CPU(ADDRESS64)
size_t baseThreshold = 3 * GB + tabCount * GB;
#else
size_t baseThreshold = tabCount > 1 ? 3 * GB : 2 * GB;
#endif
return std::min(baseThreshold, static_cast<size_t>(truncateDoubleToUint64(ramSize() * 0.9)));
}
void MemoryPressureHandler::setPageCount(unsigned pageCount)
{
if (singleton().m_pageCount == pageCount)
return;
singleton().m_pageCount = pageCount;
}
std::optional<size_t> MemoryPressureHandler::thresholdForMemoryKill()
{
if (m_configuration.killThresholdFraction)
return m_configuration.baseThreshold * (*m_configuration.killThresholdFraction);
switch (m_processState) {
case WebsamProcessState::Inactive:
return thresholdForMemoryKillOfInactiveProcess(m_pageCount);
case WebsamProcessState::Active:
return thresholdForMemoryKillOfActiveProcess(m_pageCount);
}
return std::nullopt;
}
size_t MemoryPressureHandler::thresholdForPolicy(MemoryUsagePolicy policy)
{
switch (policy) {
case MemoryUsagePolicy::Unrestricted:
return 0;
case MemoryUsagePolicy::Conservative:
return m_configuration.baseThreshold * m_configuration.conservativeThresholdFraction;
case MemoryUsagePolicy::Strict:
return m_configuration.baseThreshold * m_configuration.strictThresholdFraction;
default:
ASSERT_NOT_REACHED();
return 0;
}
}
MemoryUsagePolicy MemoryPressureHandler::policyForFootprint(size_t footprint)
{
if (footprint >= thresholdForPolicy(MemoryUsagePolicy::Strict))
return MemoryUsagePolicy::Strict;
if (footprint >= thresholdForPolicy(MemoryUsagePolicy::Conservative))
return MemoryUsagePolicy::Conservative;
return MemoryUsagePolicy::Unrestricted;
}
MemoryUsagePolicy MemoryPressureHandler::currentMemoryUsagePolicy()
{
if (m_isSimulatingMemoryWarning)
return MemoryUsagePolicy::Conservative;
if (m_isSimulatingMemoryPressure)
return MemoryUsagePolicy::Strict;
return policyForFootprint(memoryFootprint());
}
void MemoryPressureHandler::shrinkOrDie(size_t killThreshold)
{
RELEASE_LOG(MemoryPressure, "Process is above the memory kill threshold. Trying to shrink down.");
releaseMemory(Critical::Yes, Synchronous::Yes);
size_t footprint = memoryFootprint();
RELEASE_LOG(MemoryPressure, "New memory footprint: %zu MB", footprint / MB);
if (footprint < killThreshold) {
RELEASE_LOG(MemoryPressure, "Shrank below memory kill threshold. Process gets to live.");
setMemoryUsagePolicyBasedOnFootprint(footprint);
return;
}
WTFLogAlways("Unable to shrink memory footprint of process (%zu MB) below the kill thresold (%zu MB). Killed\n", footprint / MB, killThreshold / MB);
RELEASE_ASSERT(m_memoryKillCallback);
m_memoryKillCallback();
}
void MemoryPressureHandler::setMemoryUsagePolicyBasedOnFootprint(size_t footprint)
{
auto newPolicy = policyForFootprint(footprint);
if (newPolicy == m_memoryUsagePolicy)
return;
RELEASE_LOG(MemoryPressure, "Memory usage policy changed: %s -> %s", toString(m_memoryUsagePolicy).characters(), toString(newPolicy).characters());
m_memoryUsagePolicy = newPolicy;
memoryPressureStatusChanged();
}
void MemoryPressureHandler::setMemoryFootprintNotificationThresholds(Vector<uint64_t>&& thresholds, WTF::Function<void(uint64_t)>&& handler)
{
if (thresholds.isEmpty() || !handler)
return;
std::ranges::sort(thresholds, std::greater<>());
m_memoryFootprintNotificationThresholds = WTF::move(thresholds);
m_memoryFootprintNotificationHandler = WTF::move(handler);
}
void MemoryPressureHandler::measurementTimerFired()
{
size_t footprint = memoryFootprint();
#if PLATFORM(COCOA)
RELEASE_LOG(MemoryPressure, "Current memory footprint: %zu MB", footprint / MB);
#endif
while (m_memoryFootprintNotificationThresholds.size() && footprint > m_memoryFootprintNotificationThresholds.last()) {
auto notificationThreshold = m_memoryFootprintNotificationThresholds.takeLast();
m_memoryFootprintNotificationHandler(notificationThreshold);
}
auto killThreshold = thresholdForMemoryKill();
if (killThreshold && footprint >= *killThreshold) {
shrinkOrDie(*killThreshold);
return;
}
setMemoryUsagePolicyBasedOnFootprint(footprint);
switch (m_memoryUsagePolicy) {
case MemoryUsagePolicy::Unrestricted:
break;
case MemoryUsagePolicy::Conservative:
releaseMemory(Critical::No, Synchronous::No);
break;
case MemoryUsagePolicy::Strict:
releaseMemory(Critical::Yes, Synchronous::No);
break;
}
}
void MemoryPressureHandler::setProcessState(WebsamProcessState state)
{
if (m_processState == state)
return;
m_processState = state;
}
ASCIILiteral MemoryPressureHandler::processStateDescription()
{
if (RefPtr handler = memoryPressureHandlerIfExists()) {
switch (handler->processState()) {
case WebsamProcessState::Active:
return "active"_s;
case WebsamProcessState::Inactive:
return "inactive"_s;
}
}
return "unknown"_s;
}
void MemoryPressureHandler::beginSimulatedMemoryWarning()
{
if (m_isSimulatingMemoryWarning)
return;
m_isSimulatingMemoryWarning = true;
memoryPressureStatusChanged();
respondToMemoryPressure(Critical::No, Synchronous::Yes);
}
void MemoryPressureHandler::endSimulatedMemoryWarning()
{
if (!m_isSimulatingMemoryWarning)
return;
m_isSimulatingMemoryWarning = false;
memoryPressureStatusChanged();
}
void MemoryPressureHandler::beginSimulatedMemoryPressure()
{
if (m_isSimulatingMemoryPressure)
return;
m_isSimulatingMemoryPressure = true;
memoryPressureStatusChanged();
respondToMemoryPressure(Critical::Yes, Synchronous::Yes);
}
void MemoryPressureHandler::endSimulatedMemoryPressure()
{
if (!m_isSimulatingMemoryPressure)
return;
m_isSimulatingMemoryPressure = false;
memoryPressureStatusChanged();
}
void MemoryPressureHandler::releaseMemory(Critical critical, Synchronous synchronous)
{
if (!m_lowMemoryHandler)
return;
ReliefLogger log("Total");
m_lowMemoryHandler(critical, synchronous);
platformReleaseMemory(critical);
}
void MemoryPressureHandler::setMemoryPressureStatus(SystemMemoryPressureStatus status)
{
if (m_memoryPressureStatus == status)
return;
m_memoryPressureStatus = status;
memoryPressureStatusChanged();
}
void MemoryPressureHandler::memoryPressureStatusChanged()
{
if (m_memoryPressureStatusChangedCallback)
m_memoryPressureStatusChangedCallback();
}
void MemoryPressureHandler::didExceedProcessMemoryLimit(ProcessMemoryLimit limit)
{
if (m_didExceedProcessMemoryLimitCallback)
m_didExceedProcessMemoryLimitCallback(limit);
}
MemoryPressureHandler::ReliefLogger::~ReliefLogger()
{
if (loggingEnabled())
logMemoryUsageChange();
}
void MemoryPressureHandler::ReliefLogger::logMemoryUsageChange()
{
#if !RELEASE_LOG_DISABLED
#define MEMORYPRESSURE_LOG(...) RELEASE_LOG(MemoryPressure, __VA_ARGS__)
#else
#define MEMORYPRESSURE_LOG(...) WTFLogAlways(__VA_ARGS__)
#endif
auto currentMemory = platformMemoryUsage();
if (!currentMemory || !m_initialMemory) {
MEMORYPRESSURE_LOG("Memory pressure relief: %" PUBLIC_LOG_STRING ": (Unable to get dirty memory information for process)", m_logString);
return;
}
long residentDiff = currentMemory->resident - m_initialMemory->resident;
long physicalDiff = currentMemory->physical - m_initialMemory->physical;
MEMORYPRESSURE_LOG("Memory pressure relief: %" PUBLIC_LOG_STRING ": res = %zu/%zu/%ld, res+swap = %zu/%zu/%ld",
m_logString,
m_initialMemory->resident, currentMemory->resident, residentDiff,
m_initialMemory->physical, currentMemory->physical, physicalDiff);
}
#if !OS(WINDOWS)
void MemoryPressureHandler::platformInitialize() { }
#endif
MemoryPressureHandlerConfiguration::MemoryPressureHandlerConfiguration()
: baseThreshold(std::min(3 * GB, ramSize()))
, conservativeThresholdFraction(s_conservativeThresholdFraction)
, strictThresholdFraction(s_strictThresholdFraction)
, killThresholdFraction(s_killThresholdFraction)
, pollInterval(s_pollInterval)
{
}
MemoryPressureHandlerConfiguration::MemoryPressureHandlerConfiguration(uint64_t base, double conservative, double strict, std::optional<double> kill, Seconds interval)
: baseThreshold(base)
, conservativeThresholdFraction(conservative)
, strictThresholdFraction(strict)
, killThresholdFraction(kill)
, pollInterval(interval)
{
}
} // namespace WTF