RESOLVED FIXED Bug 123821
-dealloc callbacks from wrapped Objective-C objects can happen at bad times
https://bugs.webkit.org/show_bug.cgi?id=123821
Summary -dealloc callbacks from wrapped Objective-C objects can happen at bad times
Mark Hahnenberg
Reported 2013-11-05 13:17:53 PST
Currently with the JSC Obj-C API, JS wrappers for client Obj-C objects retain their associated Obj-C object. When they are swept, they release their Obj-C objects which can trigger a call to that object's -dealloc method. These -dealloc methods can then call back into the same VM, which is not allowed during lazy sweeping or VM shutdown. We can gracefully handle this case by creating our own pool of Obj-C objects to be released when it is safe to do so. The basic idea is: "Scoped object for ObjC dealloc calls. Holds a Vector<RetainPtr>. Destructors use std::move to put things in this vector, instead of calling release directly. Scoped object does the right callback shim thingy. lastChanceToFinalize makes sure to use the scoped object."
Attachments
Patch (23.39 KB, patch)
2013-11-14 13:50 PST, Mark Hahnenberg
no flags
Patch (25.27 KB, patch)
2013-11-14 15:07 PST, Mark Hahnenberg
no flags
Patch (25.48 KB, patch)
2013-11-14 15:24 PST, Mark Hahnenberg
no flags
Patch (25.48 KB, patch)
2013-11-14 15:33 PST, Mark Hahnenberg
darin: review+
Mark Hahnenberg
Comment 1 2013-11-05 13:18:46 PST
Mark Hahnenberg
Comment 2 2013-11-14 13:50:32 PST
EFL EWS Bot
Comment 3 2013-11-14 14:00:59 PST
Geoffrey Garen
Comment 4 2013-11-14 14:06:08 PST
Comment on attachment 216976 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=216976&action=review r=me, but I think you've got some fixup to do before landing. > Source/JavaScriptCore/API/ObjCCallbackFunction.mm:415 > [[m_invocation.get() target] release]; Do we need the same treatment for our invocation's target? What if that -dealloc did something unpleasant? > Source/JavaScriptCore/heap/DelayedReleaseScope.h:38 > + , m_savedScope(markedSpace.m_currentDelayedReleaseScope) I don't think we want to save the previous scope like this. If we did re-entrently create a DelayedReleaseScope (which I believe is impossible), it would not be correct to release those interior objects right away -- we would need to wait until we popped the outermost DelayedReleaseScope, or we would be violating its invariants. So, I guess we should ASSERT that the current scope is NULL, and then set ourselves as the current scope. > Source/JavaScriptCore/heap/DelayedReleaseScope.h:48 > + ~DelayedReleaseScope() > + { > + APICallbackShim callbackShim(*m_markedSpace.m_heap->vm()); > + m_delayedReleaseObjects.clear(); > + m_markedSpace.m_currentDelayedReleaseScope = m_savedScope; > + } Here, we need to unset the current scope right away, and not after we clear the vector. Otherwise, while the -dealloc methods run, we might accumulate new objects into m_delayedReleaseObjects, which we don't really want. > Source/JavaScriptCore/heap/Heap.h:190 > + template <typename T> > + void delayReleaseForObject(RetainPtr<T>&&); Let's call this "releaseSoon" or "releaseAfterGC".
Darin Adler
Comment 5 2013-11-14 14:14:21 PST
Comment on attachment 216976 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=216976&action=review > Source/JavaScriptCore/API/APIShims.h:108 > + return 0; nullptr > Source/JavaScriptCore/API/APIShims.h:113 > + return 0; nullptr > Source/JavaScriptCore/API/APIShims.h:119 > + if (!vmForDropAllLocks(exec->vm())) Seems a bit peculiar to do it this way. Why not just a function that returns a boolean? > Source/JavaScriptCore/API/APIShims.h:120 > + return 0; nullptr > Source/JavaScriptCore/API/JSAPIWrapperObject.mm:59 > + RetainPtr<CFTypeRef> retainedObject(wrapperObject->wrappedObject()); > + JSC::Heap::heap(wrapperObject)->delayReleaseForObject(std::move(retainedObject)); > [static_cast<id>(wrapperObject->wrappedObject()) release]; Is the local variable really needed here? Instead of calling release here, I suggest we call adoptNS above. That will be faster. JSC::Heap::heap(wrapperObject)->delayReleaseForObject(adoptNS(wrapperObject->wrappedObject())); The one line would replace all three lines of code above. > Source/JavaScriptCore/API/ObjCCallbackFunction.mm:416 > + RetainPtr<CFTypeRef> target([m_invocation.get() target]); > [[m_invocation.get() target] release]; > + heap.delayReleaseForObject(std::move(target)); Same here: heap.delayReleaseForObject(adoptNS([m_invocation.get() target])); >> Source/JavaScriptCore/heap/DelayedReleaseScope.h:48 >> + ~DelayedReleaseScope() >> + { >> + APICallbackShim callbackShim(*m_markedSpace.m_heap->vm()); >> + m_delayedReleaseObjects.clear(); >> + m_markedSpace.m_currentDelayedReleaseScope = m_savedScope; >> + } > > Here, we need to unset the current scope right away, and not after we clear the vector. Otherwise, while the -dealloc methods run, we might accumulate new objects into m_delayedReleaseObjects, which we don't really want. I’d also suggest including: ASSERT(m_markedSpace.m_currentDelayedReleaseScope == this); > Source/JavaScriptCore/heap/DelayedReleaseScope.h:53 > + m_delayedReleaseObjects.append(reinterpret_cast<RetainPtr<CFTypeRef>&&>(object)); This reinterpret_cast is not a safe approach. We need to instead find a way to make this work with move constructors in the RetainPtr class. >> Source/JavaScriptCore/heap/Heap.h:190 >> + template <typename T> >> + void delayReleaseForObject(RetainPtr<T>&&); > > Let's call this "releaseSoon" or "releaseAfterGC". I think this would read better as a single line. > Source/JavaScriptCore/heap/MarkedSpace.cpp:85 > + , m_currentDelayedReleaseScope(0) nullptr > Source/JavaScriptCore/heap/MarkedSpace.h:119 > + template <typename T> > + void delayReleaseForObject(RetainPtr<T>&&); Ditto.
Mark Hahnenberg
Comment 6 2013-11-14 14:15:44 PST
Comment on attachment 216976 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=216976&action=review >> Source/JavaScriptCore/API/ObjCCallbackFunction.mm:415 >> [[m_invocation.get() target] release]; > > Do we need the same treatment for our invocation's target? What if that -dealloc did something unpleasant? We are delaying the release for the invocation's target. Do you mean the invocation itself? I think the only objects that the NSInvocation could release would be the target and the arguments. Since we're not using -retainArguments, those won't be dealloc-ed during the invocation's dealloc.
EFL EWS Bot
Comment 7 2013-11-14 14:17:23 PST
Build Bot
Comment 8 2013-11-14 14:23:34 PST
Build Bot
Comment 9 2013-11-14 14:54:06 PST
Mark Hahnenberg
Comment 10 2013-11-14 15:07:04 PST
EFL EWS Bot
Comment 11 2013-11-14 15:14:37 PST
Mark Hahnenberg
Comment 12 2013-11-14 15:24:39 PST
EFL EWS Bot
Comment 13 2013-11-14 15:29:56 PST
Mark Hahnenberg
Comment 14 2013-11-14 15:33:27 PST
Darin Adler
Comment 15 2013-11-14 17:44:01 PST
Comment on attachment 216989 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=216989&action=review > Source/JavaScriptCore/API/APIShims.h:86 > + : m_dropAllLocks(shouldDropAllLocks(exec->vm()) ? exec : 0) nullptr > Source/JavaScriptCore/API/APIShims.h:93 > + : m_dropAllLocks(shouldDropAllLocks(vm) ? &vm : 0) nullptr > Source/JavaScriptCore/API/ObjCCallbackFunction.mm:411 > // We need to explicity release the target since we didn't call Missing an "l" in explicitly. Should probably grep for this typo. I think I’ve seen it elsewhere too. > Source/JavaScriptCore/API/ObjCCallbackFunction.mm:524 > + ObjCCallbackFunction* function = jsCast<ObjCCallbackFunction*>(cell); I’d use a reference for the local variable since it’s never null.
Mark Hahnenberg
Comment 16 2013-11-15 11:51:19 PST
Csaba Osztrogonác
Comment 17 2013-11-18 03:17:11 PST
It caused assertions on !CF platforms, see https://bugs.webkit.org/show_bug.cgi?id=124490 for details and proposed fix.
Note You need to log in before you can comment on or make changes to this bug.