• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
globalHandleMap()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.
enumerateGlobalHandles()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 
registerGlobalHandle(GlobalHandleType type,void * host,v8::Persistent<v8::Value> handle)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 
unregisterGlobalHandle(void * host,v8::Persistent<v8::Value> handle)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 
enumerateDOMObjectMap(DOMObjectMap & wrapperMap)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:
visitDOMWrapper(void * object,v8::Persistent<v8::Object> wrapper)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:
visitDOMWrapper(Node * object,v8::Persistent<v8::Object> wrapper)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.
gcProtectedMap()145 static DOMObjectMap& gcProtectedMap()
146 {
147     DEFINE_STATIC_LOCAL(DOMObjectMap, staticGcProtectedMap, ());
148     return staticGcProtectedMap;
149 }
150 
gcProtect(void * domObject)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 
gcUnprotect(void * domObject)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:
visitDOMWrapper(void * object,v8::Persistent<v8::Object> wrapper)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:
GrouperItem(uintptr_t groupId,Node * node,v8::Persistent<v8::Object> wrapper)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 
groupId() const225     uintptr_t groupId() const { return m_groupId; }
node() const226     Node* node() const { return m_node; }
wrapper() const227     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 
operator <(const GrouperItem & a,const GrouperItem & b)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:
ObjectGrouperVisitor()244     ObjectGrouperVisitor()
245     {
246         // FIXME: grouper_.reserveCapacity(node_map.size());  ?
247     }
248 
visitDOMWrapper(Node * node,v8::Persistent<v8::Object> wrapper)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 
applyGrouping()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.
gcPrologue()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:
visitDOMWrapper(void * object,v8::Persistent<v8::Object> wrapper)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 
getMemoryUsageInMB()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 
gcEpilogue()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 
checkMemoryUsage()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