Bug 236454 - Keep promise in scope when calling DeferredPromise::reject
Summary: Keep promise in scope when calling DeferredPromise::reject
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: New Bugs (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Gabriel Nava Marino
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2022-02-10 10:46 PST by Gabriel Nava Marino
Modified: 2022-02-18 12:28 PST (History)
5 users (show)

See Also:


Attachments
Patch (1.74 KB, patch)
2022-02-10 10:47 PST, Gabriel Nava Marino
no flags Details | Formatted Diff | Diff
Patch (1.75 KB, patch)
2022-02-11 09:49 PST, Gabriel Nava Marino
no flags Details | Formatted Diff | Diff
Patch (1.68 KB, patch)
2022-02-14 10:58 PST, Gabriel Nava Marino
no flags Details | Formatted Diff | Diff
Patch (1.90 KB, patch)
2022-02-14 14:10 PST, Gabriel Nava Marino
no flags Details | Formatted Diff | Diff
Patch (2.30 KB, patch)
2022-02-17 13:32 PST, Gabriel Nava Marino
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Gabriel Nava Marino 2022-02-10 10:46:12 PST
<rdar://problem/88327814>
Comment 1 Gabriel Nava Marino 2022-02-10 10:47:52 PST
Created attachment 451567 [details]
Patch
Comment 2 youenn fablet 2022-02-10 10:50:11 PST
Comment on attachment 451567 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=451567&action=review

> Source/WebCore/ChangeLog:9
> +        could go through a path that invokes GC on its owner and it.

'and it' is probably missing a word.
Comment 3 Gabriel Nava Marino 2022-02-10 14:23:48 PST
(In reply to youenn fablet from comment #2)
> Comment on attachment 451567 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=451567&action=review
> 
> > Source/WebCore/ChangeLog:9
> > +        could go through a path that invokes GC on its owner and it.
> 
> 'and it' is probably missing a word.

Here, I am referring to the promise (i.e. "Keep promise in scope as ... invokes GC on its owner and it.")
Comment 4 Gabriel Nava Marino 2022-02-11 09:49:50 PST
Created attachment 451714 [details]
Patch
Comment 5 EWS 2022-02-11 11:38:00 PST
Committed r289640 (247145@main): <https://commits.webkit.org/247145@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 451714 [details].
Comment 6 Darin Adler 2022-02-11 11:38:59 PST
Comment on attachment 451714 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=451714&action=review

> Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp:135
> +    Ref protectedThis(*this);

This is not the correct fix given our overall approach to reference counting. The burden of protecting "this" is on the caller, not the reject function. I suppose that consistently protecting the wrapped object in every DOM function might be prohibitive, but we chose that approach a while back, and I am worried about mixing approaches.
Comment 7 Gabriel Nava Marino 2022-02-14 10:58:02 PST
(In reply to Darin Adler from comment #6)
> Comment on attachment 451714 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=451714&action=review
> 
> > Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp:135
> > +    Ref protectedThis(*this);
> 
> This is not the correct fix given our overall approach to reference
> counting. The burden of protecting "this" is on the caller, not the reject
> function. I suppose that consistently protecting the wrapped object in every
> DOM function might be prohibitive, but we chose that approach a while back,
> and I am worried about mixing approaches.

Thank you for the context on the convention. I have updated the patch so that the caller protects the deferred promise.
Comment 8 Gabriel Nava Marino 2022-02-14 10:58:37 PST
Created attachment 451924 [details]
Patch
Comment 9 Darin Adler 2022-02-14 11:18:20 PST
Comment on attachment 451924 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=451924&action=review

> Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp:274
> +            Ref protectedPromise { promise };

Seems this doesn’t make sense. The "promise" captured is already protected, so this wouldn’t have any effect.
Comment 10 Gabriel Nava Marino 2022-02-14 11:26:34 PST
(In reply to Darin Adler from comment #9)
> Comment on attachment 451924 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=451924&action=review
> 
> > Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp:274
> > +            Ref protectedPromise { promise };
> 
> Seems this doesn’t make sense. The "promise" captured is already protected,
> so this wouldn’t have any effect.

The lambda owns the promise, but during execution, the lambda and its owner hierarchy (i.e. FetchBodyConsumer, FormDataConsumer, ...) are deallocated during garbage collection while calling DeferredPromise::reject.

This can be see in the backtrace attached to the radar.
Comment 11 Darin Adler 2022-02-14 11:30:35 PST
Comment on attachment 451924 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=451924&action=review

>>> Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp:274
>>> +            Ref protectedPromise { promise };
>> 
>> Seems this doesn’t make sense. The "promise" captured is already protected, so this wouldn’t have any effect.
> 
> The lambda owns the promise, but during execution, the lambda and its owner hierarchy (i.e. FetchBodyConsumer, FormDataConsumer, ...) are deallocated during garbage collection while calling DeferredPromise::reject.
> 
> This can be see in the backtrace attached to the radar.

Given this maybe we should have a local variable at the top of the entire function:

    [..., capturedPromise = WTFMove(promise), ...]

    auto promise = WTFMove(capturedPromise);

This might be a good idiom for situations like this, not just this one case.
Comment 12 Gabriel Nava Marino 2022-02-14 14:09:21 PST
(In reply to Darin Adler from comment #11)
> Comment on attachment 451924 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=451924&action=review
> 
> >>> Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp:274
> >>> +            Ref protectedPromise { promise };
> >> 
> >> Seems this doesn’t make sense. The "promise" captured is already protected, so this wouldn’t have any effect.
> > 
> > The lambda owns the promise, but during execution, the lambda and its owner hierarchy (i.e. FetchBodyConsumer, FormDataConsumer, ...) are deallocated during garbage collection while calling DeferredPromise::reject.
> > 
> > This can be see in the backtrace attached to the radar.
> 
> Given this maybe we should have a local variable at the top of the entire
> function:
> 
>     [..., capturedPromise = WTFMove(promise), ...]
> 
>     auto promise = WTFMove(capturedPromise);
> 
> This might be a good idiom for situations like this, not just this one case.

Thank you for the recommendation, I will update the patch and add this idiom to my notes to add in similar situations.
Comment 13 Gabriel Nava Marino 2022-02-14 14:10:20 PST
Created attachment 451943 [details]
Patch
Comment 14 Gabriel Nava Marino 2022-02-16 12:55:31 PST
The updated patch is resulting in a crash that I am now investigating:

stderr:
ASSERTION FAILED: m_ptr
/Volumes/Data/worker/macOS-AppleSilicon-Big-Sur-Debug-Build-EWS/build/WebKitBuild/Debug/usr/local/include/wtf/Ref.h(129) : T &WTF::Ref<WebCore::DeferredPromise, WTF::RawPtrTraits<WebCore::DeferredPromise>>::leakRef() [T = WebCore::DeferredPromise, Traits = WTF::RawPtrTraits<WebCore::DeferredPromise>]
1   0x13db56258 WTFCrash
2   0x118b0a7b0 WebCore::JSDOMGlobalObject* JSC::jsCast<WebCore::JSDOMGlobalObject*, JSC::JSGlobalObject>(JSC::JSGlobalObject*)
3   0x11a3dfbe8 WTF::Ref<WebCore::DeferredPromise, WTF::RawPtrTraits<WebCore::DeferredPromise> >::leakRef()
4   0x11a3dfb18 WTF::Ref<WebCore::DeferredPromise, WTF::RawPtrTraits<WebCore::DeferredPromise> >::Ref(WTF::Ref<WebCore::DeferredPromise, WTF::RawPtrTraits<WebCore::DeferredPromise> >&&)
5   0x118e01438 WTF::Ref<WebCore::DeferredPromise, WTF::RawPtrTraits<WebCore::DeferredPromise> >::Ref(WTF::Ref<WebCore::DeferredPromise, WTF::RawPtrTraits<WebCore::DeferredPromise> >&&)
6   0x11a672844 auto WebCore::FetchBodyConsumer::resolveWithFormData(WTF::Ref<WebCore::DeferredPromise, WTF::RawPtrTraits<WebCore::DeferredPromise> >&&, WTF::String const&, WebCore::FormData const&, WebCore::ScriptExecutionContext*)::$_0::operator()<WebCore::ExceptionOr<WTF::Span<unsigned char const, 18446744073709551615ul> > >(WebCore::ExceptionOr<WTF::Span<unsigned char const, 18446744073709551615ul> >&&)
7   0x11a6726a8 WTF::Detail::CallableWrapper<WebCore::FetchBodyConsumer::resolveWithFormData(WTF::Ref<WebCore::DeferredPromise, WTF::RawPtrTraits<WebCore::DeferredPromise> >&&, WTF::String const&, WebCore::FormData const&, WebCore::ScriptExecutionContext*)::$_0, void, WebCore::ExceptionOr<WTF::Span<unsigned char const, 18446744073709551615ul> > >::call(WebCore::ExceptionOr<WTF::Span<unsigned char const, 18446744073709551615ul> >)
8   0x11a66bd6c WTF::Function<void (WebCore::ExceptionOr<WTF::Span<unsigned char const, 18446744073709551615ul> >)>::operator()(WebCore::ExceptionOr<WTF::Span<unsigned char const, 18446744073709551615ul> >) const
9   0x11a66bf9c WebCore::FormDataConsumer::consume(WTF::Span<unsigned char const, 18446744073709551615ul>)
10  0x11a6960e4 auto WebCore::FormDataConsumer::consumeFile(WTF::String const&)::$_24::operator()()::'lambda'(auto&)::operator()<WebCore::ScriptExecutionContext>(auto&) const
11  0x11a695b94 WTF::Detail::CallableWrapper<WebCore::FormDataConsumer::consumeFile(WTF::String const&)::$_24::operator()()::'lambda'(auto&), void, WebCore::ScriptExecutionContext&>::call(WebCore::ScriptExecutionContext&)
12  0x11aec45e0 WTF::Function<void (WebCore::ScriptExecutionContext&)>::operator()(WebCore::ScriptExecutionContext&) const
13  0x11aeae160 WebCore::ScriptExecutionContext::Task::performTask(WebCore::ScriptExecutionContext&)
14  0x11de8dac8 WebCore::WorkerDedicatedRunLoop::Task::performTask(WebCore::WorkerOrWorkletGlobalScope*)
15  0x11de8cfd8 WebCore::WorkerDedicatedRunLoop::runInMode(WebCore::WorkerOrWorkletGlobalScope*, WebCore::ModePredicate const&)
16  0x11de8cc44 WebCore::WorkerDedicatedRunLoop::run(WebCore::WorkerOrWorkletGlobalScope*)
17  0x11de569b0 WebCore::WorkerOrWorkletThread::runEventLoop()
18  0x11df289ac WebCore::ServiceWorkerThread::runEventLoop()
19  0x11de56d28 WebCore::WorkerOrWorkletThread::workerOrWorkletThread()
20  0x11deaca14 WebCore::WorkerThread::createThread()::$_4::operator()() const
21  0x11deac990 WTF::Detail::CallableWrapper<WebCore::WorkerThread::createThread()::$_4, void>::call()
22  0x13db81018 WTF::Function<void ()>::operator()() const
23  0x13dc5c1e8 WTF::Thread::entryPoint(WTF::Thread::NewThreadContext*)
24  0x13dc6a860 WTF::wtfThreadEntryPoint(void*)
25  0x19a2db878 _pthread_start
26  0x19a2d65e0 thread_start
LEAK: 1 WebPageProxy
Comment 15 Brady Eidson 2022-02-17 13:13:40 PST
(In reply to Gabriel Nava Marino from comment #14)
> The updated patch is resulting in a crash that I am now investigating:

I chatted with Garbiel about this on slack.

This lambda is called more than once, so it's wrong to WTFMove the promise out unconditionally - It should only be moved out when it's needed for the call to reject()
Comment 16 Gabriel Nava Marino 2022-02-17 13:32:44 PST
Created attachment 452425 [details]
Patch
Comment 17 Gabriel Nava Marino 2022-02-18 10:47:15 PST
Comment on attachment 452425 [details]
Patch

Moving to cq? as I noticed the iOS-wk2 test that failed is also having issues in at least one unrelated patch.
Comment 18 EWS 2022-02-18 12:28:38 PST
Committed r290152 (247491@main): <https://commits.webkit.org/247491@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 452425 [details].