1 /* 2 * Copyright (C) 2009 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "V8GCController.h" 33 34 #include "DOMDataStore.h" 35 #include "DOMObjectsInclude.h" 36 #include "V8DOMMap.h" 37 #include "V8Index.h" 38 #include "V8Proxy.h" 39 40 #include <algorithm> 41 #include <utility> 42 #include <v8.h> 43 #include <v8-debug.h> 44 #include <wtf/HashMap.h> 45 #include <wtf/StdLibExtras.h> 46 #include <wtf/UnusedParam.h> 47 48 namespace WebCore { 49 50 #ifndef NDEBUG 51 // Keeps track of global handles created (not JS wrappers 52 // of DOM objects). Often these global handles are source 53 // of leaks. 54 // 55 // If you want to let a C++ object hold a persistent handle 56 // to a JS object, you should register the handle here to 57 // keep track of leaks. 58 // 59 // When creating a persistent handle, call: 60 // 61 // #ifndef NDEBUG 62 // V8GCController::registerGlobalHandle(type, host, handle); 63 // #endif 64 // 65 // When releasing the handle, call: 66 // 67 // #ifndef NDEBUG 68 // V8GCController::unregisterGlobalHandle(type, host, handle); 69 // #endif 70 // 71 typedef HashMap<v8::Value*, GlobalHandleInfo*> GlobalHandleMap; 72 73 static GlobalHandleMap& globalHandleMap() 74 { 75 DEFINE_STATIC_LOCAL(GlobalHandleMap, staticGlobalHandleMap, ()); 76 return staticGlobalHandleMap; 77 } 78 79 // The function is the place to set the break point to inspect 80 // live global handles. Leaks are often come from leaked global handles. 81 static void enumerateGlobalHandles() 82 { 83 for (GlobalHandleMap::iterator it = globalHandleMap().begin(), end = globalHandleMap().end(); it != end; ++it) { 84 GlobalHandleInfo* info = it->second; 85 UNUSED_PARAM(info); 86 v8::Value* handle = it->first; 87 UNUSED_PARAM(handle); 88 } 89 } 90 91 void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle) 92 { 93 ASSERT(!globalHandleMap().contains(*handle)); 94 globalHandleMap().set(*handle, new GlobalHandleInfo(host, type)); 95 } 96 97 void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle) 98 { 99 ASSERT(globalHandleMap().contains(*handle)); 100 GlobalHandleInfo* info = globalHandleMap().take(*handle); 101 ASSERT(info->m_host == host); 102 delete info; 103 } 104 #endif // ifndef NDEBUG 105 106 typedef HashMap<Node*, v8::Object*> DOMNodeMap; 107 typedef HashMap<void*, v8::Object*> DOMObjectMap; 108 109 #ifndef NDEBUG 110 111 static void enumerateDOMObjectMap(DOMObjectMap& wrapperMap) 112 { 113 for (DOMObjectMap::iterator it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) { 114 v8::Persistent<v8::Object> wrapper(it->second); 115 V8ClassIndex::V8WrapperType type = V8DOMWrapper::domWrapperType(wrapper); 116 void* object = it->first; 117 UNUSED_PARAM(type); 118 UNUSED_PARAM(object); 119 } 120 } 121 122 class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor { 123 public: 124 void visitDOMWrapper(void* object, v8::Persistent<v8::Object> wrapper) 125 { 126 V8ClassIndex::V8WrapperType type = V8DOMWrapper::domWrapperType(wrapper); 127 UNUSED_PARAM(type); 128 UNUSED_PARAM(object); 129 } 130 }; 131 132 class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor { 133 public: 134 void visitDOMWrapper(Node* object, v8::Persistent<v8::Object> wrapper) 135 { 136 UNUSED_PARAM(object); 137 ASSERT(wrapper.IsWeak()); 138 } 139 }; 140 141 #endif // NDEBUG 142 143 // A map from a DOM node to its JS wrapper, the wrapper 144 // is kept as a strong reference to survive GCs. 145 static DOMObjectMap& gcProtectedMap() 146 { 147 DEFINE_STATIC_LOCAL(DOMObjectMap, staticGcProtectedMap, ()); 148 return staticGcProtectedMap; 149 } 150 151 void V8GCController::gcProtect(void* domObject) 152 { 153 if (!domObject) 154 return; 155 if (gcProtectedMap().contains(domObject)) 156 return; 157 if (!getDOMObjectMap().contains(domObject)) 158 return; 159 160 // Create a new (strong) persistent handle for the object. 161 v8::Persistent<v8::Object> wrapper = getDOMObjectMap().get(domObject); 162 if (wrapper.IsEmpty()) 163 return; 164 165 gcProtectedMap().set(domObject, *v8::Persistent<v8::Object>::New(wrapper)); 166 } 167 168 void V8GCController::gcUnprotect(void* domObject) 169 { 170 if (!domObject) 171 return; 172 if (!gcProtectedMap().contains(domObject)) 173 return; 174 175 // Dispose the strong reference. 176 v8::Persistent<v8::Object> wrapper(gcProtectedMap().take(domObject)); 177 wrapper.Dispose(); 178 } 179 180 class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor { 181 public: 182 void visitDOMWrapper(void* object, v8::Persistent<v8::Object> wrapper) 183 { 184 ASSERT(wrapper.IsWeak()); 185 V8ClassIndex::V8WrapperType type = V8DOMWrapper::domWrapperType(wrapper); 186 switch (type) { 187 #define MAKE_CASE(TYPE, NAME) \ 188 case V8ClassIndex::TYPE: { \ 189 NAME* impl = static_cast<NAME*>(object); \ 190 if (impl->hasPendingActivity()) \ 191 wrapper.ClearWeak(); \ 192 break; \ 193 } 194 ACTIVE_DOM_OBJECT_TYPES(MAKE_CASE) 195 default: 196 ASSERT_NOT_REACHED(); 197 #undef MAKE_CASE 198 } 199 200 // Additional handling of message port ensuring that entangled ports also 201 // have their wrappers entangled. This should ideally be handled when the 202 // ports are actually entangled in MessagePort::entangle, but to avoid 203 // forking MessagePort.* this is postponed to GC time. Having this postponed 204 // has the drawback that the wrappers are "entangled/unentangled" for each 205 // GC even though their entaglement most likely is still the same. 206 if (type == V8ClassIndex::MESSAGEPORT) { 207 // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled, 208 // since the Chromium port implementation can't tell the difference. 209 MessagePort* port1 = static_cast<MessagePort*>(object); 210 if (port1->isEntangled()) 211 wrapper.ClearWeak(); 212 } 213 } 214 }; 215 216 class GrouperItem { 217 public: 218 GrouperItem(uintptr_t groupId, Node* node, v8::Persistent<v8::Object> wrapper) 219 : m_groupId(groupId) 220 , m_node(node) 221 , m_wrapper(wrapper) 222 { 223 } 224 225 uintptr_t groupId() const { return m_groupId; } 226 Node* node() const { return m_node; } 227 v8::Persistent<v8::Object> wrapper() const { return m_wrapper; } 228 229 private: 230 uintptr_t m_groupId; 231 Node* m_node; 232 v8::Persistent<v8::Object> m_wrapper; 233 }; 234 235 bool operator<(const GrouperItem& a, const GrouperItem& b) 236 { 237 return a.groupId() < b.groupId(); 238 } 239 240 typedef Vector<GrouperItem> GrouperList; 241 242 class ObjectGrouperVisitor : public DOMWrapperMap<Node>::Visitor { 243 public: 244 ObjectGrouperVisitor() 245 { 246 // FIXME: grouper_.reserveCapacity(node_map.size()); ? 247 } 248 249 void visitDOMWrapper(Node* node, v8::Persistent<v8::Object> wrapper) 250 { 251 252 // If the node is in document, put it in the ownerDocument's object group. 253 // 254 // If an image element was created by JavaScript "new Image", 255 // it is not in a document. However, if the load event has not 256 // been fired (still onloading), it is treated as in the document. 257 // 258 // Otherwise, the node is put in an object group identified by the root 259 // element of the tree to which it belongs. 260 uintptr_t groupId; 261 if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent())) 262 groupId = reinterpret_cast<uintptr_t>(node->document()); 263 else { 264 Node* root = node; 265 if (node->isAttributeNode()) { 266 root = static_cast<Attr*>(node)->ownerElement(); 267 // If the attribute has no element, no need to put it in the group, 268 // because it'll always be a group of 1. 269 if (!root) 270 return; 271 } else { 272 while (root->parent()) 273 root = root->parent(); 274 275 // If the node is alone in its DOM tree (doesn't have a parent or any 276 // children) then the group will be filtered out later anyway. 277 if (root == node && !node->hasChildNodes() && !node->hasAttributes()) 278 return; 279 } 280 groupId = reinterpret_cast<uintptr_t>(root); 281 } 282 m_grouper.append(GrouperItem(groupId, node, wrapper)); 283 } 284 285 void applyGrouping() 286 { 287 // Group by sorting by the group id. 288 std::sort(m_grouper.begin(), m_grouper.end()); 289 290 // FIXME Should probably work in iterators here, but indexes were easier for my simple mind. 291 for (size_t i = 0; i < m_grouper.size(); ) { 292 // Seek to the next key (or the end of the list). 293 size_t nextKeyIndex = m_grouper.size(); 294 for (size_t j = i; j < m_grouper.size(); ++j) { 295 if (m_grouper[i].groupId() != m_grouper[j].groupId()) { 296 nextKeyIndex = j; 297 break; 298 } 299 } 300 301 ASSERT(nextKeyIndex > i); 302 303 // We only care about a group if it has more than one object. If it only 304 // has one object, it has nothing else that needs to be kept alive. 305 if (nextKeyIndex - i <= 1) { 306 i = nextKeyIndex; 307 continue; 308 } 309 310 Vector<v8::Persistent<v8::Value> > group; 311 group.reserveCapacity(nextKeyIndex - i); 312 for (; i < nextKeyIndex; ++i) { 313 v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper(); 314 if (!wrapper.IsEmpty()) 315 group.append(wrapper); 316 /* FIXME: Re-enabled this code to avoid GCing these wrappers! 317 Currently this depends on looking up the wrapper 318 during a GC, but we don't know which isolated world 319 we're in, so it's unclear which map to look in... 320 321 // If the node is styled and there is a wrapper for the inline 322 // style declaration, we need to keep that style declaration 323 // wrapper alive as well, so we add it to the object group. 324 if (node->isStyledElement()) { 325 StyledElement* element = reinterpret_cast<StyledElement*>(node); 326 CSSStyleDeclaration* style = element->inlineStyleDecl(); 327 if (style != NULL) { 328 wrapper = getDOMObjectMap().get(style); 329 if (!wrapper.IsEmpty()) 330 group.append(wrapper); 331 } 332 } 333 */ 334 } 335 336 if (group.size() > 1) 337 v8::V8::AddObjectGroup(&group[0], group.size()); 338 339 ASSERT(i == nextKeyIndex); 340 } 341 } 342 343 private: 344 GrouperList m_grouper; 345 }; 346 347 // Create object groups for DOM tree nodes. 348 void V8GCController::gcPrologue() 349 { 350 v8::HandleScope scope; 351 352 #ifndef NDEBUG 353 DOMObjectVisitor domObjectVisitor; 354 visitDOMObjectsInCurrentThread(&domObjectVisitor); 355 #endif 356 357 // Run through all objects with possible pending activity making their 358 // wrappers non weak if there is pending activity. 359 GCPrologueVisitor prologueVisitor; 360 visitActiveDOMObjectsInCurrentThread(&prologueVisitor); 361 362 // Create object groups. 363 ObjectGrouperVisitor objectGrouperVisitor; 364 visitDOMNodesInCurrentThread(&objectGrouperVisitor); 365 objectGrouperVisitor.applyGrouping(); 366 } 367 368 class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor { 369 public: 370 void visitDOMWrapper(void* object, v8::Persistent<v8::Object> wrapper) 371 { 372 V8ClassIndex::V8WrapperType type = V8DOMWrapper::domWrapperType(wrapper); 373 switch (type) { 374 #define MAKE_CASE(TYPE, NAME) \ 375 case V8ClassIndex::TYPE: { \ 376 NAME* impl = static_cast<NAME*>(object); \ 377 if (impl->hasPendingActivity()) { \ 378 ASSERT(!wrapper.IsWeak()); \ 379 wrapper.MakeWeak(impl, &DOMDataStore::weakActiveDOMObjectCallback); \ 380 } \ 381 break; \ 382 } 383 ACTIVE_DOM_OBJECT_TYPES(MAKE_CASE) 384 default: 385 ASSERT_NOT_REACHED(); 386 #undef MAKE_CASE 387 } 388 389 if (type == V8ClassIndex::MESSAGEPORT) { 390 MessagePort* port1 = static_cast<MessagePort*>(object); 391 // We marked this port as reachable in GCPrologueVisitor. Undo this now since the 392 // port could be not reachable in the future if it gets disentangled (and also 393 // GCPrologueVisitor expects to see all handles marked as weak). 394 if (!wrapper.IsWeak() && !wrapper.IsNearDeath()) 395 wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback); 396 } 397 } 398 }; 399 400 int V8GCController::workingSetEstimateMB = 0; 401 402 namespace { 403 404 int getMemoryUsageInMB() 405 { 406 #if PLATFORM(CHROMIUM) 407 return ChromiumBridge::memoryUsageMB(); 408 #else 409 return 0; 410 #endif 411 } 412 413 } // anonymous namespace 414 415 void V8GCController::gcEpilogue() 416 { 417 v8::HandleScope scope; 418 419 // Run through all objects with pending activity making their wrappers weak 420 // again. 421 GCEpilogueVisitor epilogueVisitor; 422 visitActiveDOMObjectsInCurrentThread(&epilogueVisitor); 423 424 workingSetEstimateMB = getMemoryUsageInMB(); 425 426 #ifndef NDEBUG 427 // Check all survivals are weak. 428 DOMObjectVisitor domObjectVisitor; 429 visitDOMObjectsInCurrentThread(&domObjectVisitor); 430 431 EnsureWeakDOMNodeVisitor weakDOMNodeVisitor; 432 visitDOMNodesInCurrentThread(&weakDOMNodeVisitor); 433 434 enumerateDOMObjectMap(gcProtectedMap()); 435 enumerateGlobalHandles(); 436 #endif 437 } 438 439 void V8GCController::checkMemoryUsage() 440 { 441 #if PLATFORM(CHROMIUM) 442 // These values are appropriate for Chromium only. 443 const int lowUsageMB = 256; // If memory usage is below this threshold, do not bother forcing GC. 444 const int highUsageMB = 1024; // If memory usage is above this threshold, force GC more aggresively. 445 const int highUsageDeltaMB = 128; // Delta of memory usage growth (vs. last workingSetEstimateMB) to force GC when memory usage is high. 446 447 int memoryUsageMB = getMemoryUsageInMB(); 448 if ((memoryUsageMB > lowUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB)) 449 v8::V8::LowMemoryNotification(); 450 #endif 451 } 452 453 454 } // namespace WebCore 455