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