V8GCController.cpp revision 2bde8e466a4451c7319e3a072d118917957d6554
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 "ActiveDOMObject.h"
35#include "Attr.h"
36#include "DOMDataStore.h"
37#include "DOMImplementation.h"
38#include "HTMLImageElement.h"
39#include "HTMLNames.h"
40#include "MessagePort.h"
41#include "PlatformBridge.h"
42#include "RetainedDOMInfo.h"
43#include "RetainedObjectInfo.h"
44#include "V8Binding.h"
45#include "V8CSSRule.h"
46#include "V8CSSRuleList.h"
47#include "V8CSSStyleDeclaration.h"
48#include "V8DOMImplementation.h"
49#include "V8MessagePort.h"
50#include "V8StyleSheet.h"
51#include "V8StyleSheetList.h"
52#include "WrapperTypeInfo.h"
53
54#include <algorithm>
55#include <utility>
56#include <v8-debug.h>
57#include <wtf/HashMap.h>
58#include <wtf/StdLibExtras.h>
59#include <wtf/UnusedParam.h>
60
61namespace WebCore {
62
63#ifndef NDEBUG
64// Keeps track of global handles created (not JS wrappers
65// of DOM objects). Often these global handles are source
66// of leaks.
67//
68// If you want to let a C++ object hold a persistent handle
69// to a JS object, you should register the handle here to
70// keep track of leaks.
71//
72// When creating a persistent handle, call:
73//
74// #ifndef NDEBUG
75//    V8GCController::registerGlobalHandle(type, host, handle);
76// #endif
77//
78// When releasing the handle, call:
79//
80// #ifndef NDEBUG
81//    V8GCController::unregisterGlobalHandle(type, host, handle);
82// #endif
83//
84typedef HashMap<v8::Value*, GlobalHandleInfo*> GlobalHandleMap;
85
86static GlobalHandleMap& globalHandleMap()
87{
88    DEFINE_STATIC_LOCAL(GlobalHandleMap, staticGlobalHandleMap, ());
89    return staticGlobalHandleMap;
90}
91
92// The function is the place to set the break point to inspect
93// live global handles. Leaks are often come from leaked global handles.
94static void enumerateGlobalHandles()
95{
96    for (GlobalHandleMap::iterator it = globalHandleMap().begin(), end = globalHandleMap().end(); it != end; ++it) {
97        GlobalHandleInfo* info = it->second;
98        UNUSED_PARAM(info);
99        v8::Value* handle = it->first;
100        UNUSED_PARAM(handle);
101    }
102}
103
104void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle)
105{
106    ASSERT(!globalHandleMap().contains(*handle));
107    globalHandleMap().set(*handle, new GlobalHandleInfo(host, type));
108}
109
110void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle)
111{
112    ASSERT(globalHandleMap().contains(*handle));
113    GlobalHandleInfo* info = globalHandleMap().take(*handle);
114    ASSERT(info->m_host == host);
115    delete info;
116}
117#endif // ifndef NDEBUG
118
119typedef HashMap<Node*, v8::Object*> DOMNodeMap;
120typedef HashMap<void*, v8::Object*> DOMObjectMap;
121
122#ifndef NDEBUG
123
124class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor {
125public:
126    void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
127    {
128        WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper);
129        UNUSED_PARAM(type);
130        UNUSED_PARAM(object);
131    }
132};
133
134class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor {
135public:
136    void visitDOMWrapper(DOMDataStore* store, Node* object, v8::Persistent<v8::Object> wrapper)
137    {
138        UNUSED_PARAM(object);
139        ASSERT(wrapper.IsWeak());
140    }
141};
142
143#endif // NDEBUG
144
145class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor {
146public:
147    void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
148    {
149        WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
150
151        // Additional handling of message port ensuring that entangled ports also
152        // have their wrappers entangled. This should ideally be handled when the
153        // ports are actually entangled in MessagePort::entangle, but to avoid
154        // forking MessagePort.* this is postponed to GC time. Having this postponed
155        // has the drawback that the wrappers are "entangled/unentangled" for each
156        // GC even though their entaglement most likely is still the same.
157        if (V8MessagePort::info.equals(typeInfo)) {
158            // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled,
159            // since the Chromium port implementation can't tell the difference.
160            MessagePort* port1 = static_cast<MessagePort*>(object);
161            if (port1->isEntangled() || port1->hasPendingActivity())
162                wrapper.ClearWeak();
163        } else {
164            ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
165            if (activeDOMObject && activeDOMObject->hasPendingActivity())
166                wrapper.ClearWeak();
167        }
168    }
169};
170
171// Implements v8::RetainedObjectInfo.
172class UnspecifiedGroup : public RetainedObjectInfo {
173public:
174    explicit UnspecifiedGroup(void* object)
175        : m_object(object)
176    {
177        ASSERT(m_object);
178    }
179
180    virtual void Dispose() { delete this; }
181
182    virtual bool IsEquivalent(v8::RetainedObjectInfo* other)
183    {
184        ASSERT(other);
185        return other == this || static_cast<WebCore::RetainedObjectInfo*>(other)->GetEquivalenceClass() == this->GetEquivalenceClass();
186    }
187
188    virtual intptr_t GetHash()
189    {
190        return reinterpret_cast<intptr_t>(m_object);
191    }
192
193    virtual const char* GetLabel()
194    {
195        return "Object group";
196    }
197
198    virtual intptr_t GetEquivalenceClass()
199    {
200        return reinterpret_cast<intptr_t>(m_object);
201    }
202
203private:
204    void* m_object;
205};
206
207class GroupId {
208public:
209    GroupId() : m_type(NullType), m_groupId(0) {}
210    GroupId(Node* node) : m_type(NodeType), m_node(node) {}
211    GroupId(void* other) : m_type(OtherType), m_other(other) {}
212    bool operator!() const { return m_type == NullType; }
213    uintptr_t groupId() const { return m_groupId; }
214    RetainedObjectInfo* createRetainedObjectInfo() const
215    {
216        switch (m_type) {
217        case NullType:
218            return 0;
219        case NodeType:
220            return new RetainedDOMInfo(m_node);
221        case OtherType:
222            return new UnspecifiedGroup(m_other);
223        default:
224            return 0;
225        }
226    }
227
228private:
229    enum Type {
230        NullType,
231        NodeType,
232        OtherType
233    };
234    Type m_type;
235    union {
236        uintptr_t m_groupId;
237        Node* m_node;
238        void* m_other;
239    };
240};
241
242class GrouperItem {
243public:
244    GrouperItem(GroupId groupId, v8::Persistent<v8::Object> wrapper) : m_groupId(groupId), m_wrapper(wrapper) {}
245    uintptr_t groupId() const { return m_groupId.groupId(); }
246    RetainedObjectInfo* createRetainedObjectInfo() const { return m_groupId.createRetainedObjectInfo(); }
247    v8::Persistent<v8::Object> wrapper() const { return m_wrapper; }
248
249private:
250    GroupId m_groupId;
251    v8::Persistent<v8::Object> m_wrapper;
252};
253
254bool operator<(const GrouperItem& a, const GrouperItem& b)
255{
256    return a.groupId() < b.groupId();
257}
258
259typedef Vector<GrouperItem> GrouperList;
260
261// If the node is in document, put it in the ownerDocument's object group.
262//
263// If an image element was created by JavaScript "new Image",
264// it is not in a document. However, if the load event has not
265// been fired (still onloading), it is treated as in the document.
266//
267// Otherwise, the node is put in an object group identified by the root
268// element of the tree to which it belongs.
269static GroupId calculateGroupId(Node* node)
270{
271    if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent()))
272        return GroupId(node->document());
273
274    Node* root = node;
275    if (node->isAttributeNode()) {
276        root = static_cast<Attr*>(node)->ownerElement();
277        // If the attribute has no element, no need to put it in the group,
278        // because it'll always be a group of 1.
279        if (!root)
280            return GroupId();
281    } else {
282        while (Node* parent = root->parentNode())
283            root = parent;
284    }
285
286    return GroupId(root);
287}
288
289static GroupId calculateGroupId(StyleBase* styleBase)
290{
291    ASSERT(styleBase);
292    StyleBase* current = styleBase;
293    StyleSheet* styleSheet = 0;
294    while (true) {
295        // Special case: CSSStyleDeclarations might be either inline and in this case
296        // we need to group them with their node or regular ones.
297        if (current->isMutableStyleDeclaration()) {
298            CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(current);
299            if (cssMutableStyleDeclaration->isInlineStyleDeclaration()) {
300                ASSERT(cssMutableStyleDeclaration->parent()->isStyleSheet());
301                return calculateGroupId(cssMutableStyleDeclaration->node());
302            }
303            // Either we have no parent, or this parent is a CSSRule.
304            ASSERT(cssMutableStyleDeclaration->parent() == cssMutableStyleDeclaration->parentRule());
305        }
306
307        if (current->isStyleSheet())
308            styleSheet = static_cast<StyleSheet*>(current);
309
310        StyleBase* parent = current->parent();
311        if (!parent)
312            break;
313        current = parent;
314    }
315
316    if (styleSheet) {
317        if (Node* ownerNode = styleSheet->ownerNode())
318            return calculateGroupId(ownerNode);
319        return GroupId(styleSheet);
320    }
321
322    return GroupId(current);
323}
324
325class GrouperVisitor : public DOMWrapperMap<Node>::Visitor, public DOMWrapperMap<void>::Visitor {
326public:
327    void visitDOMWrapper(DOMDataStore* store, Node* node, v8::Persistent<v8::Object> wrapper)
328    {
329        GroupId groupId = calculateGroupId(node);
330        if (!groupId)
331            return;
332        m_grouper.append(GrouperItem(groupId, wrapper));
333    }
334
335    void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
336    {
337        WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
338
339        if (typeInfo->isSubclass(&V8StyleSheetList::info)) {
340            StyleSheetList* styleSheetList = static_cast<StyleSheetList*>(object);
341            GroupId groupId(styleSheetList);
342            if (Document* document = styleSheetList->document())
343                groupId = GroupId(document);
344            m_grouper.append(GrouperItem(groupId, wrapper));
345
346        } else if (typeInfo->isSubclass(&V8DOMImplementation::info)) {
347            DOMImplementation* domImplementation = static_cast<DOMImplementation*>(object);
348            GroupId groupId(domImplementation);
349            if (Document* document = domImplementation->ownerDocument())
350                groupId = GroupId(document);
351            m_grouper.append(GrouperItem(groupId, wrapper));
352
353        } else if (typeInfo->isSubclass(&V8StyleSheet::info) || typeInfo->isSubclass(&V8CSSRule::info)) {
354            m_grouper.append(GrouperItem(calculateGroupId(static_cast<StyleBase*>(object)), wrapper));
355
356        } else if (typeInfo->isSubclass(&V8CSSStyleDeclaration::info)) {
357            CSSStyleDeclaration* cssStyleDeclaration = static_cast<CSSStyleDeclaration*>(object);
358
359            GroupId groupId = calculateGroupId(cssStyleDeclaration);
360            m_grouper.append(GrouperItem(groupId, wrapper));
361
362        } else if (typeInfo->isSubclass(&V8CSSRuleList::info)) {
363            CSSRuleList* cssRuleList = static_cast<CSSRuleList*>(object);
364            GroupId groupId(cssRuleList);
365            StyleList* styleList = cssRuleList->styleList();
366            if (styleList)
367                groupId = calculateGroupId(styleList);
368            m_grouper.append(GrouperItem(groupId, wrapper));
369        }
370    }
371
372    void applyGrouping()
373    {
374        // Group by sorting by the group id.
375        std::sort(m_grouper.begin(), m_grouper.end());
376
377        for (size_t i = 0; i < m_grouper.size(); ) {
378            // Seek to the next key (or the end of the list).
379            size_t nextKeyIndex = m_grouper.size();
380            for (size_t j = i; j < m_grouper.size(); ++j) {
381                if (m_grouper[i].groupId() != m_grouper[j].groupId()) {
382                    nextKeyIndex = j;
383                    break;
384                }
385            }
386
387            ASSERT(nextKeyIndex > i);
388
389            // We only care about a group if it has more than one object. If it only
390            // has one object, it has nothing else that needs to be kept alive.
391            if (nextKeyIndex - i <= 1) {
392                i = nextKeyIndex;
393                continue;
394            }
395
396            size_t rootIndex = i;
397
398            Vector<v8::Persistent<v8::Value> > group;
399            group.reserveCapacity(nextKeyIndex - i);
400            for (; i < nextKeyIndex; ++i) {
401                v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper();
402                if (!wrapper.IsEmpty())
403                    group.append(wrapper);
404            }
405
406            if (group.size() > 1)
407                v8::V8::AddObjectGroup(&group[0], group.size(), m_grouper[rootIndex].createRetainedObjectInfo());
408
409            ASSERT(i == nextKeyIndex);
410        }
411    }
412
413private:
414    GrouperList m_grouper;
415};
416
417// Create object groups for DOM tree nodes.
418void V8GCController::gcPrologue()
419{
420    v8::HandleScope scope;
421
422#ifndef NDEBUG
423    DOMObjectVisitor domObjectVisitor;
424    visitDOMObjectsInCurrentThread(&domObjectVisitor);
425#endif
426
427    // Run through all objects with possible pending activity making their
428    // wrappers non weak if there is pending activity.
429    GCPrologueVisitor prologueVisitor;
430    visitActiveDOMObjectsInCurrentThread(&prologueVisitor);
431
432    // Create object groups.
433    GrouperVisitor grouperVisitor;
434    visitDOMNodesInCurrentThread(&grouperVisitor);
435    visitDOMObjectsInCurrentThread(&grouperVisitor);
436    grouperVisitor.applyGrouping();
437
438    // Clean single element cache for string conversions.
439    lastStringImpl = 0;
440    lastV8String.Clear();
441}
442
443class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor {
444public:
445    void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
446    {
447        WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
448        if (V8MessagePort::info.equals(typeInfo)) {
449            MessagePort* port1 = static_cast<MessagePort*>(object);
450            // We marked this port as reachable in GCPrologueVisitor.  Undo this now since the
451            // port could be not reachable in the future if it gets disentangled (and also
452            // GCPrologueVisitor expects to see all handles marked as weak).
453            if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity())
454                wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback);
455        } else {
456            ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
457            if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
458                ASSERT(!wrapper.IsWeak());
459                // NOTE: To re-enable weak status of the active object we use
460                // |object| from the map and not |activeDOMObject|. The latter
461                // may be a different pointer (in case ActiveDOMObject is not
462                // the main base class of the object's class) and pointer
463                // identity is required by DOM map functions.
464                wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback);
465            }
466        }
467    }
468};
469
470int V8GCController::workingSetEstimateMB = 0;
471
472namespace {
473
474int getMemoryUsageInMB()
475{
476#if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
477    return PlatformBridge::memoryUsageMB();
478#else
479    return 0;
480#endif
481}
482
483int getActualMemoryUsageInMB()
484{
485#if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
486    return PlatformBridge::actualMemoryUsageMB();
487#else
488    return 0;
489#endif
490}
491
492}  // anonymous namespace
493
494void V8GCController::gcEpilogue()
495{
496    v8::HandleScope scope;
497
498    // Run through all objects with pending activity making their wrappers weak
499    // again.
500    GCEpilogueVisitor epilogueVisitor;
501    visitActiveDOMObjectsInCurrentThread(&epilogueVisitor);
502
503    workingSetEstimateMB = getActualMemoryUsageInMB();
504
505#ifndef NDEBUG
506    // Check all survivals are weak.
507    DOMObjectVisitor domObjectVisitor;
508    visitDOMObjectsInCurrentThread(&domObjectVisitor);
509
510    EnsureWeakDOMNodeVisitor weakDOMNodeVisitor;
511    visitDOMNodesInCurrentThread(&weakDOMNodeVisitor);
512
513    enumerateGlobalHandles();
514#endif
515}
516
517void V8GCController::checkMemoryUsage()
518{
519#if PLATFORM(CHROMIUM) || PLATFORM(QT) && !OS(SYMBIAN)
520    // These values are appropriate for Chromium only.
521    const int lowUsageMB = 256;  // If memory usage is below this threshold, do not bother forcing GC.
522    const int highUsageMB = 1024;  // If memory usage is above this threshold, force GC more aggresively.
523    const int highUsageDeltaMB = 128;  // Delta of memory usage growth (vs. last workingSetEstimateMB) to force GC when memory usage is high.
524#elif PLATFORM(ANDROID)
525    // Query the PlatformBridge for memory thresholds as these vary device to device.
526    static const int lowUsageMB = PlatformBridge::lowMemoryUsageMB();
527    static const int highUsageMB = PlatformBridge::highMemoryUsageMB();
528    static const int highUsageDeltaMB = PlatformBridge::highUsageDeltaMB();
529#else
530    return;
531#endif
532
533    int memoryUsageMB = getMemoryUsageInMB();
534    if ((memoryUsageMB > lowUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB))
535        v8::V8::LowMemoryNotification();
536}
537
538
539}  // namespace WebCore
540