1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include <algorithm>
8 #include <windows.h>
9 #include <memoryapi.h>
10 #include "gtest/gtest.h"
11
12 #include "AvailableMemoryWatcher.h"
13 #include "mozilla/Atomics.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/SpinEventLoopUntil.h"
16 #include "mozilla/Unused.h"
17 #include "mozilla/Vector.h"
18 #include "nsComponentManagerUtils.h"
19 #include "nsIObserver.h"
20 #include "nsIObserverService.h"
21 #include "nsServiceManagerUtils.h"
22 #include "nsITimer.h"
23 #include "nsMemoryPressure.h"
24 #include "nsWindowsHelpers.h"
25 #include "nsIWindowsRegKey.h"
26 #include "nsXULAppAPI.h"
27 #include "TelemetryFixture.h"
28 #include "TelemetryTestHelpers.h"
29
30 using namespace mozilla;
31
32 namespace {
33
34 static constexpr size_t kBytesInMB = 1024 * 1024;
35
36 template <typename ConditionT>
WaitUntil(const ConditionT & aCondition,uint32_t aTimeoutMs)37 bool WaitUntil(const ConditionT& aCondition, uint32_t aTimeoutMs) {
38 const uint64_t t0 = ::GetTickCount64();
39 bool isTimeout = false;
40
41 // The message queue can be empty and the loop stops
42 // waiting for a new event before detecting timeout.
43 // Creating a timer to fire a timeout event.
44 nsCOMPtr<nsITimer> timer;
45 NS_NewTimerWithFuncCallback(
46 getter_AddRefs(timer),
47 [](nsITimer*, void* isTimeout) {
48 *reinterpret_cast<bool*>(isTimeout) = true;
49 },
50 &isTimeout, aTimeoutMs, nsITimer::TYPE_ONE_SHOT, __func__);
51
52 SpinEventLoopUntil("xpcom-tests:WaitUntil"_ns, [&]() -> bool {
53 if (isTimeout) {
54 return true;
55 }
56
57 bool done = aCondition();
58 if (done) {
59 fprintf(stderr, "Done in %llu msec\n", ::GetTickCount64() - t0);
60 }
61 return done;
62 });
63
64 return !isTimeout;
65 }
66
67 class Spinner final : public nsIObserver {
68 nsCOMPtr<nsIObserverService> mObserverSvc;
69 nsDependentCString mTopicToWatch;
70 Maybe<nsDependentString> mSubTopicToWatch;
71 bool mTopicObserved;
72
73 ~Spinner() = default;
74
75 public:
76 NS_DECL_ISUPPORTS
77
Spinner(nsIObserverService * aObserverSvc,const char * const aTopic,const char16_t * const aSubTopic)78 Spinner(nsIObserverService* aObserverSvc, const char* const aTopic,
79 const char16_t* const aSubTopic)
80 : mObserverSvc(aObserverSvc),
81 mTopicToWatch(aTopic),
82 mSubTopicToWatch(aSubTopic ? Some(nsDependentString(aSubTopic))
83 : Nothing()),
84 mTopicObserved(false) {}
85
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)86 NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
87 const char16_t* aData) override {
88 if (mTopicToWatch == aTopic) {
89 if ((mSubTopicToWatch.isNothing() && !aData) ||
90 mSubTopicToWatch.ref() == aData) {
91 mTopicObserved = true;
92 mObserverSvc->RemoveObserver(this, aTopic);
93
94 // Force the loop to move in case that there is no event in the queue.
95 nsCOMPtr<nsIRunnable> dummyEvent = new Runnable(__func__);
96 NS_DispatchToMainThread(dummyEvent);
97 }
98 } else {
99 fprintf(stderr, "Unexpected topic: %s\n", aTopic);
100 }
101
102 return NS_OK;
103 }
104
StartListening()105 void StartListening() {
106 mTopicObserved = false;
107 mObserverSvc->AddObserver(this, mTopicToWatch.get(), false);
108 }
109
Wait(uint32_t aTimeoutMs)110 bool Wait(uint32_t aTimeoutMs) {
111 return WaitUntil([this]() { return this->mTopicObserved; }, aTimeoutMs);
112 }
113 };
114
115 NS_IMPL_ISUPPORTS(Spinner, nsIObserver)
116
117 /**
118 * Starts a new thread with a message queue to process
119 * memory allocation/free requests
120 */
121 class MemoryEater {
122 using PageT = UniquePtr<void, VirtualFreeDeleter>;
123
ThreadStart(LPVOID aParam)124 static DWORD WINAPI ThreadStart(LPVOID aParam) {
125 return reinterpret_cast<MemoryEater*>(aParam)->ThreadProc();
126 }
127
TouchMemory(void * aAddr,size_t aSize)128 static void TouchMemory(void* aAddr, size_t aSize) {
129 constexpr uint32_t kPageSize = 4096;
130 volatile uint8_t x = 0;
131 auto base = reinterpret_cast<uint8_t*>(aAddr);
132 for (int64_t i = 0, pages = aSize / kPageSize; i < pages; ++i) {
133 // Pick a random place in every allocated page
134 // and dereference it.
135 x ^= *(base + i * kPageSize + rand() % kPageSize);
136 }
137 (void)x;
138 }
139
GetAvailablePhysicalMemoryInMb()140 static uint32_t GetAvailablePhysicalMemoryInMb() {
141 MEMORYSTATUSEX statex = {sizeof(statex)};
142 if (!::GlobalMemoryStatusEx(&statex)) {
143 return 0;
144 }
145
146 return static_cast<uint32_t>(statex.ullAvailPhys / kBytesInMB);
147 }
148
AddWorkingSet(size_t aSize,Vector<PageT> & aOutput)149 static bool AddWorkingSet(size_t aSize, Vector<PageT>& aOutput) {
150 constexpr size_t kMinGranularity = 64 * 1024;
151
152 size_t currentSize = aSize;
153 while (aSize >= kMinGranularity) {
154 if (!GetAvailablePhysicalMemoryInMb()) {
155 // If the available physical memory is less than 1MB, we finish
156 // allocation though there may be still the available commit space.
157 fprintf(stderr, "No enough physical memory.\n");
158 return false;
159 }
160
161 PageT page(::VirtualAlloc(nullptr, currentSize, MEM_RESERVE | MEM_COMMIT,
162 PAGE_READWRITE));
163 if (!page) {
164 DWORD gle = ::GetLastError();
165 if (gle != ERROR_COMMITMENT_LIMIT) {
166 return false;
167 }
168
169 // Try again with a smaller allocation size.
170 currentSize /= 2;
171 continue;
172 }
173
174 aSize -= currentSize;
175
176 // VirtualAlloc consumes the commit space, but we need to *touch* memory
177 // to consume physical memory
178 TouchMemory(page.get(), currentSize);
179 Unused << aOutput.emplaceBack(std::move(page));
180 }
181 return true;
182 }
183
184 DWORD mThreadId;
185 nsAutoHandle mThread;
186 nsAutoHandle mMessageQueueReady;
187 Atomic<bool> mTaskStatus;
188
189 enum class TaskType : UINT {
190 Alloc = WM_USER, // WPARAM = Allocation size
191 Free,
192
193 Last,
194 };
195
ThreadProc()196 DWORD ThreadProc() {
197 Vector<PageT> stock;
198 MSG msg;
199
200 // Force the system to create a message queue
201 ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
202
203 // Ready to get a message. Unblock the main thread.
204 ::SetEvent(mMessageQueueReady.get());
205
206 for (;;) {
207 BOOL result = ::GetMessage(&msg, reinterpret_cast<HWND>(-1), WM_QUIT,
208 static_cast<UINT>(TaskType::Last));
209 if (result == -1) {
210 return ::GetLastError();
211 }
212 if (!result) {
213 // Got WM_QUIT
214 break;
215 }
216
217 switch (static_cast<TaskType>(msg.message)) {
218 case TaskType::Alloc:
219 mTaskStatus = AddWorkingSet(msg.wParam, stock);
220 break;
221 case TaskType::Free:
222 stock = Vector<PageT>();
223 mTaskStatus = true;
224 break;
225 default:
226 MOZ_ASSERT_UNREACHABLE("Unexpected message in the queue");
227 break;
228 }
229 }
230
231 return static_cast<DWORD>(msg.wParam);
232 }
233
PostTask(TaskType aTask,WPARAM aW=0,LPARAM aL=0) const234 bool PostTask(TaskType aTask, WPARAM aW = 0, LPARAM aL = 0) const {
235 return !!::PostThreadMessageW(mThreadId, static_cast<UINT>(aTask), aW, aL);
236 }
237
238 public:
MemoryEater()239 MemoryEater()
240 : mThread(::CreateThread(nullptr, 0, ThreadStart, this, 0, &mThreadId)),
241 mMessageQueueReady(::CreateEventW(nullptr, /*bManualReset*/ TRUE,
242 /*bInitialState*/ FALSE, nullptr)) {
243 ::WaitForSingleObject(mMessageQueueReady.get(), INFINITE);
244 }
245
~MemoryEater()246 ~MemoryEater() {
247 ::PostThreadMessageW(mThreadId, WM_QUIT, 0, 0);
248 if (::WaitForSingleObject(mThread.get(), 30000) != WAIT_OBJECT_0) {
249 ::TerminateThread(mThread.get(), 0);
250 }
251 }
252
GetTaskStatus() const253 bool GetTaskStatus() const { return mTaskStatus; }
RequestAlloc(size_t aSize)254 void RequestAlloc(size_t aSize) { PostTask(TaskType::Alloc, aSize); }
RequestFree()255 void RequestFree() { PostTask(TaskType::Free); }
256 };
257
258 class MockTabUnloader final : public nsITabUnloader {
259 ~MockTabUnloader() = default;
260
261 uint32_t mCounter;
262
263 public:
MockTabUnloader()264 MockTabUnloader() : mCounter(0) {}
265
266 NS_DECL_THREADSAFE_ISUPPORTS
267
ResetCounter()268 void ResetCounter() { mCounter = 0; }
GetCounter() const269 uint32_t GetCounter() const { return mCounter; }
270
UnloadTabAsync()271 NS_IMETHOD UnloadTabAsync() override {
272 ++mCounter;
273 // Issue a memory-pressure to verify OnHighMemory issues
274 // a memory-pressure-stop event.
275 NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
276 return NS_OK;
277 }
278 };
279
280 NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader)
281
282 } // namespace
283
284 class AvailableMemoryWatcherFixture : public TelemetryTestFixture {
285 static const char kPrefLowCommitSpaceThreshold[];
286
287 RefPtr<nsAvailableMemoryWatcherBase> mWatcher;
288 nsCOMPtr<nsIObserverService> mObserverSvc;
289
290 protected:
IsPageFileExpandable()291 static bool IsPageFileExpandable() {
292 const auto kMemMgmtKey =
293 u"SYSTEM\\CurrentControlSet\\Control\\"
294 u"Session Manager\\Memory Management"_ns;
295
296 nsresult rv;
297 nsCOMPtr<nsIWindowsRegKey> regKey =
298 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
299 if (NS_FAILED(rv)) {
300 return false;
301 }
302
303 rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, kMemMgmtKey,
304 nsIWindowsRegKey::ACCESS_READ);
305 if (NS_FAILED(rv)) {
306 return false;
307 }
308
309 nsAutoString pagingFiles;
310 rv = regKey->ReadStringValue(u"PagingFiles"_ns, pagingFiles);
311 if (NS_FAILED(rv)) {
312 return false;
313 }
314
315 // The value data is REG_MULTI_SZ and each element is "<path> <min> <max>".
316 // If the page file size is automatically managed for all drives, the <path>
317 // is set to "?:\pagefile.sys".
318 // If the page file size is configured per drive, for a drive whose page
319 // file is set to "system managed size", both <min> and <max> are set to 0.
320 return !pagingFiles.IsEmpty() &&
321 (pagingFiles[0] == u'?' || FindInReadable(u" 0 0"_ns, pagingFiles));
322 }
323
GetAllocationSizeToTriggerMemoryNotification()324 static size_t GetAllocationSizeToTriggerMemoryNotification() {
325 // The percentage of the used physical memory to the total physical memory
326 // size which is big enough to trigger a memory resource notification.
327 constexpr uint32_t kThresholdPercentage = 98;
328 // If the page file is not expandable, leave a little commit space.
329 const uint32_t kMinimumSafeCommitSpaceMb =
330 IsPageFileExpandable() ? 0 : 1024;
331
332 MEMORYSTATUSEX statex = {sizeof(statex)};
333 EXPECT_TRUE(::GlobalMemoryStatusEx(&statex));
334
335 // How much memory needs to be used to trigger the notification
336 const size_t targetUsedTotalMb =
337 (statex.ullTotalPhys / kBytesInMB) * kThresholdPercentage / 100;
338
339 // How much memory is currently consumed
340 const size_t currentConsumedMb =
341 (statex.ullTotalPhys - statex.ullAvailPhys) / kBytesInMB;
342
343 if (currentConsumedMb >= targetUsedTotalMb) {
344 fprintf(stderr, "The available physical memory is already low.\n");
345 return 0;
346 }
347
348 // How much memory we need to allocate to trigger the notification
349 const uint32_t allocMb = targetUsedTotalMb - currentConsumedMb;
350
351 // If we allocate the target amount, how much commit space will be
352 // left available.
353 const uint32_t estimtedAvailCommitSpace = std::max(
354 0,
355 static_cast<int32_t>((statex.ullAvailPageFile / kBytesInMB) - allocMb));
356
357 // If the available commit space will be too low, we should not continue
358 if (estimtedAvailCommitSpace < kMinimumSafeCommitSpaceMb) {
359 fprintf(stderr, "The available commit space will be short - %d\n",
360 estimtedAvailCommitSpace);
361 return 0;
362 }
363
364 fprintf(stderr,
365 "Total physical memory = %ul\n"
366 "Available commit space = %ul\n"
367 "Amount to allocate = %ul\n"
368 "Future available commit space after allocation = %d\n",
369 static_cast<uint32_t>(statex.ullTotalPhys / kBytesInMB),
370 static_cast<uint32_t>(statex.ullAvailPageFile / kBytesInMB),
371 allocMb, estimtedAvailCommitSpace);
372 return allocMb * kBytesInMB;
373 }
374
SetThresholdAsPercentageOfCommitSpace(uint32_t aPercentage)375 static void SetThresholdAsPercentageOfCommitSpace(uint32_t aPercentage) {
376 aPercentage = std::min(100u, aPercentage);
377
378 MEMORYSTATUSEX statex = {sizeof(statex)};
379 EXPECT_TRUE(::GlobalMemoryStatusEx(&statex));
380
381 const uint32_t newVal = static_cast<uint32_t>(
382 (statex.ullAvailPageFile / kBytesInMB) * aPercentage / 100);
383 fprintf(stderr, "Setting %s to %u\n", kPrefLowCommitSpaceThreshold, newVal);
384
385 Preferences::SetUint(kPrefLowCommitSpaceThreshold, newVal);
386 }
387
388 static constexpr uint32_t kStateChangeTimeoutMs = 20000;
389 static constexpr uint32_t kNotificationTimeoutMs = 20000;
390
391 RefPtr<Spinner> mHighMemoryObserver;
392 RefPtr<MockTabUnloader> mTabUnloader;
393 MemoryEater mMemEater;
394 nsAutoHandle mLowMemoryHandle;
395
SetUp()396 void SetUp() override {
397 TelemetryTestFixture::SetUp();
398
399 mObserverSvc = do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
400 ASSERT_TRUE(mObserverSvc);
401
402 mHighMemoryObserver =
403 new Spinner(mObserverSvc, "memory-pressure-stop", nullptr);
404 mTabUnloader = new MockTabUnloader;
405
406 mWatcher = nsAvailableMemoryWatcherBase::GetSingleton();
407 mWatcher->RegisterTabUnloader(mTabUnloader);
408
409 mLowMemoryHandle.own(
410 ::CreateMemoryResourceNotification(LowMemoryResourceNotification));
411 ASSERT_TRUE(mLowMemoryHandle);
412
413 // We set the threshold to 50% of the current available commit space.
414 // This means we declare low-memory when the available commit space
415 // gets lower than this threshold, otherwise we declare high-memory.
416 SetThresholdAsPercentageOfCommitSpace(50);
417 }
418
TearDown()419 void TearDown() override {
420 StopUserInteraction();
421 Preferences::ClearUser(kPrefLowCommitSpaceThreshold);
422 }
423
WaitForMemoryResourceNotification()424 bool WaitForMemoryResourceNotification() {
425 uint64_t t0 = ::GetTickCount64();
426 if (::WaitForSingleObject(mLowMemoryHandle, kNotificationTimeoutMs) !=
427 WAIT_OBJECT_0) {
428 fprintf(stderr, "The memory notification was not triggered.\n");
429 return false;
430 }
431 fprintf(stderr, "Notified in %llu msec\n", ::GetTickCount64() - t0);
432 return true;
433 }
434
StartUserInteraction()435 void StartUserInteraction() {
436 mObserverSvc->NotifyObservers(nullptr, "user-interaction-active", nullptr);
437 }
438
StopUserInteraction()439 void StopUserInteraction() {
440 mObserverSvc->NotifyObservers(nullptr, "user-interaction-inactive",
441 nullptr);
442 }
443 };
444
445 const char AvailableMemoryWatcherFixture::kPrefLowCommitSpaceThreshold[] =
446 "browser.low_commit_space_threshold_mb";
447
448 class MemoryWatcherTelemetryEvent {
449 static nsLiteralString sEventCategory;
450 static nsLiteralString sEventMethod;
451 static nsLiteralString sEventObject;
452
453 uint32_t mLastCountOfEvents;
454
455 public:
MemoryWatcherTelemetryEvent(JSContext * aCx)456 explicit MemoryWatcherTelemetryEvent(JSContext* aCx) : mLastCountOfEvents(0) {
457 JS::RootedValue snapshot(aCx);
458 TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot);
459 nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray(
460 aCx, snapshot, sEventCategory, sEventMethod, sEventObject);
461 mLastCountOfEvents = eventValues.Length();
462 }
463
ValidateLastEvent(JSContext * aCx)464 void ValidateLastEvent(JSContext* aCx) {
465 JS::RootedValue snapshot(aCx);
466 TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot);
467 nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray(
468 aCx, snapshot, sEventCategory, sEventMethod, sEventObject);
469
470 // A new event was generated.
471 EXPECT_EQ(eventValues.Length(), mLastCountOfEvents + 1);
472 if (eventValues.IsEmpty()) {
473 return;
474 }
475
476 // Update mLastCountOfEvents for a subsequent call to ValidateLastEvent
477 ++mLastCountOfEvents;
478
479 nsTArray<nsString> tokens;
480 for (const nsAString& token : eventValues.LastElement().Split(',')) {
481 tokens.AppendElement(token);
482 }
483 EXPECT_EQ(tokens.Length(), 3);
484 if (tokens.Length() != 3) {
485 const wchar_t* valueStr = eventValues.LastElement().get();
486 fprintf(stderr, "Unexpected event value: %S\n", valueStr);
487 return;
488 }
489
490 // Since this test does not involve TabUnloader, the first two numbers
491 // are always expected to be zero.
492 EXPECT_STREQ(tokens[0].get(), L"0");
493 EXPECT_STREQ(tokens[1].get(), L"0");
494
495 // The third token should be a valid floating number.
496 nsresult rv;
497 tokens[2].ToDouble(&rv);
498 EXPECT_TRUE(NS_SUCCEEDED(rv));
499 }
500 };
501
502 nsLiteralString MemoryWatcherTelemetryEvent::sEventCategory =
503 u"memory_watcher"_ns;
504 nsLiteralString MemoryWatcherTelemetryEvent::sEventMethod =
505 u"on_high_memory"_ns;
506 nsLiteralString MemoryWatcherTelemetryEvent::sEventObject = u"stats"_ns;
507
TEST_F(AvailableMemoryWatcherFixture,AlwaysActive)508 TEST_F(AvailableMemoryWatcherFixture, AlwaysActive) {
509 AutoJSContextWithGlobal cx(mCleanGlobal);
510 MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext());
511 StartUserInteraction();
512
513 const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification();
514 if (!allocSize) {
515 // Not enough memory to safely create a low-memory situation.
516 // Aborting the test without failure.
517 return;
518 }
519
520 mTabUnloader->ResetCounter();
521 mMemEater.RequestAlloc(allocSize);
522 if (!WaitForMemoryResourceNotification()) {
523 // If the notification was not triggered, abort the test without failure
524 // because it's not a fault in nsAvailableMemoryWatcher.
525 return;
526 }
527
528 EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
529 kStateChangeTimeoutMs));
530
531 mHighMemoryObserver->StartListening();
532 mMemEater.RequestFree();
533 EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs));
534
535 telemetryEvent.ValidateLastEvent(cx.GetJSContext());
536 }
537
TEST_F(AvailableMemoryWatcherFixture,InactiveToActive)538 TEST_F(AvailableMemoryWatcherFixture, InactiveToActive) {
539 AutoJSContextWithGlobal cx(mCleanGlobal);
540 MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext());
541 const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification();
542 if (!allocSize) {
543 // Not enough memory to safely create a low-memory situation.
544 // Aborting the test without failure.
545 return;
546 }
547
548 mTabUnloader->ResetCounter();
549 mMemEater.RequestAlloc(allocSize);
550 if (!WaitForMemoryResourceNotification()) {
551 // If the notification was not triggered, abort the test without failure
552 // because it's not a fault in nsAvailableMemoryWatcher.
553 return;
554 }
555
556 mHighMemoryObserver->StartListening();
557 EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
558 kStateChangeTimeoutMs));
559
560 mMemEater.RequestFree();
561
562 // OnHighMemory should not be triggered during no user interaction
563 // eve after all memory was freed. Expecting false.
564 EXPECT_FALSE(mHighMemoryObserver->Wait(3000));
565
566 StartUserInteraction();
567
568 // After user is active, we expect true.
569 EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs));
570
571 telemetryEvent.ValidateLastEvent(cx.GetJSContext());
572 }
573
TEST_F(AvailableMemoryWatcherFixture,HighCommitSpace_AlwaysActive)574 TEST_F(AvailableMemoryWatcherFixture, HighCommitSpace_AlwaysActive) {
575 // Setting a low threshold simulates a high commit space.
576 SetThresholdAsPercentageOfCommitSpace(1);
577 StartUserInteraction();
578
579 const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification();
580 if (!allocSize) {
581 // Not enough memory to safely create a low-memory situation.
582 // Aborting the test without failure.
583 return;
584 }
585
586 mTabUnloader->ResetCounter();
587 mMemEater.RequestAlloc(allocSize);
588 if (!WaitForMemoryResourceNotification()) {
589 // If the notification was not triggered, abort the test without failure
590 // because it's not a fault in nsAvailableMemoryWatcher.
591 return;
592 }
593
594 // Tab unload will not be triggered because the commit space is not low.
595 EXPECT_FALSE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
596 kStateChangeTimeoutMs / 2));
597
598 mMemEater.RequestFree();
599 ::Sleep(kStateChangeTimeoutMs / 2);
600
601 // Set a high threshold and make sure the watcher will trigger the tab
602 // unloader next time.
603 SetThresholdAsPercentageOfCommitSpace(50);
604
605 mMemEater.RequestAlloc(allocSize);
606 if (!WaitForMemoryResourceNotification()) {
607 return;
608 }
609
610 EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
611 kStateChangeTimeoutMs));
612
613 mHighMemoryObserver->StartListening();
614 mMemEater.RequestFree();
615 EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs));
616 }
617
TEST_F(AvailableMemoryWatcherFixture,HighCommitSpace_InactiveToActive)618 TEST_F(AvailableMemoryWatcherFixture, HighCommitSpace_InactiveToActive) {
619 // Setting a low threshold simulates a high commit space.
620 SetThresholdAsPercentageOfCommitSpace(1);
621
622 const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification();
623 if (!allocSize) {
624 // Not enough memory to safely create a low-memory situation.
625 // Aborting the test without failure.
626 return;
627 }
628
629 mTabUnloader->ResetCounter();
630 mMemEater.RequestAlloc(allocSize);
631 if (!WaitForMemoryResourceNotification()) {
632 // If the notification was not triggered, abort the test without failure
633 // because it's not a fault in nsAvailableMemoryWatcher.
634 return;
635 }
636
637 // Tab unload will not be triggered because the commit space is not low.
638 EXPECT_FALSE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
639 kStateChangeTimeoutMs / 2));
640
641 mMemEater.RequestFree();
642 ::Sleep(kStateChangeTimeoutMs / 2);
643
644 // Set a high threshold and make sure the watcher will trigger the tab
645 // unloader next time.
646 SetThresholdAsPercentageOfCommitSpace(50);
647
648 // When the user becomes active, the watcher will resume the timer.
649 StartUserInteraction();
650
651 mMemEater.RequestAlloc(allocSize);
652 if (!WaitForMemoryResourceNotification()) {
653 return;
654 }
655
656 EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
657 kStateChangeTimeoutMs));
658
659 mHighMemoryObserver->StartListening();
660 mMemEater.RequestFree();
661 EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs));
662 }
663