testGCFinalizeCallback.cpp (6439B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "js/GlobalObject.h" 6 #include "jsapi-tests/tests.h" 7 8 using namespace js; 9 using namespace JS; 10 11 static const unsigned BufSize = 20; 12 static unsigned FinalizeCalls = 0; 13 static JSFinalizeStatus StatusBuffer[BufSize]; 14 15 BEGIN_TEST(testGCFinalizeCallback) { 16 AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true); 17 AutoGCParameter param2(cx, JSGC_PER_ZONE_GC_ENABLED, true); 18 19 /* Full GC, non-incremental. */ 20 FinalizeCalls = 0; 21 JS_GC(cx); 22 CHECK(cx->runtime()->gc.isFullGc()); 23 CHECK(checkSingleGroup()); 24 CHECK(checkFinalizeStatus()); 25 26 /* Full GC, incremental. */ 27 FinalizeCalls = 0; 28 JS::PrepareForFullGC(cx); 29 SliceBudget startBudget(TimeBudget(1000000)); 30 JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, 31 startBudget); 32 while (cx->runtime()->gc.isIncrementalGCInProgress()) { 33 JS::PrepareForFullGC(cx); 34 SliceBudget budget(TimeBudget(1000000)); 35 JS::IncrementalGCSlice(cx, JS::GCReason::API, budget); 36 } 37 CHECK(!cx->runtime()->gc.isIncrementalGCInProgress()); 38 CHECK(cx->runtime()->gc.isFullGc()); 39 CHECK(checkMultipleGroups()); 40 CHECK(checkFinalizeStatus()); 41 42 #ifdef JS_GC_ZEAL 43 // Bug 1377593 - the below tests want to control how many zones are GC'ing, 44 // and some zeal modes will convert them into all-zones GCs. 45 JS::SetGCZeal(cx, 0, 0); 46 #endif 47 48 JS::RootedObject global1(cx, createTestGlobal()); 49 JS::RootedObject global2(cx, createTestGlobal()); 50 JS::RootedObject global3(cx, createTestGlobal()); 51 CHECK(global1); 52 CHECK(global2); 53 CHECK(global3); 54 55 /* Zone GC, non-incremental, single zone. */ 56 FinalizeCalls = 0; 57 JS::PrepareZoneForGC(cx, global1->zone()); 58 JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API); 59 CHECK(!cx->runtime()->gc.isFullGc()); 60 CHECK(checkSingleGroup()); 61 CHECK(checkFinalizeStatus()); 62 63 /* Zone GC, non-incremental, multiple zones. */ 64 FinalizeCalls = 0; 65 JS::PrepareZoneForGC(cx, global1->zone()); 66 JS::PrepareZoneForGC(cx, global2->zone()); 67 JS::PrepareZoneForGC(cx, global3->zone()); 68 JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API); 69 CHECK(!cx->runtime()->gc.isFullGc()); 70 CHECK(checkSingleGroup()); 71 CHECK(checkFinalizeStatus()); 72 73 /* Zone GC, incremental, single zone. */ 74 FinalizeCalls = 0; 75 JS::PrepareZoneForGC(cx, global1->zone()); 76 SliceBudget budget(TimeBudget(1000000)); 77 JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, budget); 78 while (cx->runtime()->gc.isIncrementalGCInProgress()) { 79 JS::PrepareZoneForGC(cx, global1->zone()); 80 budget = SliceBudget(TimeBudget(1000000)); 81 JS::IncrementalGCSlice(cx, JS::GCReason::API, budget); 82 } 83 CHECK(!cx->runtime()->gc.isIncrementalGCInProgress()); 84 CHECK(!cx->runtime()->gc.isFullGc()); 85 CHECK(checkSingleGroup()); 86 CHECK(checkFinalizeStatus()); 87 88 /* Zone GC, incremental, multiple zones. */ 89 FinalizeCalls = 0; 90 JS::PrepareZoneForGC(cx, global1->zone()); 91 JS::PrepareZoneForGC(cx, global2->zone()); 92 JS::PrepareZoneForGC(cx, global3->zone()); 93 budget = SliceBudget(TimeBudget(1000000)); 94 JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, budget); 95 while (cx->runtime()->gc.isIncrementalGCInProgress()) { 96 JS::PrepareZoneForGC(cx, global1->zone()); 97 JS::PrepareZoneForGC(cx, global2->zone()); 98 JS::PrepareZoneForGC(cx, global3->zone()); 99 budget = SliceBudget(TimeBudget(1000000)); 100 JS::IncrementalGCSlice(cx, JS::GCReason::API, budget); 101 } 102 CHECK(!cx->runtime()->gc.isIncrementalGCInProgress()); 103 CHECK(!cx->runtime()->gc.isFullGc()); 104 CHECK(checkMultipleGroups()); 105 CHECK(checkFinalizeStatus()); 106 107 #ifdef JS_GC_ZEAL 108 109 /* Full GC with reset due to new zone, becoming zone GC. */ 110 111 FinalizeCalls = 0; 112 JS::SetGCZeal(cx, 9, 1000000); 113 JS::PrepareForFullGC(cx); 114 budget = SliceBudget(WorkBudget(1)); 115 cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget); 116 CHECK(cx->runtime()->gc.state() == gc::State::Mark); 117 CHECK(cx->runtime()->gc.isFullGc()); 118 119 JS::RootedObject global4(cx, createTestGlobal()); 120 budget = SliceBudget(WorkBudget(1)); 121 cx->runtime()->gc.debugGCSlice(budget); 122 while (cx->runtime()->gc.isIncrementalGCInProgress()) { 123 cx->runtime()->gc.debugGCSlice(budget); 124 } 125 CHECK(!cx->runtime()->gc.isIncrementalGCInProgress()); 126 CHECK(checkSingleGroup()); 127 CHECK(checkFinalizeStatus()); 128 129 JS::SetGCZeal(cx, 0, 0); 130 131 #endif 132 133 /* 134 * Make some use of the globals here to ensure the compiler doesn't optimize 135 * them away in release builds, causing the zones to be collected and 136 * the test to fail. 137 */ 138 CHECK(JS_IsGlobalObject(global1)); 139 CHECK(JS_IsGlobalObject(global2)); 140 CHECK(JS_IsGlobalObject(global3)); 141 142 return true; 143 } 144 145 JSObject* createTestGlobal() { 146 JS::RealmOptions options; 147 return JS_NewGlobalObject(cx, getGlobalClass(), nullptr, 148 JS::FireOnNewGlobalHook, options); 149 } 150 151 virtual bool init() override { 152 if (!JSAPIRuntimeTest::init()) { 153 return false; 154 } 155 156 JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr); 157 return true; 158 } 159 160 virtual void uninit() override { 161 JS_RemoveFinalizeCallback(cx, FinalizeCallback); 162 JSAPIRuntimeTest::uninit(); 163 } 164 165 bool checkSingleGroup() { 166 CHECK(FinalizeCalls < BufSize); 167 CHECK(FinalizeCalls == 4); 168 return true; 169 } 170 171 bool checkMultipleGroups() { 172 CHECK(FinalizeCalls < BufSize); 173 CHECK(FinalizeCalls % 3 == 1); 174 CHECK((FinalizeCalls - 1) / 3 > 1); 175 return true; 176 } 177 178 bool checkFinalizeStatus() { 179 /* 180 * The finalize callback should be called twice for each sweep group 181 * finalized, with status JSFINALIZE_GROUP_START and JSFINALIZE_GROUP_END, 182 * and then once more with JSFINALIZE_COLLECTION_END. 183 */ 184 185 for (unsigned i = 0; i < FinalizeCalls - 1; i += 3) { 186 CHECK(StatusBuffer[i] == JSFINALIZE_GROUP_PREPARE); 187 CHECK(StatusBuffer[i + 1] == JSFINALIZE_GROUP_START); 188 CHECK(StatusBuffer[i + 2] == JSFINALIZE_GROUP_END); 189 } 190 191 CHECK(StatusBuffer[FinalizeCalls - 1] == JSFINALIZE_COLLECTION_END); 192 193 return true; 194 } 195 196 static void FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status, 197 void* data) { 198 if (FinalizeCalls < BufSize) { 199 StatusBuffer[FinalizeCalls] = status; 200 } 201 ++FinalizeCalls; 202 } 203 END_TEST(testGCFinalizeCallback)