RESOLVED FIXED 251835
The Document object is leaked on some pages using media (like YouTube.com)
https://bugs.webkit.org/show_bug.cgi?id=251835
Summary The Document object is leaked on some pages using media (like YouTube.com)
Simon Fraser (smfr)
Reported 2023-02-06 21:19:35 PST
Launch a recent Safari or MiniBrowser build from terminal (so you can see the output), and visit a YouTube.com video page (or a page with YouTube.com in a subframe). In that tab, navigate to about:blank, then trigger the low memory handler (to clear caches): > notifyutil -p "org.WebKit.lowMemory" then dump the list of live documents: > notifyutil -p "com.apple.WebKit.showAllDocuments" Note that the YouTube document, and probably several other documents are listed. Tests also show leaked Documents: run-webkit-tests media --world-leaks shows that these tests trigger Document leaks: media/W3C/audio/error/error_onerror_called_on_bogus_source.html media/W3C/audio/networkState/networkState_during_loadstart.html media/W3C/audio/src/src_reflects_attribute_not_source_elements.html media/W3C/video/canPlayType/canPlayType_application_octet_stream_with_codecs_1.html media/W3C/video/error/error_onerror_called_on_bogus_source.html media/W3C/video/events/event_canplay.html media/media-session/actionHandlerInternalMappings.html media/media-session/mock-actionHandlers.html media/media-session/user-gesture-action-handlers.html media/modern-media-controls/media-controller/media-controller-auto-hide-mouse-enter-and-mouse-leave.html media/modern-media-controls/overflow-support/playback-speed.html media/modern-media-controls/time-control/time-control.html media/modern-media-controls/tracks-support/text-track-selected-via-media-api.html media/picture-in-picture/picture-in-picture-interruption.html media/track/video-track-alternate-groups.html platform/mac/media/encrypted-media/fps-generateRequest.html See https://trac.webkit.org/wiki/Abandoned%20documents for Document leaks info
Attachments
Radar WebKit Bug Importer
Comment 1 2023-02-06 21:19:56 PST
Simon Fraser (smfr)
Comment 2 2023-02-06 21:29:12 PST
Reproduces in 254454@main. Not a recent regression.
Ryan Reno
Comment 3 2023-04-24 16:49:12 PDT
I've been working on bringing Simon’s Ref Tracking patch forward from 2018 and I think I've got it working such that we can see actionable stack traces to find leaked Documents. Unfortunately I can’t reproduce the leaks from LayoutTests in MiniBrowser. That is, if I navigate to one of the tests that run-webkit-tests reports as a leak and then navigate back to about:blank (or some other simple HTML document that is same-origin) then issue an org.WebKit.lowMemory notification the Document for the test is reclaimed. I can reliably reproduce the YouTube leak, though. The summary of this post is I think that for some reason the cached collections that are a result of calls to document.getElementsByClassName (and document.all and document.getElementsByTagName) are not being cleared and since they store a circular reference to a document that document is leaked. https://github.com/rreno/WebKit has a branch called ryan/ref-tracking-with-conditional-inheritance where Ref/RefPtr are instrumented similarly to Simon's patch from 2018. There are a number of tokens which did not get a corresponding deref though I think the most interesting and/or relevant stacks may be stacks which call document.getElementsByClassName, document.all, and document.getElementsByTagName. The former two seemed the most interesting as those were the results of HTMLMediaElement events and servicing rAF callbacks, respectively. In the stack containing HTMLMediaElement::dispatchEvent we're calling getElementsByClassName which stores an un-derefed ref on the document node as part of the CachedHTMLCollection. This appears to me to be a circular reference. In the stack servicing the rAF callback we're calling document.all which is again storing a Ref to the leaked document on the document node as part of the HTMLAllCollection. Here are the captured stacks: (HTMLMediaElement event stack) RefTracker: Backtrace for token 14621 (https://www.youtube.com/watch?v=REDACTED) 1 0x13400c37c WTF::RefTracker::trackRef(WTF::String const&) 2 0x14db51198 WebCore::Document::trackRef() 3 0x14db150d8 void WTF::RefTrackingTraits::ref<WebCore::ContainerNode>(WebCore::ContainerNode&) 4 0x14db15068 WTF::Ref<WebCore::ContainerNode, WTF::RawPtrTraits<WebCore::ContainerNode>, WTF::RefDerefTraits>::Ref(WebCore::ContainerNode&) 5 0x14daff6d4 WTF::Ref<WebCore::ContainerNode, WTF::RawPtrTraits<WebCore::ContainerNode>, WTF::RefDerefTraits>::Ref(WebCore::ContainerNode&) 6 0x14e0a5ce0 WebCore::HTMLCollection::HTMLCollection(WebCore::ContainerNode&, WebCore::CollectionType) 7 0x14db05508 WebCore::CachedHTMLCollection<WebCore::ClassCollection, (WebCore::CollectionTraversalType)0>::CachedHTMLCollection(WebCore::ContainerNode&, WebCore::CollectionType) 8 0x14db0545c WebCore::ClassCollection::ClassCollection(WebCore::ContainerNode&, WebCore::CollectionType, WTF::AtomString const&) 9 0x14db01aa4 WebCore::ClassCollection::ClassCollection(WebCore::ContainerNode&, WebCore::CollectionType, WTF::AtomString const&) 10 0x14db019e4 WebCore::ClassCollection::create(WebCore::ContainerNode&, WebCore::CollectionType, WTF::AtomString const&) 11 0x14db24d38 WTF::Ref<WebCore::ClassCollection, WTF::RawPtrTraits<WebCore::ClassCollection>, WTF::RefDerefTraits> WebCore::NodeListsNodeData::addCachedCollection<WebCore::ClassCollection, WebCore::ContainerNode>(WebCore::ContainerNode&, WebCore::CollectionType, WTF::AtomString const&) 12 0x14db24c54 WebCore::ContainerNode::getElementsByClassName(WTF::AtomString const&) 13 0x14ae4bd94 WebCore::jsDocumentPrototypeFunction_getElementsByClassNameBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSDocument*) 14 0x14ae4bb3c long long WebCore::IDLOperation<WebCore::JSDocument>::call<&WebCore::jsDocumentPrototypeFunction_getElementsByClassNameBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSDocument*), (WebCore::CastedThisErrorBehavior)0>(JSC::JSGlobalObject&, JSC::CallFrame&, char const*) 15 0x14ae49e90 WebCore::jsDocumentPrototypeFunction_getElementsByClassName(JSC::JSGlobalObject*, JSC::CallFrame*) [snip] - null JSC frames 23 0x1357f98f8 JSC::Interpreter::executeCallImpl(JSC::VM&, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 24 0x1357f99ec JSC::Interpreter::executeCall(JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 25 0x135afc560 JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 26 0x135c8973c JSC::boundThisNoArgsFunctionCall(JSC::JSGlobalObject*, JSC::CallFrame*) [snip] - null JSC frames 54 0x1357f98f8 JSC::Interpreter::executeCallImpl(JSC::VM&, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 55 0x1357f99ec JSC::Interpreter::executeCall(JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 56 0x135afc560 JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 57 0x135afc61c JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 58 0x135afc944 JSC::profiledCall(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 59 0x14d2c5074 WebCore::JSExecState::profiledCall(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 60 0x14d2e3d9c WebCore::JSEventListener::handleEvent(WebCore::ScriptExecutionContext&, WebCore::Event&) 61 0x14dcc9fc4 WebCore::EventTarget::innerInvokeEventListeners(WebCore::Event&, WTF::Vector<WTF::RefPtr<WebCore::RegisteredEventListener, WTF::RawPtrTraits<WebCore::RegisteredEventListener>, WTF::RefDerefTraits>, 1ul, WTF::CrashOnOverflow, 2ul, WTF::FastMalloc>, WebCore::EventTarget::EventInvokePhase) 62 0x14dcbe4b4 WebCore::EventTarget::fireEventListeners(WebCore::Event&, WebCore::EventTarget::EventInvokePhase) 63 0x14dcbe288 WebCore::EventContext::handleLocalEvents(WebCore::Event&, WebCore::EventTarget::EventInvokePhase) const 64 0x14dcbf458 WebCore::dispatchEventInDOM(WebCore::Event&, WebCore::EventPath const&) 65 0x14dcbebcc WebCore::EventDispatcher::dispatchEvent(WebCore::Node&, WebCore::Event&) 66 0x14dd60af0 WebCore::Node::dispatchEvent(WebCore::Event&) 67 0x14e138330 WebCore::HTMLMediaElement::dispatchEvent(WebCore::Event&) 68 0x14daf8368 WebCore::ActiveDOMObject::queueCancellableTaskToDispatchEventInternal(WebCore::EventTarget&, WebCore::TaskSource, WTF::TaskCancellationGroup&, WTF::Ref<WebCore::Event, WTF::RawPtrTraits<WebCore::Event>, WTF::RefDerefTraits>&&)::$_5::operator()() const 69 0x14daf8254 WTF::Detail::CallableWrapper<WebCore::ActiveDOMObject::queueCancellableTaskToDispatchEventInternal(WebCore::EventTarget&, WebCore::TaskSource, WTF::TaskCancellationGroup&, WTF::Ref<WebCore::Event, WTF::RawPtrTraits<WebCore::Event>, WTF::RefDerefTraits>&&)::$_5, void>::call() 70 0x14c692564 WTF::Function<void ()>::operator()() const 71 0x14d262d48 WTF::CancellableTask::operator()() 72 0x14d262b64 WTF::Detail::CallableWrapper<WTF::CancellableTask, void>::call() 73 0x14c692564 WTF::Function<void ()>::operator()() const 74 0x14daf3e04 WebCore::ActiveDOMObjectEventDispatchTask::execute() 75 0x14dcc1e10 WebCore::EventLoop::run() 76 0x14de4b58c WebCore::WindowEventLoop::didReachTimeToRun() 77 0x14de4f4c4 decltype(*std::declval<WebCore::WindowEventLoop*&>().*std::declval<void (WebCore::WindowEventLoop::*&)()>()()) std::__1::__invoke[abi:v160000]<void (WebCore::WindowEventLoop::*&)(), WebCore::WindowEventLoop*&, void>(void (WebCore::WindowEventLoop::*&)(), WebCore::WindowEventLoop*&) 78 0x14de4f40c std::__1::__bind_return<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, std::__1::tuple<>, __is_valid_bind_return<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, std::__1::tuple<>>::value>::type std::__1::__apply_functor[abi:v160000]<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, 0ul, std::__1::tuple<>>(void (WebCore::WindowEventLoop::*&)(), std::__1::tuple<WebCore::WindowEventLoop*>&, std::__1::__tuple_indices<0ul>, std::__1::tuple<>&&) 79 0x14de4f3c0 std::__1::__bind_return<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, std::__1::tuple<>, __is_valid_bind_return<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, std::__1::tuple<>>::value>::type std::__1::__bind<void (WebCore::WindowEventLoop::*&)(), WebCore::WindowEventLoop*>::operator()[abi:v160000]<>() 80 0x14de4f35c WTF::Detail::CallableWrapper<std::__1::__bind<void (WebCore::WindowEventLoop::*&)(), WebCore::WindowEventLoop*>, void>::call() 81 0x14c692564 WTF::Function<void ()>::operator()() const 82 0x14d313ee0 WebCore::Timer::fired() 83 0x14edd93fc WebCore::ThreadTimers::sharedTimerFiredInternal() 84 0x14ede2158 WebCore::ThreadTimers::setSharedTimer(WebCore::SharedTimer*)::$_0::operator()() const 85 0x14ede20fc WTF::Detail::CallableWrapper<WebCore::ThreadTimers::setSharedTimer(WebCore::SharedTimer*)::$_0, void>::call() 86 0x14c692564 WTF::Function<void ()>::operator()() const 87 0x14ed7a48c WebCore::MainThreadSharedTimer::fired() 88 0x14ee76db8 WebCore::timerFired(__CFRunLoopTimer*, void*) [snip] (rAF callback stack) RefTracker: Backtrace for token 15876 (https://www.youtube.com/watch?v=REDACTED) 1 0x13400c37c WTF::RefTracker::trackRef(WTF::String const&) 2 0x14db51198 WebCore::Document::trackRef() 3 0x14db150d8 void WTF::RefTrackingTraits::ref<WebCore::ContainerNode>(WebCore::ContainerNode&) 4 0x14db15068 WTF::Ref<WebCore::ContainerNode, WTF::RawPtrTraits<WebCore::ContainerNode>, WTF::RefDerefTraits>::Ref(WebCore::ContainerNode&) 5 0x14daff6d4 WTF::Ref<WebCore::ContainerNode, WTF::RawPtrTraits<WebCore::ContainerNode>, WTF::RefDerefTraits>::Ref(WebCore::ContainerNode&) 6 0x14e0a5ce0 WebCore::HTMLCollection::HTMLCollection(WebCore::ContainerNode&, WebCore::CollectionType) 7 0x14daf0f60 WebCore::CachedHTMLCollection<WebCore::AllDescendantsCollection, (WebCore::CollectionTraversalType)0>::CachedHTMLCollection(WebCore::ContainerNode&, WebCore::CollectionType) 8 0x14daf0f00 WebCore::AllDescendantsCollection::AllDescendantsCollection(WebCore::ContainerNode&, WebCore::CollectionType) 9 0x14e07064c WebCore::HTMLAllCollection::HTMLAllCollection(WebCore::Document&, WebCore::CollectionType) 10 0x14e0646bc WebCore::HTMLAllCollection::HTMLAllCollection(WebCore::Document&, WebCore::CollectionType) 11 0x14e064674 WebCore::HTMLAllCollection::create(WebCore::Document&, WebCore::CollectionType) 12 0x14dbaa4bc WTF::Ref<WebCore::HTMLAllCollection, WTF::RawPtrTraits<WebCore::HTMLAllCollection>, WTF::RefDerefTraits> WebCore::NodeListsNodeData::addCachedCollection<WebCore::HTMLAllCollection, WebCore::Document>(WebCore::Document&, WebCore::CollectionType) 13 0x14dbaa3d0 WebCore::Document::all() 14 0x14aebd7f0 WebCore::jsDocument_allGetter(JSC::JSGlobalObject&, WebCore::JSDocument&) 15 0x14addc534 long long WebCore::IDLAttribute<WebCore::JSDocument>::get<&WebCore::jsDocument_allGetter(JSC::JSGlobalObject&, WebCore::JSDocument&), (WebCore::CastedThisErrorBehavior)3>(JSC::JSGlobalObject&, long long, JSC::PropertyName) 16 0x14addc400 WebCore::jsDocument_all(JSC::JSGlobalObject*, long long, JSC::PropertyName) 17 0x135ca598c WTF::FunctionPtr<(WTF::PtrTag)28802, long long (JSC::JSGlobalObject*, long long, JSC::PropertyName), (WTF::FunctionAttributes)1>::operator()(JSC::JSGlobalObject*, long long, JSC::PropertyName) const 18 0x135f16b28 JSC::PropertySlot::customGetter(JSC::VM&, JSC::PropertyName) const 19 0x13462ec88 JSC::PropertySlot::getValue(JSC::JSGlobalObject*, JSC::PropertyName) const 20 0x135c6f730 JSC::JSValue::get(JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) const 21 0x1359d7544 JSC::LLInt::performLLIntGetByID(JSC::BytecodeIndex, JSC::CodeBlock*, JSC::JSGlobalObject*, JSC::JSValue, JSC::Identifier const&, JSC::GetByIdModeMetadata&) 22 0x1359d7268 llint_slow_path_get_by_id 23 0x13465d8e8 llint_function_for_construct_arity_checkTagGateAfter [snip] - null JSC frames 45 0x1357f98f8 JSC::Interpreter::executeCallImpl(JSC::VM&, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 46 0x1357f99ec JSC::Interpreter::executeCall(JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 47 0x135afc560 JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 48 0x135afc61c JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 49 0x14d2c8488 WebCore::JSExecState::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 50 0x14d2c81d8 WebCore::JSCustomElementInterface::invokeCallback(WebCore::Element&, JSC::JSObject*, WTF::Function<void (JSC::JSGlobalObject*, WebCore::JSDOMGlobalObject*, JSC::MarkedVector<JSC::JSValue, 8ul, WTF::RecordOverflow>&)> const&) 51 0x14d2c8a0c WebCore::JSCustomElementInterface::invokeAttributeChangedCallback(WebCore::Element&, WebCore::QualifiedName const&, WTF::AtomString const&, WTF::AtomString const&) 52 0x14db401f8 WebCore::CustomElementReactionQueueItem::invoke(WebCore::Element&, WebCore::JSCustomElementInterface&) 53 0x14db3fdc4 WebCore::CustomElementReactionQueue::invokeAll(WebCore::Element&) 54 0x14db4eb94 WebCore::CustomElementQueue::invokeAll() 55 0x14db40a0c WebCore::CustomElementQueue::processQueue(JSC::JSGlobalObject*) 56 0x14db408e8 WebCore::CustomElementReactionStack::processQueue(JSC::JSGlobalObject*) 57 0x14a9fa104 WebCore::CustomElementReactionStack::~CustomElementReactionStack() 58 0x14acda690 WebCore::CustomElementReactionStack::~CustomElementReactionStack() 59 0x14af61ccc WebCore::jsElementPrototypeFunction_removeAttributeBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSElement*) 60 0x14af61a38 long long WebCore::IDLOperation<WebCore::JSElement>::call<&WebCore::jsElementPrototypeFunction_removeAttributeBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSElement*), (WebCore::CastedThisErrorBehavior)0>(JSC::JSGlobalObject&, JSC::CallFrame&, char const*) 61 0x14af5e758 WebCore::jsElementPrototypeFunction_removeAttribute(JSC::JSGlobalObject*, JSC::CallFrame*) [snip] - null JSC frames 69 0x1357f98f8 JSC::Interpreter::executeCallImpl(JSC::VM&, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 70 0x1357f99ec JSC::Interpreter::executeCall(JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 71 0x135afc560 JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 72 0x135c8973c JSC::boundThisNoArgsFunctionCall(JSC::JSGlobalObject*, JSC::CallFrame*) [snip] - null JSC frames 76 0x1357f98f8 JSC::Interpreter::executeCallImpl(JSC::VM&, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 77 0x1357f99ec JSC::Interpreter::executeCall(JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 78 0x135afc560 JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 79 0x135afc61c JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 80 0x135afc944 JSC::profiledCall(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 81 0x14d2c5074 WebCore::JSExecState::profiledCall(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 82 0x14d2c4c04 WebCore::JSCallbackData::invokeCallback(WebCore::JSDOMGlobalObject&, JSC::JSObject*, JSC::JSValue, JSC::MarkedVector<JSC::JSValue, 8ul, WTF::RecordOverflow>&, WebCore::JSCallbackData::CallbackType, JSC::PropertyName, WTF::NakedPtr<JSC::Exception>&) 83 0x14ab3ab3c WebCore::JSCallbackDataStrong::invokeCallback(JSC::JSValue, JSC::MarkedVector<JSC::JSValue, 8ul, WTF::RecordOverflow>&, WebCore::JSCallbackData::CallbackType, JSC::PropertyName, WTF::NakedPtr<JSC::Exception>&) 84 0x14bad90bc WebCore::JSRequestAnimationFrameCallback::handleEvent(double) 85 0x14ddea3ac WebCore::ScriptedAnimationController::serviceRequestAnimationFrameCallbacks(WTF::Seconds) 86 0x14dbaddd0 WebCore::Document::serviceRequestAnimationFrameCallbacks() 87 0x14eba4df0 WebCore::Page::updateRendering()::$_28::operator()(WebCore::Document&) const 88 0x14eba4d94 WTF::Detail::CallableWrapper<WebCore::Page::updateRendering()::$_28, void, WebCore::Document&>::call(WebCore::Document&) 89 0x14ddaf6c4 WTF::Function<void (WebCore::Document&)>::operator()(WebCore::Document&) const 90 0x14eb64540 WebCore::Page::forEachDocumentFromMainFrame(WebCore::LocalFrame const&, WTF::Function<void (WebCore::Document&)> const&) 91 0x14eb527cc WebCore::Page::forEachDocument(WTF::Function<void (WebCore::Document&)> const&) const 92 0x14eb5b064 WebCore::Page::updateRendering()::$_21::operator()(WebCore::RenderingUpdateStep, WTF::Function<void (WebCore::Document&)> const&) const 93 0x14eb5ac98 WebCore::Page::updateRendering() 94 0x11858fc0c WebKit::WebPage::updateRendering() 95 0x1162fbc88 WebKit::RemoteLayerTreeDrawingArea::updateRendering() 96 0x116306524 decltype(*std::declval<WebKit::RemoteLayerTreeDrawingArea*&>().*std::declval<void (WebKit::RemoteLayerTreeDrawingArea::*&)()>()()) std::__1::__invoke[abi:v160000]<void (WebKit::RemoteLayerTreeDrawingArea::*&)(), WebKit::RemoteLayerTreeDrawingArea*&, void>(void (WebKit::RemoteLayerTreeDrawingArea::*&)(), WebKit::RemoteLayerTreeDrawingArea*&) 97 0x11630646c std::__1::__bind_return<void (WebKit::RemoteLayerTreeDrawingArea::*)(), std::__1::tuple<WebKit::RemoteLayerTreeDrawingArea*>, std::__1::tuple<>, __is_valid_bind_return<void (WebKit::RemoteLayerTreeDrawingArea::*)(), std::__1::tuple<WebKit::RemoteLayerTreeDrawingArea*>, std::__1::tuple<>>::value>::type std::__1::__apply_functor[abi:v160000]<void (WebKit::RemoteLayerTreeDrawingArea::*)(), std::__1::tuple<WebKit::RemoteLayerTreeDrawingArea*>, 0ul, std::__1::tuple<>>(void (WebKit::RemoteLayerTreeDrawingArea::*&)(), std::__1::tuple<WebKit::RemoteLayerTreeDrawingArea*>&, std::__1::__tuple_indices<0ul>, std::__1::tuple<>&&) 98 0x116306420 std::__1::__bind_return<void (WebKit::RemoteLayerTreeDrawingArea::*)(), std::__1::tuple<WebKit::RemoteLayerTreeDrawingArea*>, std::__1::tuple<>, __is_valid_bind_return<void (WebKit::RemoteLayerTreeDrawingArea::*)(), std::__1::tuple<WebKit::RemoteLayerTreeDrawingArea*>, std::__1::tuple<>>::value>::type std::__1::__bind<void (WebKit::RemoteLayerTreeDrawingArea::*&)(), WebKit::RemoteLayerTreeDrawingArea*>::operator()[abi:v160000]<>() 99 0x1163063bc WTF::Detail::CallableWrapper<std::__1::__bind<void (WebKit::RemoteLayerTreeDrawingArea::*&)(), WebKit::RemoteLayerTreeDrawingArea*>, void>::call() 100 0x115b51c98 WTF::Function<void ()>::operator()() const 101 0x1163014b4 WebCore::Timer::fired() 102 0x14edd93fc WebCore::ThreadTimers::sharedTimerFiredInternal() 103 0x14ede2158 WebCore::ThreadTimers::setSharedTimer(WebCore::SharedTimer*)::$_0::operator()() const 104 0x14ede20fc WTF::Detail::CallableWrapper<WebCore::ThreadTimers::setSharedTimer(WebCore::SharedTimer*)::$_0, void>::call() 105 0x14c692564 WTF::Function<void ()>::operator()() const 106 0x14ed7a48c WebCore::MainThreadSharedTimer::fired() 107 0x14ee76db8 WebCore::timerFired(__CFRunLoopTimer*, void*) [snip]
Ryan Reno
Comment 4 2023-04-25 14:12:51 PDT
Simon suggested I use the GCHeapInspector tool to investigate further. That tool found a Function object as being the shortest path to the leaked HTMLDocument. In particular, the function "queryHandler" is marked (GC root - DOMGCOutput). This object appears to be part of a custom element "<iron-media-query>" from the desktop_polymer.js resource delivered by the YouTube page. This matches something I noticed in both the RefPtr instrumentation and when putting a breakpoint in the DOMGCOutputConstraint::executeImplImpl function - CustomElementReactionStack is involved in the stack trace of both the un-derefed instrumentation and in the breakpoint in the constrain. There were many hits on the constraint - mostly by GC threads - but the CustomElementReactionStack was an interesting hit from the main thread. In fact the only other time that breakpoint got hit was in a stack containing the HTMLDocumentParser.
Ryan Reno
Comment 5 2023-04-25 14:14:41 PDT
> In fact the only other time that breakpoint got hit was in a stack containing > the HTMLDocumentParser. **the only other time that breakpoint got hit _from the main thread_ was in a stack containing the HTMLDocumentParser.
Ryan Reno
Comment 6 2023-04-26 15:10:00 PDT
The iron-media-query custom element seems to essentially be a wrapper over a MediaQueryList event listener. I added some instrumentation to EventListener RefPtrs and indeed they are left over after navigating away from a YouTube video. This isn't all that surprising given that the whole document is retained. I put breakpoints in MediaQueryList::addListener and MediaQueryList::removeListener and they are (in hindsight unsurprisingly) called via custom element bindings. What is interesting is it seems like there are unpaired calls to add and remove - that is we are not calling removeListener as many times and we call addListener. So I added a global int32_t that checks balance by incrementing and decrementing in the appropriate functions. I found that after navigating away from the YouTube page and issuing the lowMemory notification the counter was at +10. Since JSCEventListeners keep their function objects alive via marking when visited there's some strong evidence this imbalance in MediaQueryList addListener/removeListener is the source of the leaked document.
Ryan Reno
Comment 7 2023-04-30 20:16:37 PDT
Ok it's not event listeners on the MediaQueryList. I added a call to removeAllEventListeners when the MediaQueryList is detached from the MediaQueryMatcher and the leak persisted. I re-ran the reproducing steps with Ref Tracking again and found some underef-ed source buffers originating from calls to MedaiSource.addSourceBuffer in JS. Looking at the GCHeapInspector there are three MediaSource objects alive, one of which has the YouTube URL. I clicked "Show all paths" (of which there are over 16k). The four roots tied for shortest path (36) are all JS Function objects which are held by Strong<> handles. These four functions are also roots of the leaked document (only 11 steps away)! I'm not sure if it's a coincidence or not but MediaSource and the leaked HTMLDocument happen to have the exact same number of paths from all roots to them - 16,626 I'm going to go looking for Strong<> handles that don't belong or should have otherwise been released. I wonder if some of the concepts from RefTracking can be applied to those handles without blowing anything up. Getting tattle tale stack traces would be helpful.
Ryan Reno
Comment 8 2023-05-01 12:59:15 PDT
I added ref tracking to Strong which went surprisingly well. I guess the code paths YouTube exercised did not depend on the sizeof(Strong<T>) being the sizeof a JSCell. See below the stack trace which shows that strong refs to MediaSession action handler callbacks are being created by addActionHandler. If I naively remove all action handlers when the MediaSession is stopped then the document is no longer leaked after the navigation + lowMemory warning. The right thing to do here might be to convert the callbacks to being held as weak references and then visiting them so long as the MediaSession is not stopped. There might be some more nuance here w.r.t. the domain (like do we need to also destroy the media session? can it be recreated with the callbacks later?) but that GC strategy is probably generally the right approach. RefTracker: Backtrace for token 88501 () 1 0x138861a1c WTF::RefTracker::trackRef(WTF::String const&) 2 0x2806d0820 JSC::Strong<JSC::JSObject, (JSC::ShouldStrongDestructorGrabLock)0>::Strong(JSC::VM&, JSC::JSObject*) 3 0x2806d0460 JSC::Strong<JSC::JSObject, (JSC::ShouldStrongDestructorGrabLock)0>::Strong(JSC::VM&, JSC::JSObject*) 4 0x2806d041c WebCore::JSCallbackDataStrong::JSCallbackDataStrong(JSC::JSObject*, WebCore::JSDOMGlobalObject*, void*) 5 0x280670948 WebCore::JSCallbackDataStrong::JSCallbackDataStrong(JSC::JSObject*, WebCore::JSDOMGlobalObject*, void*) 6 0x28142112c WebCore::JSMediaSessionActionHandler::JSMediaSessionActionHandler(JSC::JSObject*, WebCore::JSDOMGlobalObject*) 7 0x2814211d8 WebCore::JSMediaSessionActionHandler::JSMediaSessionActionHandler(JSC::JSObject*, WebCore::JSDOMGlobalObject*) 8 0x28149e4dc WebCore::JSMediaSessionActionHandler::create(JSC::JSObject*, WebCore::JSDOMGlobalObject*) 9 0x28149e3fc WTF::RefPtr<WebCore::JSMediaSessionActionHandler, WTF::RawPtrTraits<WebCore::JSMediaSessionActionHandler>, WTF::RefDerefTraits> WebCore::Converter<WebCore::IDLCallbackFunction<WebCore::JSMediaSessionActionHandler>>::convert<WebCore::jsMediaSessionPrototypeFunction_setActionHandlerBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMediaSession*)::'lambda0'(JSC::JSGlobalObject&, JSC::ThrowScope&)>(JSC::JSGlobalObject&, JSC::JSValue, WebCore::JSDOMGlobalObject&, WebCore::jsMediaSessionPrototypeFunction_setActionHandlerBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMediaSession*)::'lambda0'(JSC::JSGlobalObject&, JSC::ThrowScope&)&&) 10 0x28149e2dc WTF::RefPtr<WebCore::JSMediaSessionActionHandler, WTF::RawPtrTraits<WebCore::JSMediaSessionActionHandler>, WTF::RefDerefTraits> WebCore::Converter<WebCore::IDLNullable<WebCore::IDLCallbackFunction<WebCore::JSMediaSessionActionHandler>>>::convert<WebCore::jsMediaSessionPrototypeFunction_setActionHandlerBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMediaSession*)::'lambda0'(JSC::JSGlobalObject&, JSC::ThrowScope&)>(JSC::JSGlobalObject&, JSC::JSValue, WebCore::JSDOMGlobalObject&, WebCore::jsMediaSessionPrototypeFunction_setActionHandlerBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMediaSession*)::'lambda0'(JSC::JSGlobalObject&, JSC::ThrowScope&)&&) 11 0x28149df18 WebCore::Converter<WebCore::IDLNullable<WebCore::IDLCallbackFunction<WebCore::JSMediaSessionActionHandler>>>::ReturnType WebCore::convert<WebCore::IDLNullable<WebCore::IDLCallbackFunction<WebCore::JSMediaSessionActionHandler>>, WebCore::jsMediaSessionPrototypeFunction_setActionHandlerBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMediaSession*)::'lambda0'(JSC::JSGlobalObject&, JSC::ThrowScope&)>(JSC::JSGlobalObject&, JSC::JSValue, WebCore::JSDOMGlobalObject&, WebCore::jsMediaSessionPrototypeFunction_setActionHandlerBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMediaSession*)::'lambda0'(JSC::JSGlobalObject&, JSC::ThrowScope&)&&) 12 0x28149dcd4 WebCore::jsMediaSessionPrototypeFunction_setActionHandlerBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMediaSession*) 13 0x28149da58 long long WebCore::IDLOperation<WebCore::JSMediaSession>::call<&WebCore::jsMediaSessionPrototypeFunction_setActionHandlerBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMediaSession*), (WebCore::CastedThisErrorBehavior)0>(JSC::JSGlobalObject&, JSC::CallFrame&, char const*) 14 0x28149d788 WebCore::jsMediaSessionPrototypeFunction_setActionHandler(JSC::JSGlobalObject*, JSC::CallFrame*) [snip] 20 0x13a057f98 JSC::Interpreter::executeCallImpl(JSC::VM&, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 21 0x13a05808c JSC::Interpreter::executeCall(JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 22 0x13a35ae70 JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 23 0x13a4e4b38 JSC::boundThisNoArgsFunctionCall(JSC::JSGlobalObject*, JSC::CallFrame*) [snip] 43 0x13a057f98 JSC::Interpreter::executeCallImpl(JSC::VM&, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 44 0x13a05808c JSC::Interpreter::executeCall(JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 45 0x13a35ae70 JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) 46 0x13a35af2c JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 47 0x13a35b254 JSC::profiledCall(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 48 0x282ff7624 WebCore::JSExecState::profiledCall(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) 49 0x283015b2c WebCore::JSEventListener::handleEvent(WebCore::ScriptExecutionContext&, WebCore::Event&) 50 0x2839fa348 WebCore::EventTarget::innerInvokeEventListeners(WebCore::Event&, WTF::Vector<WTF::RefPtr<WebCore::RegisteredEventListener, WTF::RawPtrTraits<WebCore::RegisteredEventListener>, WTF::RefDerefTraits>, 1ul, WTF::CrashOnOverflow, 2ul, WTF::FastMalloc>, WebCore::EventTarget::EventInvokePhase) 51 0x2839ee828 WebCore::EventTarget::fireEventListeners(WebCore::Event&, WebCore::EventTarget::EventInvokePhase) 52 0x2839ee5fc WebCore::EventContext::handleLocalEvents(WebCore::Event&, WebCore::EventTarget::EventInvokePhase) const 53 0x2839ef7cc WebCore::dispatchEventInDOM(WebCore::Event&, WebCore::EventPath const&) 54 0x2839eef40 WebCore::EventDispatcher::dispatchEvent(WebCore::Node&, WebCore::Event&) 55 0x283a90cdc WebCore::Node::dispatchEvent(WebCore::Event&) 56 0x283e686ec WebCore::HTMLMediaElement::dispatchEvent(WebCore::Event&) 57 0x2838287d8 WebCore::ActiveDOMObject::queueCancellableTaskToDispatchEventInternal(WebCore::EventTarget&, WebCore::TaskSource, WTF::TaskCancellationGroup&, WTF::Ref<WebCore::Event, WTF::RawPtrTraits<WebCore::Event>, WTF::RefDerefTraits>&&)::$_5::operator()() const 58 0x2838286c4 WTF::Detail::CallableWrapper<WebCore::ActiveDOMObject::queueCancellableTaskToDispatchEventInternal(WebCore::EventTarget&, WebCore::TaskSource, WTF::TaskCancellationGroup&, WTF::Ref<WebCore::Event, WTF::RawPtrTraits<WebCore::Event>, WTF::RefDerefTraits>&&)::$_5, void>::call() 59 0x283046bfc WTF::Function<void ()>::operator()() const 60 0x282f95194 WTF::CancellableTask::operator()() 61 0x282f94fb0 WTF::Detail::CallableWrapper<WTF::CancellableTask, void>::call() 62 0x283046bfc WTF::Function<void ()>::operator()() const 63 0x2838241dc WebCore::ActiveDOMObjectEventDispatchTask::execute() 64 0x2839f2184 WebCore::EventLoop::run() 65 0x283b7b718 WebCore::WindowEventLoop::didReachTimeToRun() 66 0x283b7f650 decltype(*std::declval<WebCore::WindowEventLoop*&>().*std::declval<void (WebCore::WindowEventLoop::*&)()>()()) std::__1::__invoke[abi:v160000]<void (WebCore::WindowEventLoop::*&)(), WebCore::WindowEventLoop*&, void>(void (WebCore::WindowEventLoop::*&)(), WebCore::WindowEventLoop*&) 67 0x283b7f598 std::__1::__bind_return<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, std::__1::tuple<>, __is_valid_bind_return<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, std::__1::tuple<>>::value>::type std::__1::__apply_functor[abi:v160000]<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, 0ul, std::__1::tuple<>>(void (WebCore::WindowEventLoop::*&)(), std::__1::tuple<WebCore::WindowEventLoop*>&, std::__1::__tuple_indices<0ul>, std::__1::tuple<>&&) 68 0x283b7f54c std::__1::__bind_return<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, std::__1::tuple<>, __is_valid_bind_return<void (WebCore::WindowEventLoop::*)(), std::__1::tuple<WebCore::WindowEventLoop*>, std::__1::tuple<>>::value>::type std::__1::__bind<void (WebCore::WindowEventLoop::*&)(), WebCore::WindowEventLoop*>::operator()[abi:v160000]<>() 69 0x283b7f4e8 WTF::Detail::CallableWrapper<std::__1::__bind<void (WebCore::WindowEventLoop::*&)(), WebCore::WindowEventLoop*>, void>::call() 70 0x283046bfc WTF::Function<void ()>::operator()() const 71 0x280190d04 WebCore::Timer::fired() 72 0x284b09b60 WebCore::ThreadTimers::sharedTimerFiredInternal() 73 0x284b128bc WebCore::ThreadTimers::setSharedTimer(WebCore::SharedTimer*)::$_0::operator()() const 74 0x284b12860 WTF::Detail::CallableWrapper<WebCore::ThreadTimers::setSharedTimer(WebCore::SharedTimer*)::$_0, void>::call() 75 0x283046bfc WTF::Function<void ()>::operator()() const 76 0x284aaabb4 WebCore::MainThreadSharedTimer::fired() 77 0x284ba7520 WebCore::timerFired(__CFRunLoopTimer*, void*) [snip]
Ryan Reno
Comment 9 2023-05-01 13:00:03 PDT
Strong refs created by setActionHandler not addActionHandler
Ryan Reno
Comment 10 2023-05-01 15:06:21 PDT
Ryan Reno
Comment 11 2023-05-01 15:08:42 PDT
That above pull request isn't quite it but I want to get some EWS feedback. Still need to figure out how to write a test for this Still need to figure out why referencingNodeCount is still non-zero. The count is down to less than 10 where it was on the order of 10,000 before.
Ryan Reno
Comment 12 2023-05-02 16:33:38 PDT
So there's just 1 node left referencing the document after changing the MediaSessionActionHandler callback to a weak reference. The document does not have any children but one last node is alive which incremented its referencingNodeCount variable. I added ref tracking to Node ctor and dtors and got a backtrace for an HTMLCanvasElement: RefTracker: Backtrace for token 509 (document 0x1111a0000) (node 0x11502b810) 1 0x1360da754 WTF::RefTracker::trackDocRef(void*, void*) 2 0x283a8cc58 WebCore::Node::Node(WebCore::Document&, WTF::OptionSet<WebCore::Node::NodeFlag>) 3 0x2838b3610 WebCore::ContainerNode::ContainerNode(WebCore::Document&, WTF::OptionSet<WebCore::Node::NodeFlag>) 4 0x2839a1238 WebCore::Element::Element(WebCore::QualifiedName const&, WebCore::Document&, WTF::OptionSet<WebCore::Node::NodeFlag>) 5 0x280648478 WebCore::StyledElement::StyledElement(WebCore::QualifiedName const&, WebCore::Document&, WTF::OptionSet<WebCore::Node::NodeFlag>) 6 0x280648104 WebCore::HTMLElement::HTMLElement(WebCore::QualifiedName const&, WebCore::Document&, WTF::OptionSet<WebCore::Node::NodeFlag>) 7 0x283dd61fc WebCore::HTMLCanvasElement::HTMLCanvasElement(WebCore::QualifiedName const&, WebCore::Document&) 8 0x283dd6380 WebCore::HTMLCanvasElement::HTMLCanvasElement(WebCore::QualifiedName const&, WebCore::Document&) 9 0x283dd648c WebCore::HTMLCanvasElement::create(WebCore::QualifiedName const&, WebCore::Document&) 10 0x280631890 WebCore::canvasConstructor(WebCore::QualifiedName const&, WebCore::Document&, WebCore::HTMLFormElement*, bool) 11 0x28062e9ec WebCore::HTMLElementFactory::createKnownElement(WTF::AtomString const&, WebCore::Document&, WebCore::HTMLFormElement*, bool) 12 0x2838bbd5c WebCore::ExceptionOr<WTF::Ref<WebCore::Element, WTF::RawPtrTraits<WebCore::Element>, WTF::RefDerefTraits>> WebCore::createHTMLElementWithNameValidation<WTF::AtomString>(WebCore::Document&, WTF::AtomString const&) 13 0x2838bbc20 WebCore::Document::createElementForBindings(WTF::AtomString const&) 14 0x280b6ef18 WebCore::jsDocumentPrototypeFunction_createElementBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSDocument*) 15 0x280b6ecc0 long long WebCore::IDLOperation<WebCore::JSDocument>::call<&WebCore::jsDocumentPrototypeFunction_createElementBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSDocument*), (WebCore::CastedThisErrorBehavior)0>(JSC::JSGlobalObject&, JSC::CallFrame&, char const*) 16 0x280b6cda4 WebCore::jsDocumentPrototypeFunction_createElement(JSC::JSGlobalObject*, JSC::CallFrame*) [snip] 29 0x1378dba54 JSC::Interpreter::executeProgram(JSC::SourceCode const&, JSC::JSGlobalObject*, JSC::JSObject*) 30 0x137c18eb4 JSC::evaluate(JSC::JSGlobalObject*, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&) 31 0x137c19004 JSC::profiledEvaluate(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&) 32 0x2830fc948 WebCore::JSExecState::profiledEvaluate(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&) 33 0x2830fc3d0 WebCore::ScriptController::evaluateInWorld(WebCore::ScriptSourceCode const&, WebCore::DOMWrapperWorld&) 34 0x2830fc200 WebCore::ScriptController::evaluateInWorldIgnoringException(WebCore::ScriptSourceCode const&, WebCore::DOMWrapperWorld&) 35 0x2830fcc54 WebCore::ScriptController::evaluateIgnoringException(WebCore::ScriptSourceCode const&) 36 0x283adf380 WebCore::ScriptElement::executeClassicScript(WebCore::ScriptSourceCode const&) 37 0x283adcf9c WebCore::ScriptElement::prepareScript(WTF::TextPosition const&, WebCore::ScriptElement::LegacyTypeSupport) 38 0x2840d4870 WebCore::HTMLScriptRunner::runScript(WebCore::ScriptElement&, WTF::TextPosition const&) 39 0x2840d4644 WebCore::HTMLScriptRunner::execute(WTF::Ref<WebCore::ScriptElement, WTF::RawPtrTraits<WebCore::ScriptElement>, WTF::RefDerefTraits>&&, WTF::TextPosition const&) 40 0x284096814 WebCore::HTMLDocumentParser::runScriptsForPausedTreeBuilder() 41 0x284096d20 WebCore::HTMLDocumentParser::pumpTokenizerLoop(WebCore::HTMLDocumentParser::SynchronousMode, bool, WebCore::PumpSession&) 42 0x2840960ac WebCore::HTMLDocumentParser::pumpTokenizer(WebCore::HTMLDocumentParser::SynchronousMode) 43 0x284096424 WebCore::HTMLDocumentParser::resumeParsingAfterYield() 44 0x2840c9388 WebCore::HTMLParserScheduler::continueNextChunkTimerFired() 45 0x2840d2208 decltype(*std::declval<WebCore::HTMLParserScheduler*&>().*std::declval<void (WebCore::HTMLParserScheduler::*&)()>()()) std::__1::__invoke[abi:v160000]<void (WebCore::HTMLParserScheduler::*&)(), WebCore::HTMLParserScheduler*&, void>(void (WebCore::HTMLParserScheduler::*&)(), WebCore::HTMLParserScheduler*&) 46 0x2840d2170 std::__1::__bind_return<void (WebCore::HTMLParserScheduler::*)(), std::__1::tuple<WebCore::HTMLParserScheduler*>, std::__1::tuple<>, __is_valid_bind_return<void (WebCore::HTMLParserScheduler::*)(), std::__1::tuple<WebCore::HTMLParserScheduler*>, std::__1::tuple<>>::value>::type std::__1::__apply_functor[abi:v160000]<void (WebCore::HTMLParserScheduler::*)(), std::__1::tuple<WebCore::HTMLParserScheduler*>, 0ul, std::__1::tuple<>>(void (WebCore::HTMLParserScheduler::*&)(), std::__1::tuple<WebCore::HTMLParserScheduler*>&, std::__1::__tuple_indices<0ul>, std::__1::tuple<>&&) 47 0x2840d2124 std::__1::__bind_return<void (WebCore::HTMLParserScheduler::*)(), std::__1::tuple<WebCore::HTMLParserScheduler*>, std::__1::tuple<>, __is_valid_bind_return<void (WebCore::HTMLParserScheduler::*)(), std::__1::tuple<WebCore::HTMLParserScheduler*>, std::__1::tuple<>>::value>::type std::__1::__bind<void (WebCore::HTMLParserScheduler::*&)(), WebCore::HTMLParserScheduler*>::operator()[abi:v160000]<>() 48 0x2840d20c0 WTF::Detail::CallableWrapper<std::__1::__bind<void (WebCore::HTMLParserScheduler::*&)(), WebCore::HTMLParserScheduler*>, void>::call() 49 0x2823c909c WTF::Function<void ()>::operator()() const 50 0x28304c1e8 WebCore::Timer::fired() 51 0x284b13be0 WebCore::ThreadTimers::sharedTimerFiredInternal() 52 0x284b1c93c WebCore::ThreadTimers::setSharedTimer(WebCore::SharedTimer*)::$_0::operator()() const 53 0x284b1c8e0 WTF::Detail::CallableWrapper<WebCore::ThreadTimers::setSharedTimer(WebCore::SharedTimer*)::$_0, void>::call() 54 0x2823c909c WTF::Function<void ()>::operator()() const 55 0x284ab4bf4 WebCore::MainThreadSharedTimer::fired() 56 0x284bb15a0 WebCore::timerFired(__CFRunLoopTimer*, void*) [snip] I think then that this is probably a separate issue from the MediaSessionActionHandler-caused leak so I'll open a new bug. There are also SVGDocuments left over which I'll try to investigate in another bug.
EWS
Comment 13 2023-05-03 20:09:49 PDT
Committed 263660@main (56280cdcbd8a): <https://commits.webkit.org/263660@main> Reviewed commits have been landed. Closing PR #13341 and removing active labels.
WebKit Commit Bot
Comment 14 2023-05-08 14:38:06 PDT
Re-opened since this is blocked by bug 256490
Ryan Reno
Comment 15 2023-05-08 17:39:34 PDT
It looks like the MediaSessionActionHandler callbacks are being collected before the MediaSession can mark them. Some slightly more complex lifetime management may be needed here.
Ryan Reno
Comment 16 2023-05-08 20:31:42 PDT
Oh I think I needed to override ActiveDOMObject's virtualHasPendingActivity for the MediaSession to check if it has any action handlers associated with it. That way the GC knows to actually call visitAdditionalChildren! We shouldn't leak even if action handlers have been added to the map because we also check to see if the ScriptExecutionContext has stopped in isReachableFromOpaqueRoots. So if the page is in the page cache and we're clearing it due to low memory we shouldn't leak even if we would otherwise have reported having pending activity. I'll write up a test to be sure of all of this but I think the solution is the following: 1. Convert MediaSessionActionHandler to a weak callback 2. visitAdditionalChildren from the MediaSession to mark any action handlers it has 3. override virtualHasPendingActivity on MediaSession to return true if there are any action handlers in the map. Since we'll be hitting a hash map in a loop quite often after this change and the map has a fixed size known at compile time it might be worth just using a fixed size array as a lookup table for these action handlers instead.
Ryan Reno
Comment 17 2023-05-08 22:45:10 PDT
EWS
Comment 18 2023-05-09 10:18:21 PDT
Committed 263868@main (e938617efad6): <https://commits.webkit.org/263868@main> Reviewed commits have been landed. Closing PR #13627 and removing active labels.
Note You need to log in before you can comment on or make changes to this bug.