Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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/. */
7 #include "mozilla/AvailableMemoryTracker.h"
9 #if defined(XP_WIN)
10 #include "prinrval.h"
11 #include "prenv.h"
12 #include "nsIMemoryReporter.h"
13 #include "nsMemoryPressure.h"
14 #endif
16 #include "nsIObserver.h"
17 #include "nsIObserverService.h"
18 #include "nsIRunnable.h"
19 #include "nsISupports.h"
20 #include "nsThreadUtils.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/Services.h"
25 #if defined(XP_WIN)
26 # include "nsWindowsDllInterceptor.h"
27 # include <windows.h>
28 #endif
30 #if defined(MOZ_MEMORY)
31 # include "mozmemory.h"
32 #endif // MOZ_MEMORY
34 using namespace mozilla;
36 namespace {
38 #if defined(XP_WIN)
40 // We don't want our diagnostic functions to call malloc, because that could
41 // call VirtualAlloc, and we'd end up back in here! So here are a few simple
42 // debugging macros (modeled on jemalloc's), which hopefully won't allocate.
44 // #define LOGGING_ENABLED
46 #ifdef LOGGING_ENABLED
48 #define LOG(msg) \
49 do { \
50 safe_write(msg); \
51 safe_write("\n"); \
52 } while(0)
54 #define LOG2(m1, m2) \
55 do { \
56 safe_write(m1); \
57 safe_write(m2); \
58 safe_write("\n"); \
59 } while(0)
61 #define LOG3(m1, m2, m3) \
62 do { \
63 safe_write(m1); \
64 safe_write(m2); \
65 safe_write(m3); \
66 safe_write("\n"); \
67 } while(0)
69 #define LOG4(m1, m2, m3, m4) \
70 do { \
71 safe_write(m1); \
72 safe_write(m2); \
73 safe_write(m3); \
74 safe_write(m4); \
75 safe_write("\n"); \
76 } while(0)
78 #else
80 #define LOG(msg)
81 #define LOG2(m1, m2)
82 #define LOG3(m1, m2, m3)
83 #define LOG4(m1, m2, m3, m4)
85 #endif
87 void safe_write(const char *a)
88 {
89 // Well, puts isn't exactly "safe", but at least it doesn't call malloc...
90 fputs(a, stdout);
91 }
93 void safe_write(uint64_t x)
94 {
95 // 2^64 is 20 decimal digits.
96 const unsigned int max_len = 21;
97 char buf[max_len];
98 buf[max_len - 1] = '\0';
100 uint32_t i;
101 for (i = max_len - 2; i < max_len && x > 0; i--)
102 {
103 buf[i] = "0123456789"[x % 10];
104 x /= 10;
105 }
107 safe_write(&buf[i + 1]);
108 }
110 #ifdef DEBUG
111 #define DEBUG_WARN_IF_FALSE(cond, msg) \
112 do { \
113 if (!(cond)) { \
114 safe_write(__FILE__); \
115 safe_write(":"); \
116 safe_write(__LINE__); \
117 safe_write(" "); \
118 safe_write(msg); \
119 safe_write("\n"); \
120 } \
121 } while(0)
122 #else
123 #define DEBUG_WARN_IF_FALSE(cond, msg)
124 #endif
126 uint32_t sLowVirtualMemoryThreshold = 0;
127 uint32_t sLowCommitSpaceThreshold = 0;
128 uint32_t sLowPhysicalMemoryThreshold = 0;
129 uint32_t sLowMemoryNotificationIntervalMS = 0;
131 Atomic<uint32_t> sNumLowVirtualMemEvents;
132 Atomic<uint32_t> sNumLowCommitSpaceEvents;
133 Atomic<uint32_t> sNumLowPhysicalMemEvents;
135 WindowsDllInterceptor sKernel32Intercept;
136 WindowsDllInterceptor sGdi32Intercept;
138 // Has Init() been called?
139 bool sInitialized = false;
141 // Has Activate() been called? The hooks don't do anything until this happens.
142 bool sHooksActive = false;
144 // Alas, we'd like to use mozilla::TimeStamp, but we can't, because it acquires
145 // a lock!
146 volatile bool sHasScheduledOneLowMemoryNotification = false;
147 volatile PRIntervalTime sLastLowMemoryNotificationTime;
149 // These are function pointers to the functions we wrap in Init().
151 void* (WINAPI *sVirtualAllocOrig)
152 (LPVOID aAddress, SIZE_T aSize, DWORD aAllocationType, DWORD aProtect);
154 void* (WINAPI *sMapViewOfFileOrig)
155 (HANDLE aFileMappingObject, DWORD aDesiredAccess,
156 DWORD aFileOffsetHigh, DWORD aFileOffsetLow,
157 SIZE_T aNumBytesToMap);
159 HBITMAP (WINAPI *sCreateDIBSectionOrig)
160 (HDC aDC, const BITMAPINFO *aBitmapInfo,
161 UINT aUsage, VOID **aBits,
162 HANDLE aSection, DWORD aOffset);
164 /**
165 * Fire a memory pressure event if it's been long enough since the last one we
166 * fired.
167 */
168 bool MaybeScheduleMemoryPressureEvent()
169 {
170 // If this interval rolls over, we may fire an extra memory pressure
171 // event, but that's not a big deal.
172 PRIntervalTime interval = PR_IntervalNow() - sLastLowMemoryNotificationTime;
173 if (sHasScheduledOneLowMemoryNotification &&
174 PR_IntervalToMilliseconds(interval) < sLowMemoryNotificationIntervalMS) {
176 LOG("Not scheduling low physical memory notification, "
177 "because not enough time has elapsed since last one.");
178 return false;
179 }
181 // There's a bit of a race condition here, since an interval may be a
182 // 64-bit number, and 64-bit writes aren't atomic on x86-32. But let's
183 // not worry about it -- the races only happen when we're already
184 // experiencing memory pressure and firing notifications, so the worst
185 // thing that can happen is that we fire two notifications when we
186 // should have fired only one.
187 sHasScheduledOneLowMemoryNotification = true;
188 sLastLowMemoryNotificationTime = PR_IntervalNow();
190 LOG("Scheduling memory pressure notification.");
191 NS_DispatchEventualMemoryPressure(MemPressure_New);
192 return true;
193 }
195 void CheckMemAvailable()
196 {
197 if (!sHooksActive) {
198 return;
199 }
201 MEMORYSTATUSEX stat;
202 stat.dwLength = sizeof(stat);
203 bool success = GlobalMemoryStatusEx(&stat);
205 DEBUG_WARN_IF_FALSE(success, "GlobalMemoryStatusEx failed.");
207 if (success)
208 {
209 // sLowVirtualMemoryThreshold is in MB, but ullAvailVirtual is in bytes.
210 if (stat.ullAvailVirtual < sLowVirtualMemoryThreshold * 1024 * 1024) {
211 // If we're running low on virtual memory, unconditionally schedule the
212 // notification. We'll probably crash if we run out of virtual memory,
213 // so don't worry about firing this notification too often.
214 LOG("Detected low virtual memory.");
215 ++sNumLowVirtualMemEvents;
216 NS_DispatchEventualMemoryPressure(MemPressure_New);
217 }
218 else if (stat.ullAvailPageFile < sLowCommitSpaceThreshold * 1024 * 1024) {
219 LOG("Detected low available page file space.");
220 if (MaybeScheduleMemoryPressureEvent()) {
221 ++sNumLowCommitSpaceEvents;
222 }
223 }
224 else if (stat.ullAvailPhys < sLowPhysicalMemoryThreshold * 1024 * 1024) {
225 LOG("Detected low physical memory.");
226 if (MaybeScheduleMemoryPressureEvent()) {
227 ++sNumLowPhysicalMemEvents;
228 }
229 }
230 }
231 }
233 LPVOID WINAPI
234 VirtualAllocHook(LPVOID aAddress, SIZE_T aSize,
235 DWORD aAllocationType,
236 DWORD aProtect)
237 {
238 // It's tempting to see whether we have enough free virtual address space for
239 // this allocation and, if we don't, synchronously fire a low-memory
240 // notification to free some before we allocate.
241 //
242 // Unfortunately that doesn't work, principally because code doesn't expect a
243 // call to malloc could trigger a GC (or call into the other routines which
244 // are triggered by a low-memory notification).
245 //
246 // I think the best we can do here is try to allocate the memory and check
247 // afterwards how much free virtual address space we have. If we're running
248 // low, we schedule a low-memory notification to run as soon as possible.
250 LPVOID result = sVirtualAllocOrig(aAddress, aSize, aAllocationType, aProtect);
252 // Don't call CheckMemAvailable for MEM_RESERVE if we're not tracking low
253 // virtual memory. Similarly, don't call CheckMemAvailable for MEM_COMMIT if
254 // we're not tracking low physical memory.
255 if ((sLowVirtualMemoryThreshold != 0 && aAllocationType & MEM_RESERVE) ||
256 (sLowPhysicalMemoryThreshold != 0 && aAllocationType & MEM_COMMIT)) {
257 LOG3("VirtualAllocHook(size=", aSize, ")");
258 CheckMemAvailable();
259 }
261 return result;
262 }
264 LPVOID WINAPI
265 MapViewOfFileHook(HANDLE aFileMappingObject,
266 DWORD aDesiredAccess,
267 DWORD aFileOffsetHigh,
268 DWORD aFileOffsetLow,
269 SIZE_T aNumBytesToMap)
270 {
271 LPVOID result = sMapViewOfFileOrig(aFileMappingObject, aDesiredAccess,
272 aFileOffsetHigh, aFileOffsetLow,
273 aNumBytesToMap);
274 LOG("MapViewOfFileHook");
275 CheckMemAvailable();
276 return result;
277 }
279 HBITMAP WINAPI
280 CreateDIBSectionHook(HDC aDC,
281 const BITMAPINFO *aBitmapInfo,
282 UINT aUsage,
283 VOID **aBits,
284 HANDLE aSection,
285 DWORD aOffset)
286 {
287 // There are a lot of calls to CreateDIBSection, so we make some effort not
288 // to CheckMemAvailable() for calls to CreateDIBSection which allocate only
289 // a small amount of memory.
291 // If aSection is non-null, CreateDIBSection won't allocate any new memory.
292 bool doCheck = false;
293 if (sHooksActive && !aSection && aBitmapInfo) {
294 uint16_t bitCount = aBitmapInfo->bmiHeader.biBitCount;
295 if (bitCount == 0) {
296 // MSDN says bitCount == 0 means that it figures out how many bits each
297 // pixel gets by examining the corresponding JPEG or PNG data. We'll just
298 // assume the worst.
299 bitCount = 32;
300 }
302 // |size| contains the expected allocation size in *bits*. Height may be
303 // negative (indicating the direction the DIB is drawn in), so we take the
304 // absolute value.
305 int64_t size = bitCount * aBitmapInfo->bmiHeader.biWidth *
306 aBitmapInfo->bmiHeader.biHeight;
307 if (size < 0)
308 size *= -1;
310 // If we're allocating more than 1MB, check how much memory is left after
311 // the allocation.
312 if (size > 1024 * 1024 * 8) {
313 LOG3("CreateDIBSectionHook: Large allocation (size=", size, ")");
314 doCheck = true;
315 }
316 }
318 HBITMAP result = sCreateDIBSectionOrig(aDC, aBitmapInfo, aUsage, aBits,
319 aSection, aOffset);
321 if (doCheck) {
322 CheckMemAvailable();
323 }
325 return result;
326 }
328 static int64_t
329 LowMemoryEventsVirtualDistinguishedAmount()
330 {
331 return sNumLowVirtualMemEvents;
332 }
334 static int64_t
335 LowMemoryEventsPhysicalDistinguishedAmount()
336 {
337 return sNumLowPhysicalMemEvents;
338 }
340 class LowEventsReporter MOZ_FINAL : public nsIMemoryReporter
341 {
342 public:
343 NS_DECL_ISUPPORTS
345 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
346 nsISupports* aData)
347 {
348 nsresult rv;
350 // We only do virtual-memory tracking on 32-bit builds.
351 if (sizeof(void*) == 4) {
352 rv = MOZ_COLLECT_REPORT(
353 "low-memory-events/virtual", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
354 LowMemoryEventsVirtualDistinguishedAmount(),
355 "Number of low-virtual-memory events fired since startup. We fire such an "
356 "event if we notice there is less than memory.low_virtual_mem_threshold_mb of "
357 "virtual address space available (if zero, this behavior is disabled). The "
358 "process will probably crash if it runs out of virtual address space, so "
359 "this event is dire.");
360 NS_ENSURE_SUCCESS(rv, rv);
361 }
363 rv = MOZ_COLLECT_REPORT(
364 "low-commit-space-events", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
365 sNumLowCommitSpaceEvents,
366 "Number of low-commit-space events fired since startup. We fire such an "
367 "event if we notice there is less than memory.low_commit_space_threshold_mb of "
368 "commit space available (if zero, this behavior is disabled). Windows will "
369 "likely kill the process if it runs out of commit space, so this event is "
370 "dire.");
371 NS_ENSURE_SUCCESS(rv, rv);
373 rv = MOZ_COLLECT_REPORT(
374 "low-memory-events/physical", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
375 LowMemoryEventsPhysicalDistinguishedAmount(),
376 "Number of low-physical-memory events fired since startup. We fire such an "
377 "event if we notice there is less than memory.low_physical_memory_threshold_mb "
378 "of physical memory available (if zero, this behavior is disabled). The "
379 "machine will start to page if it runs out of physical memory. This may "
380 "cause it to run slowly, but it shouldn't cause it to crash.");
381 NS_ENSURE_SUCCESS(rv, rv);
383 return NS_OK;
384 }
385 };
386 NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter)
388 #endif // defined(XP_WIN)
390 /**
391 * This runnable is executed in response to a memory-pressure event; we spin
392 * the event-loop when receiving the memory-pressure event in the hope that
393 * other observers will synchronously free some memory that we'll be able to
394 * purge here.
395 */
396 class nsJemallocFreeDirtyPagesRunnable MOZ_FINAL : public nsIRunnable
397 {
398 public:
399 NS_DECL_ISUPPORTS
400 NS_DECL_NSIRUNNABLE
401 };
403 NS_IMPL_ISUPPORTS(nsJemallocFreeDirtyPagesRunnable, nsIRunnable)
405 NS_IMETHODIMP
406 nsJemallocFreeDirtyPagesRunnable::Run()
407 {
408 MOZ_ASSERT(NS_IsMainThread());
410 #if defined(MOZ_MEMORY)
411 jemalloc_free_dirty_pages();
412 #endif
414 return NS_OK;
415 }
417 /**
418 * The memory pressure watcher is used for listening to memory-pressure events
419 * and reacting upon them. We use one instance per process currently only for
420 * cleaning up dirty unused pages held by jemalloc.
421 */
422 class nsMemoryPressureWatcher MOZ_FINAL : public nsIObserver
423 {
424 public:
425 NS_DECL_ISUPPORTS
426 NS_DECL_NSIOBSERVER
428 void Init();
430 private:
431 static bool sFreeDirtyPages;
432 };
434 NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver)
436 bool nsMemoryPressureWatcher::sFreeDirtyPages = false;
438 /**
439 * Initialize and subscribe to the memory-pressure events. We subscribe to the
440 * observer service in this method and not in the constructor because we need
441 * to hold a strong reference to 'this' before calling the observer service.
442 */
443 void
444 nsMemoryPressureWatcher::Init()
445 {
446 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
448 if (os) {
449 os->AddObserver(this, "memory-pressure", /* ownsWeak */ false);
450 }
452 Preferences::AddBoolVarCache(&sFreeDirtyPages, "memory.free_dirty_pages",
453 false);
454 }
456 /**
457 * Reacts to all types of memory-pressure events, launches a runnable to
458 * free dirty pages held by jemalloc.
459 */
460 NS_IMETHODIMP
461 nsMemoryPressureWatcher::Observe(nsISupports *subject, const char *topic,
462 const char16_t *data)
463 {
464 MOZ_ASSERT(!strcmp(topic, "memory-pressure"), "Unknown topic");
466 if (sFreeDirtyPages) {
467 nsRefPtr<nsIRunnable> runnable = new nsJemallocFreeDirtyPagesRunnable();
469 NS_DispatchToMainThread(runnable);
470 }
472 return NS_OK;
473 }
475 } // anonymous namespace
477 namespace mozilla {
478 namespace AvailableMemoryTracker {
480 void Activate()
481 {
482 #if defined(_M_IX86) && defined(XP_WIN)
483 MOZ_ASSERT(sInitialized);
484 MOZ_ASSERT(!sHooksActive);
486 // On 64-bit systems, hardcode sLowVirtualMemoryThreshold to 0 -- we assume
487 // we're not going to run out of virtual memory!
488 if (sizeof(void*) > 4) {
489 sLowVirtualMemoryThreshold = 0;
490 }
491 else {
492 Preferences::AddUintVarCache(&sLowVirtualMemoryThreshold,
493 "memory.low_virtual_mem_threshold_mb", 128);
494 }
496 Preferences::AddUintVarCache(&sLowPhysicalMemoryThreshold,
497 "memory.low_physical_memory_threshold_mb", 0);
498 Preferences::AddUintVarCache(&sLowCommitSpaceThreshold,
499 "memory.low_commit_space_threshold_mb", 128);
500 Preferences::AddUintVarCache(&sLowMemoryNotificationIntervalMS,
501 "memory.low_memory_notification_interval_ms", 10000);
503 RegisterStrongMemoryReporter(new LowEventsReporter());
504 RegisterLowMemoryEventsVirtualDistinguishedAmount(LowMemoryEventsVirtualDistinguishedAmount);
505 RegisterLowMemoryEventsPhysicalDistinguishedAmount(LowMemoryEventsPhysicalDistinguishedAmount);
506 sHooksActive = true;
507 #endif
509 // This object is held alive by the observer service.
510 nsRefPtr<nsMemoryPressureWatcher> watcher = new nsMemoryPressureWatcher();
511 watcher->Init();
512 }
514 void Init()
515 {
516 // Do nothing on x86-64, because nsWindowsDllInterceptor is not thread-safe
517 // on 64-bit. (On 32-bit, it's probably thread-safe.) Even if we run Init()
518 // before any other of our threads are running, another process may have
519 // started a remote thread which could call VirtualAlloc!
520 //
521 // Moreover, the benefit of this code is less clear when we're a 64-bit
522 // process, because we aren't going to run out of virtual memory, and the
523 // system is likely to have a fair bit of physical memory.
525 #if defined(_M_IX86) && defined(XP_WIN)
526 // Don't register the hooks if we're a build instrumented for PGO: If we're
527 // an instrumented build, the compiler adds function calls all over the place
528 // which may call VirtualAlloc; this makes it hard to prevent
529 // VirtualAllocHook from reentering itself.
530 if (!PR_GetEnv("MOZ_PGO_INSTRUMENTED")) {
531 sKernel32Intercept.Init("Kernel32.dll");
532 sKernel32Intercept.AddHook("VirtualAlloc",
533 reinterpret_cast<intptr_t>(VirtualAllocHook),
534 (void**) &sVirtualAllocOrig);
535 sKernel32Intercept.AddHook("MapViewOfFile",
536 reinterpret_cast<intptr_t>(MapViewOfFileHook),
537 (void**) &sMapViewOfFileOrig);
539 sGdi32Intercept.Init("Gdi32.dll");
540 sGdi32Intercept.AddHook("CreateDIBSection",
541 reinterpret_cast<intptr_t>(CreateDIBSectionHook),
542 (void**) &sCreateDIBSectionOrig);
543 }
545 sInitialized = true;
546 #endif
547 }
549 } // namespace AvailableMemoryTracker
550 } // namespace mozilla