Home | History | Annotate | Download | only in v8
      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