mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 20:05:49 +00:00
Bug 718100 - 'Web workers should GC more'. r=mrbkap.
--HG-- extra : transplant_source : %03D%F4%26%AA%03T%8A%B9%B7%27%AF%D4%8C%85%B2%DB%DFf%EF
This commit is contained in:
parent
2f07cad37c
commit
b00fe831b3
@ -92,6 +92,8 @@ using namespace mozilla::xpconnect::memory;
|
||||
// The maximum number of threads to use for workers, overridable via pref.
|
||||
#define MAX_WORKERS_PER_DOMAIN 10
|
||||
|
||||
PR_STATIC_ASSERT(MAX_WORKERS_PER_DOMAIN >= 1);
|
||||
|
||||
// The default number of seconds that close handlers will be allowed to run.
|
||||
#define MAX_SCRIPT_RUN_TIME_SEC 10
|
||||
|
||||
@ -106,7 +108,27 @@ using namespace mozilla::xpconnect::memory;
|
||||
#define PREF_WORKERS_GCZEAL "dom.workers.gczeal"
|
||||
#define PREF_MAX_SCRIPT_RUN_TIME "dom.max_script_run_time"
|
||||
|
||||
PR_STATIC_ASSERT(MAX_WORKERS_PER_DOMAIN >= 1);
|
||||
#define GC_REQUEST_OBSERVER_TOPIC "child-gc-request"
|
||||
#define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure"
|
||||
|
||||
#define BROADCAST_ALL_WORKERS(_func, ...) \
|
||||
PR_BEGIN_MACRO \
|
||||
AssertIsOnMainThread(); \
|
||||
\
|
||||
nsAutoTArray<WorkerPrivate*, 100> workers; \
|
||||
{ \
|
||||
MutexAutoLock lock(mMutex); \
|
||||
\
|
||||
mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers); \
|
||||
} \
|
||||
\
|
||||
if (!workers.IsEmpty()) { \
|
||||
AutoSafeJSContext cx; \
|
||||
for (PRUint32 index = 0; index < workers.Length(); index++) { \
|
||||
workers[index]-> _func (cx, ##__VA_ARGS__); \
|
||||
} \
|
||||
} \
|
||||
PR_END_MACRO
|
||||
|
||||
namespace {
|
||||
|
||||
@ -907,6 +929,15 @@ RuntimeService::Init()
|
||||
|
||||
mObserved = true;
|
||||
|
||||
if (NS_FAILED(obs->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC, false))) {
|
||||
NS_WARNING("Failed to register for GC request notifications!");
|
||||
}
|
||||
|
||||
if (NS_FAILED(obs->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC,
|
||||
false))) {
|
||||
NS_WARNING("Failed to register for memory pressure notifications!");
|
||||
}
|
||||
|
||||
for (PRUint32 index = 0; index < ArrayLength(gPrefsToWatch); index++) {
|
||||
if (NS_FAILED(Preferences::RegisterCallback(PrefCallback,
|
||||
gPrefsToWatch[index], this))) {
|
||||
@ -1019,7 +1050,7 @@ RuntimeService::Cleanup()
|
||||
while (mDomainMap.Count()) {
|
||||
MutexAutoUnlock unlock(mMutex);
|
||||
|
||||
if (NS_FAILED(NS_ProcessNextEvent(currentThread))) {
|
||||
if (!NS_ProcessNextEvent(currentThread)) {
|
||||
NS_WARNING("Something bad happened!");
|
||||
break;
|
||||
}
|
||||
@ -1037,6 +1068,15 @@ RuntimeService::Cleanup()
|
||||
}
|
||||
|
||||
if (obs) {
|
||||
if (NS_FAILED(obs->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC))) {
|
||||
NS_WARNING("Failed to unregister for GC request notifications!");
|
||||
}
|
||||
|
||||
if (NS_FAILED(obs->RemoveObserver(this,
|
||||
MEMORY_PRESSURE_OBSERVER_TOPIC))) {
|
||||
NS_WARNING("Failed to unregister for memory pressure notifications!");
|
||||
}
|
||||
|
||||
nsresult rv =
|
||||
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID);
|
||||
mObserved = NS_FAILED(rv);
|
||||
@ -1195,66 +1235,29 @@ RuntimeService::NoteIdleThread(nsIThread* aThread)
|
||||
void
|
||||
RuntimeService::UpdateAllWorkerJSContextOptions()
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
|
||||
nsAutoTArray<WorkerPrivate*, 100> workers;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers);
|
||||
}
|
||||
|
||||
if (!workers.IsEmpty()) {
|
||||
AutoSafeJSContext cx;
|
||||
for (PRUint32 index = 0; index < workers.Length(); index++) {
|
||||
workers[index]->UpdateJSContextOptions(cx, GetDefaultJSContextOptions());
|
||||
}
|
||||
}
|
||||
BROADCAST_ALL_WORKERS(UpdateJSContextOptions, GetDefaultJSContextOptions());
|
||||
}
|
||||
|
||||
void
|
||||
RuntimeService::UpdateAllWorkerJSRuntimeHeapSize()
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
|
||||
nsAutoTArray<WorkerPrivate*, 100> workers;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers);
|
||||
}
|
||||
|
||||
if (!workers.IsEmpty()) {
|
||||
AutoSafeJSContext cx;
|
||||
for (PRUint32 index = 0; index < workers.Length(); index++) {
|
||||
workers[index]->UpdateJSRuntimeHeapSize(cx,
|
||||
GetDefaultJSRuntimeHeapSize());
|
||||
}
|
||||
}
|
||||
BROADCAST_ALL_WORKERS(UpdateJSRuntimeHeapSize, GetDefaultJSRuntimeHeapSize());
|
||||
}
|
||||
|
||||
#ifdef JS_GC_ZEAL
|
||||
void
|
||||
RuntimeService::UpdateAllWorkerGCZeal()
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
|
||||
nsAutoTArray<WorkerPrivate*, 100> workers;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers);
|
||||
}
|
||||
|
||||
if (!workers.IsEmpty()) {
|
||||
AutoSafeJSContext cx;
|
||||
for (PRUint32 index = 0; index < workers.Length(); index++) {
|
||||
workers[index]->UpdateGCZeal(cx, GetDefaultGCZeal());
|
||||
}
|
||||
}
|
||||
BROADCAST_ALL_WORKERS(UpdateGCZeal, GetDefaultGCZeal());
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
RuntimeService::GarbageCollectAllWorkers(bool aShrinking)
|
||||
{
|
||||
BROADCAST_ALL_WORKERS(GarbageCollect, aShrinking);
|
||||
}
|
||||
|
||||
// nsISupports
|
||||
NS_IMPL_ISUPPORTS1(RuntimeService, nsIObserver)
|
||||
|
||||
@ -1269,6 +1272,14 @@ RuntimeService::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
Cleanup();
|
||||
return NS_OK;
|
||||
}
|
||||
if (!strcmp(aTopic, GC_REQUEST_OBSERVER_TOPIC)) {
|
||||
GarbageCollectAllWorkers(false);
|
||||
return NS_OK;
|
||||
}
|
||||
if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
|
||||
GarbageCollectAllWorkers(true);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_NOTREACHED("Unknown observer topic!");
|
||||
return NS_OK;
|
||||
|
@ -233,6 +233,9 @@ public:
|
||||
UpdateAllWorkerGCZeal();
|
||||
#endif
|
||||
|
||||
void
|
||||
GarbageCollectAllWorkers(bool aShrinking);
|
||||
|
||||
class AutoSafeJSContext
|
||||
{
|
||||
JSContext* mContext;
|
||||
|
@ -58,6 +58,7 @@
|
||||
|
||||
#include "jsfriendapi.h"
|
||||
#include "jsdbgapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "jsprf.h"
|
||||
#include "js/MemoryMetrics.h"
|
||||
|
||||
@ -91,6 +92,12 @@
|
||||
#define EXTRA_GC
|
||||
#endif
|
||||
|
||||
// GC will run once every thirty seconds during normal execution.
|
||||
#define NORMAL_GC_TIMER_DELAY_MS 30000
|
||||
|
||||
// GC will run five seconds after the last event is processed.
|
||||
#define IDLE_GC_TIMER_DELAY_MS 5000
|
||||
|
||||
using mozilla::MutexAutoLock;
|
||||
using mozilla::TimeDuration;
|
||||
using mozilla::TimeStamp;
|
||||
@ -1418,6 +1425,43 @@ public:
|
||||
};
|
||||
#endif
|
||||
|
||||
class GarbageCollectRunnable : public WorkerControlRunnable
|
||||
{
|
||||
protected:
|
||||
bool mShrinking;
|
||||
bool mCollectChildren;
|
||||
|
||||
public:
|
||||
GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
|
||||
bool aCollectChildren)
|
||||
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
|
||||
mShrinking(aShrinking), mCollectChildren(aCollectChildren)
|
||||
{ }
|
||||
|
||||
bool
|
||||
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
||||
{
|
||||
// Silence bad assertions, this can be dispatched from either the main
|
||||
// thread or the timer thread..
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
||||
bool aDispatchResult)
|
||||
{
|
||||
// Silence bad assertions, this can be dispatched from either the main
|
||||
// thread or the timer thread..
|
||||
}
|
||||
|
||||
bool
|
||||
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
||||
{
|
||||
aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class CollectRuntimeStatsRunnable : public WorkerControlRunnable
|
||||
{
|
||||
typedef mozilla::Mutex Mutex;
|
||||
@ -2170,6 +2214,18 @@ WorkerPrivateParent<Derived>::UpdateGCZeal(JSContext* aCx, PRUint8 aGCZeal)
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
WorkerPrivateParent<Derived>::GarbageCollect(JSContext* aCx, bool aShrinking)
|
||||
{
|
||||
nsRefPtr<GarbageCollectRunnable> runnable =
|
||||
new GarbageCollectRunnable(ParentAsWorkerPrivate(), aShrinking, true);
|
||||
if (!runnable->Dispatch(aCx)) {
|
||||
NS_WARNING("Failed to update worker heap size!");
|
||||
JS_ClearPendingException(aCx);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
WorkerPrivateParent<Derived>::SetBaseURI(nsIURI* aBaseURI)
|
||||
@ -2495,6 +2551,37 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
|
||||
mStatus = Running;
|
||||
}
|
||||
|
||||
// We need a timer for GC. The basic plan is to run a normal (non-shrinking)
|
||||
// GC periodically (NORMAL_GC_TIMER_DELAY_MS) while the worker is running.
|
||||
// Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_MS) timer to
|
||||
// run a shrinking GC. If the worker receives more messages then the short
|
||||
// timer is canceled and the periodic timer resumes.
|
||||
nsCOMPtr<nsITimer> gcTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
|
||||
if (!gcTimer) {
|
||||
JS_ReportError(aCx, "Failed to create GC timer!");
|
||||
return;
|
||||
}
|
||||
|
||||
bool normalGCTimerRunning = false;
|
||||
|
||||
// We need to swap event targets below to get different types of GC behavior.
|
||||
nsCOMPtr<nsIEventTarget> normalGCEventTarget;
|
||||
nsCOMPtr<nsIEventTarget> idleGCEventTarget;
|
||||
|
||||
// We also need to track the idle GC event so that we don't confuse it with a
|
||||
// generic event that should re-trigger the idle GC timer.
|
||||
nsCOMPtr<nsIRunnable> idleGCEvent;
|
||||
{
|
||||
nsRefPtr<GarbageCollectRunnable> runnable =
|
||||
new GarbageCollectRunnable(this, false, false);
|
||||
normalGCEventTarget = new WorkerRunnableEventTarget(runnable);
|
||||
|
||||
runnable = new GarbageCollectRunnable(this, true, false);
|
||||
idleGCEventTarget = new WorkerRunnableEventTarget(runnable);
|
||||
|
||||
idleGCEvent = runnable;
|
||||
}
|
||||
|
||||
mMemoryReporter = new WorkerMemoryReporter(this);
|
||||
|
||||
if (NS_FAILED(NS_RegisterMemoryMultiReporter(mMemoryReporter))) {
|
||||
@ -2504,6 +2591,8 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
|
||||
|
||||
for (;;) {
|
||||
Status currentStatus;
|
||||
bool scheduleIdleGC;
|
||||
|
||||
nsIRunnable* event;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
@ -2512,19 +2601,72 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
|
||||
mCondVar.Wait();
|
||||
}
|
||||
|
||||
bool eventIsNotIdleGCEvent;
|
||||
currentStatus = mStatus;
|
||||
|
||||
{
|
||||
MutexAutoUnlock unlock(mMutex);
|
||||
|
||||
if (!normalGCTimerRunning &&
|
||||
event != idleGCEvent &&
|
||||
currentStatus <= Terminating) {
|
||||
// Must always cancel before changing the timer's target.
|
||||
if (NS_FAILED(gcTimer->Cancel())) {
|
||||
NS_WARNING("Failed to cancel GC timer!");
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(gcTimer->SetTarget(normalGCEventTarget)) &&
|
||||
NS_SUCCEEDED(gcTimer->InitWithFuncCallback(
|
||||
DummyCallback, nsnull,
|
||||
NORMAL_GC_TIMER_DELAY_MS,
|
||||
nsITimer::TYPE_REPEATING_SLACK))) {
|
||||
normalGCTimerRunning = true;
|
||||
}
|
||||
else {
|
||||
JS_ReportError(aCx, "Failed to start normal GC timer!");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef EXTRA_GC
|
||||
// Find GC bugs...
|
||||
JS_GC(aCx);
|
||||
#endif
|
||||
|
||||
// Keep track of whether or not this is the idle GC event.
|
||||
eventIsNotIdleGCEvent = event != idleGCEvent;
|
||||
|
||||
event->Run();
|
||||
NS_RELEASE(event);
|
||||
}
|
||||
|
||||
currentStatus = mStatus;
|
||||
scheduleIdleGC = mControlQueue.IsEmpty() &&
|
||||
mQueue.IsEmpty() &&
|
||||
eventIsNotIdleGCEvent;
|
||||
}
|
||||
|
||||
// Take care of the GC timer. If we're starting the close sequence then we
|
||||
// kill the timer once and for all. Otherwise we schedule the idle timeout
|
||||
// if there are no more events.
|
||||
if (currentStatus > Terminating || scheduleIdleGC) {
|
||||
if (NS_SUCCEEDED(gcTimer->Cancel())) {
|
||||
normalGCTimerRunning = false;
|
||||
}
|
||||
else {
|
||||
NS_WARNING("Failed to cancel GC timer!");
|
||||
}
|
||||
}
|
||||
|
||||
if (scheduleIdleGC) {
|
||||
if (NS_SUCCEEDED(gcTimer->SetTarget(idleGCEventTarget)) &&
|
||||
NS_SUCCEEDED(gcTimer->InitWithFuncCallback(
|
||||
DummyCallback, nsnull,
|
||||
IDLE_GC_TIMER_DELAY_MS,
|
||||
nsITimer::TYPE_ONE_SHOT))) {
|
||||
}
|
||||
else {
|
||||
JS_ReportError(aCx, "Failed to start idle GC timer!");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef EXTRA_GC
|
||||
@ -2552,6 +2694,11 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
|
||||
|
||||
// If we're supposed to die then we should exit the loop.
|
||||
if (currentStatus == Killing) {
|
||||
// Always make sure the timer is canceled.
|
||||
if (NS_FAILED(gcTimer->Cancel())) {
|
||||
NS_WARNING("Failed to cancel the GC timer!");
|
||||
}
|
||||
|
||||
// Call this before unregistering the reporter as we may be racing with
|
||||
// the main thread.
|
||||
DisableMemoryReporter();
|
||||
@ -3640,6 +3787,26 @@ WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, PRUint8 aGCZeal)
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
|
||||
bool aCollectChildren)
|
||||
{
|
||||
AssertIsOnWorkerThread();
|
||||
|
||||
if (aShrinking) {
|
||||
JS_ShrinkingGC(aCx);
|
||||
}
|
||||
else {
|
||||
JS_GC(aCx);
|
||||
}
|
||||
|
||||
if (aCollectChildren) {
|
||||
for (PRUint32 index = 0; index < mChildWorkers.Length(); index++) {
|
||||
mChildWorkers[index]->GarbageCollect(aCx, aShrinking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
template <class Derived>
|
||||
void
|
||||
|
@ -318,6 +318,9 @@ public:
|
||||
UpdateGCZeal(JSContext* aCx, PRUint8 aGCZeal);
|
||||
#endif
|
||||
|
||||
void
|
||||
GarbageCollect(JSContext* aCx, bool aShrinking);
|
||||
|
||||
using events::EventTarget::GetEventListenerOnEventTarget;
|
||||
using events::EventTarget::SetEventListenerOnEventTarget;
|
||||
|
||||
@ -686,6 +689,10 @@ public:
|
||||
UpdateGCZealInternal(JSContext* aCx, PRUint8 aGCZeal);
|
||||
#endif
|
||||
|
||||
void
|
||||
GarbageCollectInternal(JSContext* aCx, bool aShrinking,
|
||||
bool aCollectChildren);
|
||||
|
||||
JSContext*
|
||||
GetJSContext() const
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user