Bug 197172 - JSC should have public API for unhandled promise rejections
Summary: JSC should have public API for unhandled promise rejections
Status: REOPENED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: All All
: P2 Normal
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2019-04-22 11:35 PDT by Michael Anthony
Modified: 2019-09-06 14:13 PDT (History)
21 users (show)

See Also:


Attachments
Patch (17.70 KB, patch)
2019-05-12 00:09 PDT, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Finish Yusuke's PoC (21.66 KB, patch)
2019-07-05 20:27 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
Patch based on microtask queue (22.13 KB, patch)
2019-07-05 22:23 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
WIP patch based on run loop (19.96 KB, patch)
2019-07-11 15:08 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
WIP patch based on run loop (24.92 KB, patch)
2019-07-14 16:07 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
Minimal PromiseDeferredTimer usage example (2.43 KB, patch)
2019-07-14 18:52 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
Patch (29.97 KB, patch)
2019-07-30 17:20 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
Patch (run loop) (32.87 KB, patch)
2019-08-09 15:05 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
Patch (microtask queue) (26.92 KB, patch)
2019-08-14 11:48 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
Patch (21.26 KB, patch)
2019-08-20 17:44 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
Patch (21.45 KB, patch)
2019-08-21 15:25 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
Patch (19.52 KB, patch)
2019-08-21 16:36 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff
Patch for landing (22.12 KB, patch)
2019-08-23 11:19 PDT, Ross Kirsling
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Michael Anthony 2019-04-22 11:35:22 PDT
Hi,

Apologies if this is in the wrong place, but I'm using JavaScriptCore in a native Swift app and I'm unable to catch errors created in an async promise stack. I believe Safari has a global window event, similar to Node's process.on('unhandledRejection'), but I have had no luck finding anything describing how to do this when creating a JSCEngine natively.
Comment 1 Radar WebKit Bug Importer 2019-04-22 16:38:13 PDT
<rdar://problem/50112559>
Comment 2 Keith Miller 2019-04-30 11:34:15 PDT
Unfortunately, I don't think there is anyway to do this right now in JSC. It probably wouldn't be too hard to add a delegate to JSContext that would be called on unhandledRejection though. I can't promise when such an API will happen, however, but it seems like a good idea to have something like that, IMO.
Comment 3 Yusuke Suzuki 2019-05-12 00:09:41 PDT
Created attachment 369669 [details]
Patch

Simple one, just works
Comment 4 Don Olmstead 2019-06-10 11:19:29 PDT
This obsolete now that https://bugs.webkit.org/show_bug.cgi?id=198709 landed?
Comment 5 Ross Kirsling 2019-06-20 15:42:25 PDT
Is this blocked by anything? Turns out we have a need for this too so I'd be happy to assist in getting it done.
Comment 6 Ross Kirsling 2019-07-05 20:27:19 PDT
Created attachment 373561 [details]
Finish Yusuke's PoC
Comment 7 Ross Kirsling 2019-07-05 22:23:39 PDT
Created attachment 373564 [details]
Patch based on microtask queue
Comment 8 Ross Kirsling 2019-07-05 22:48:58 PDT
This patch completes the other half of Yusuke's proof of concept.

This is my first time dealing with the public API, so here's hoping I've not made too many mistakes with regard to best practices. :)

In particular, Yusuke mentioned that we need to GC-mark the held promises but I don't understand concretely what is missing.
Comment 9 Keith Miller 2019-07-06 11:15:37 PDT
Comment on attachment 373564 [details]
Patch based on microtask queue

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

> Source/JavaScriptCore/ChangeLog:10
> +        This patch allows callbacks for unhandled and late-handled promise rejections to be set via JSC's public C API.
> +        Since there is no event loop in such an environment, these callbacks fire off of the microtask queue.
> +        Callbacks receive the promise and rejection reason as arguments and their return value is ignored.

To be clear, we do assume there's a run loop in the C API. We just don't have a way to configure it beyond the default of using the one associated with the thread the VM was created on. We use that run loop for GC work and for resolving Wasm compilations. 

I think we should use the same queue for this... but I could be convinced otherwise.
Comment 10 Ross Kirsling 2019-07-11 15:08:14 PDT
Created attachment 373962 [details]
WIP patch based on run loop
Comment 11 Ross Kirsling 2019-07-14 16:07:26 PDT
Created attachment 374097 [details]
WIP patch based on run loop
Comment 12 Ross Kirsling 2019-07-14 18:52:59 PDT
Created attachment 374102 [details]
Minimal PromiseDeferredTimer usage example

So if vm.promiseDeferredTimer is the correct way to schedule something on the run loop, then I think there must be something else that I'm not understanding, because I don't see how this can result in a usable API.

Here's a patch that's as minimal as I could make it. Every time you call `Promise.reject()` from the JSC REPL, it schedules some text to be printed. In order to have the printing actually occur, however, we have to make sure that the REPL calls runRunLoop after each iteration.

It's possible that this would be acceptable for the REPL case, but the actual goal here is to provide a public API for registering unhandled rejection callbacks. It seems rather ghastly if this new API is to require another API that the embedder must call every single time they want these callbacks to actually fire. But even if that were how it has to be, I'm not actually sure how to implement said other API without running into the must-NOT-be-holding-a-lock ASSERT:
https://github.com/WebKit/webkit/blob/master/Source/JavaScriptCore/runtime/PromiseDeferredTimer.cpp#L87
Comment 13 Keith Miller 2019-07-15 11:05:37 PDT
(In reply to Ross Kirsling from comment #12)
> Created attachment 374102 [details]
> Minimal PromiseDeferredTimer usage example
> 
> So if vm.promiseDeferredTimer is the correct way to schedule something on
> the run loop, then I think there must be something else that I'm not
> understanding, because I don't see how this can result in a usable API.
> 
> Here's a patch that's as minimal as I could make it. Every time you call
> `Promise.reject()` from the JSC REPL, it schedules some text to be printed.
> In order to have the printing actually occur, however, we have to make sure
> that the REPL calls runRunLoop after each iteration.

Unless I'm missing something, you don't have to call a second API. That API is just for testing purposes so that we break out of the runloop when we are done testing. Any application with a runloop will be running JSC out of a runloop anyway so they shouldn't need to call that API. We don't do so from the CLI because it's a CLI tool rather than a full app. I think we should design our APIs around use in full applications rather than run-once tools as that's probably the vast majority of use cases.

> 
> It's possible that this would be acceptable for the REPL case, but the
> actual goal here is to provide a public API for registering unhandled
> rejection callbacks. It seems rather ghastly if this new API is to require
> another API that the embedder must call every single time they want these
> callbacks to actually fire. But even if that were how it has to be, I'm not
> actually sure how to implement said other API without running into the
> must-NOT-be-holding-a-lock ASSERT:
> https://github.com/WebKit/webkit/blob/master/Source/JavaScriptCore/runtime/
> PromiseDeferredTimer.cpp#L87

Since this is a diagnostic API I don't think it's the end of the world. That said, I could see expecting this to run before my application exits, which the runloop based version makes more complicated. One option could be to check for ready promises when releasing the VM lock. We'll need to verify this behavior is valid WRT WebCore though. e.g. if wasm compilations are ok to run after micro tasks have been drained but before the rest of WebCore is consulted. There might also be other weirdness; I have to think about it more.

That assertion is there because we are going to run a runloop, which can run for an unbounded amount of time. If we are holding the API lock, it's possible that the VM will be locked indefinitely and the application could deadlock. It's also not a fundamental assertion just a this is possibly a bug if you hit it one.
Comment 14 Ross Kirsling 2019-07-15 13:24:56 PDT
(In reply to Keith Miller from comment #13)
> Unless I'm missing something, you don't have to call a second API. That API
> is just for testing purposes so that we break out of the runloop when we are
> done testing. Any application with a runloop will be running JSC out of a
> runloop anyway so they shouldn't need to call that API. We don't do so from
> the CLI because it's a CLI tool rather than a full app. I think we should
> design our APIs around use in full applications rather than run-once tools
> as that's probably the vast majority of use cases.

That makes sense; perhaps the focal point of my confusion then is just that I don't know how to make my API test valid. 😅
Comment 15 Ross Kirsling 2019-07-16 18:08:45 PDT
(In reply to Ross Kirsling from comment #12)
> But even if that were how it has to be, I'm not
> actually sure how to implement said other API without running into the
> must-NOT-be-holding-a-lock ASSERT
Hmm, apparently I was wrong about this part -- introducing a JSGlobalContextRunRunLoop call does "just work" for TestAPI.

(Actually, for Cocoa, it suffices to call RunLoop::current().runForDuration(0_s), but confusingly RunLoop::iterate() doesn't work on other platforms -- for instance, the Windows message queue just never sees any messages, regardless of whether I try blocking on the current thread or make a new one...)



(In reply to Ross Kirsling from comment #14)
> That makes sense; perhaps the focal point of my confusion then is just that
> I don't know how to make my API test valid. 😅
I was also wrong about this -- given the above, it's easy to make the test valid; making it representative of application usage is another story.

Concretely, we have a sample app for our platform that does two things:
1. create two threads, each of which executes a script and serves as an inspection target
2. start remote inspector server and (using a hacked-in downstream API) kick off the RunLoopGeneric that it requires

Using the WIP patch allows us to set an unhandled rejection callback on either of the inspection targets, but that callback will never fire.
If we add in JSGlobalContextRunRunLoop and call it on every iteration of that thread's while(true) loop, then things do seem to work.

Perhaps I was just confused by how brainlessly simple the microtask-queue-based approach is to use, and this *would* in fact be acceptable for application usage if we were to give it the name JSGlobalContextHandlePendingTasks instead?


But if not, then I surely don't know how to determine what the correct answer is; I actually still don't think I even understand what "Any application with a runloop will be running JSC out of a runloop anyway so they shouldn't need to call that API." means. (I initially thought it meant that this would all magically work in an application context based on something else that they're expected to do. Maybe that's true for ObjC or something? But it's clearly not true elsewhere.)
Comment 16 Ross Kirsling 2019-07-16 19:33:57 PDT
Hmm, it appears that another way to make the sample app I mentioned work is to call that "hacked-in downstream API" -- i.e. just RunLoop::run() -- on the individual JSContext threads.

So then it would seem like what we require is a public API to call one of the following:
1. vm.promiseDeferredTimer->runRunLoop()
2. RunLoop::run()
3. RunLoop::iterate()

The first is probably too specific and the second is probably unrealistic if it implies relinquishing all control. So then perhaps it's just a matter of finding a way to implement RunLoop::iterate() for all platforms and exposing it to the user?
Comment 17 Ross Kirsling 2019-07-26 18:01:28 PDT
(Note that my previous comments weren't rhetorical, but... :P)

Allow me to go back to square one here and ask something I would've asked much earlier had I started this effort from scratch:

If we look at what WebCore and node-jsc do, the existing issue could simply be phrased as "since globalObjectMethodTable is a private matter, there is no public API by which an embedder can provide their own promiseRejectionTracker implementation".

In this thread, we've been assuming that promiseRejectionTracker is something with a singular expected implementation and therefore copy-pasting it from WebCore down into JSC would be appropriate so long as we change the scheduling mechanism. But then if it's turning out that the scheduling mechanism isn't fully contained within JSC anyway (in the sense that we need to design a whole additional API in order for the embedder to be able to use it), then should we just have them register a whole promiseRejectionTracker instead, instead of registering JSFunctions for individual event callbacks?

After all, my design here was predicated upon Yusuke's first patch, in which the embedder needs not worry about timing, thanks to the microtask queue. If we're now thinking that they *should* worry about timing, via their own lowercase-r run loop which they need for other things anyway, that's fine, but doesn't that mean the capital-R WTF::RunLoop was just a red herring this whole time?

One way or another, I need to know what target I'm aiming for, if there's any hope of hitting it. :D
Comment 18 Ross Kirsling 2019-07-30 16:31:12 PDT
After all that turmoil, it seems that my confusion all stemmed from something very simple:

WTF::RunLoop never checks USE(CF) because Windows platforms are USE(WINDOWS_EVENT_LOOP) && USE(CF), while JSC::RunLoopTimer / JSC::PromiseDeferredTimer exclusively check USE(CF).

While I think this is something that needs to be reworked in a future patch, it turns out that defining a JSRunLoopIterate() with a USE(CF)-conditional implementation suffices to make this patch work on Windows.
Comment 19 Ross Kirsling 2019-07-30 17:20:23 PDT
Created attachment 375197 [details]
Patch
Comment 20 Darin Adler 2019-08-07 16:40:06 PDT
Comment on attachment 375197 [details]
Patch

This isn’t great for iOS and macOS where we have an Objective-C and Swift API that we are not adding to here.
Comment 21 Ross Kirsling 2019-08-07 21:29:00 PDT
(In reply to Darin Adler from comment #20)
> Comment on attachment 375197 [details]
> Patch
> 
> This isn’t great for iOS and macOS where we have an Objective-C and Swift
> API that we are not adding to here.

I was hoping that JSGlobalContextSetUnhandledRejectionCallback would be useful regardless of what sort of application it is -- is that not the case? (Or more broadly, does "anything that can be platform-agnostic ought to be" not apply along this particular boundary?)

Either way, Keith had also voiced concern to me over IRC regarding JSRunLoopIterate not being useful for CF platforms and suggested perhaps exposing a flag that would cause `vm.promiseDeferredTimer->runRunLoop()` to be called after microtask drain instead.

(He also suggested roping in Geoff for further opinions.)

To be frank, I find it sort of disheartening that we have WTF::RunLoop and yet JSC ignores it completely when it can, accessing CFRunLoop directly and expecting embedders to do the same, creating what seems like an unnecessarily-fundamental divide between embedder platforms. :(

Maybe the original microtask queue-based solution wasn't so bad? ;)
Comment 22 Geoffrey Garen 2019-08-08 12:44:58 PDT
> To be frank, I find it sort of disheartening that we have WTF::RunLoop and
> yet JSC ignores it completely when it can, accessing CFRunLoop directly and
> expecting embedders to do the same, creating what seems like an
> unnecessarily-fundamental divide between embedder platforms. :(

There's no reason it has to be this way!

I believe some of this behavior developed organically because the original CF use in JSC predated WTF::RunLoop. Now that we have WTF::RunLoop, let's use it!
Comment 23 Darin Adler 2019-08-08 16:03:02 PDT
(In reply to Ross Kirsling from comment #21)
> I was hoping that JSGlobalContextSetUnhandledRejectionCallback would be
> useful regardless of what sort of application it is -- is that not the case?
> (Or more broadly, does "anything that can be platform-agnostic ought to be"
> not apply along this particular boundary?)

These C functions are not part of the API on iOS and macOS. They are considered internal implementation details on those platforms. So, on those platforms we often need to add Objective-C API that serves the same purpose. That’s what I was referring to.
Comment 24 Ross Kirsling 2019-08-09 10:14:26 PDT
(In reply to Geoffrey Garen from comment #22)
> I believe some of this behavior developed organically because the original
> CF use in JSC predated WTF::RunLoop. Now that we have WTF::RunLoop, let's
> use it!

This is relieving to hear -- I didn't realize the timeline. :)
I suppose I'll handle that in a separate patch though, if the solution here doesn't depend on it.


(In reply to Darin Adler from comment #23)
> These C functions are not part of the API on iOS and macOS. They are
> considered internal implementation details on those platforms. So, on those
> platforms we often need to add Objective-C API that serves the same purpose.
> That’s what I was referring to.

Wow, I had no idea, but I see what you're saying -- I can add wrappers to JSContext.mm if that suffices.

Actually, if that's the case, then would JSRunLoopIterate still be reasonable to add *just* for C and not for ObjC, if the concern is purely that Cocoa platforms won't use it? (Or is this bad form, if perhaps the C API is meant to be a strict subset?)
Comment 25 Ross Kirsling 2019-08-09 11:34:25 PDT
(In reply to Ross Kirsling from comment #24)
> Actually, if that's the case, then would JSRunLoopIterate still be
> reasonable to add *just* for C and not for ObjC, if the concern is purely
> that Cocoa platforms won't use it? (Or is this bad form, if perhaps the C
> API is meant to be a strict subset?)

Digging further, it seems like if nothing else, maybe it could be moved to JSBasePrivate alongside JSReportExtraMemoryCost?
Comment 26 Ross Kirsling 2019-08-09 15:05:21 PDT
Created attachment 375965 [details]
Patch (run loop)
Comment 27 Ross Kirsling 2019-08-09 15:07:38 PDT
Comment on attachment 375965 [details]
Patch (run loop)

Moved JSRunLoopIterate to JSBasePrivate, added ObjC wrappers for callback registration.
Comment 28 Ross Kirsling 2019-08-13 11:40:01 PDT
Ping?
Comment 29 Ross Kirsling 2019-08-14 11:48:40 PDT
Created attachment 376286 [details]
Patch (microtask queue)
Comment 30 Ross Kirsling 2019-08-14 13:09:49 PDT
Comment on attachment 376286 [details]
Patch (microtask queue)

Keith informed me that we might be okay with the original microtask queue-based approach after all (which would remove any cross-platform worries for the time being) -- if so, we can go with this patch instead.
Comment 31 Ross Kirsling 2019-08-20 17:44:15 PDT
Created attachment 376833 [details]
Patch
Comment 32 Ross Kirsling 2019-08-20 17:45:51 PDT
Comment on attachment 376833 [details]
Patch

Keith suggested dropping the "rejectionhandled" API if nobody's requesting it, so here's the simplest patch yet -- it only tracks unhandled rejections and fires off the microtask queue.
Comment 33 Darin Adler 2019-08-21 09:43:46 PDT
Comment on attachment 376833 [details]
Patch

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

This looks good to me, but I think a JavaScript engine expert should probably do the formal review+ duties.

> Source/JavaScriptCore/API/JSContext.h:184
> +@discussion Similar to window.addEventListener('unhandledrejection'), but for a standalone JSContext.

I think it’s confusing that this method documentation does not mention that, for the JSContext from a web page, this callback will never be called because WebKit will handle it. The documentation acknowledges the existence of web browsing by mentioning the event listener on the window object, but then doesn’t clarify that this method is only relevant when there is no window object, essentially "when JavaScript is used outside a webpage context".

I’m not sure the right way to word this, but I think it needs to be mentioned.

> Source/JavaScriptCore/API/JSContext.mm:269
> +    if (exception)
> +        [self notifyException:exception];

Is it OK to not have a "return" here? Why?

> Source/JavaScriptCore/API/JSContextRef.h:162
> +@discussion JSC-only alternative to window.addEventListener('unhandledrejection').

Here we get slightly closer by saying "JSC-only". But the same comment as above applies.
Comment 34 Keith Miller 2019-08-21 10:54:55 PDT
(In reply to Darin Adler from comment #33)
> Comment on attachment 376833 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=376833&action=review
> 
> This looks good to me, but I think a JavaScript engine expert should
> probably do the formal review+ duties.
> 
> > Source/JavaScriptCore/API/JSContext.h:184
> > +@discussion Similar to window.addEventListener('unhandledrejection'), but for a standalone JSContext.
> 
> I think it’s confusing that this method documentation does not mention that,
> for the JSContext from a web page, this callback will never be called
> because WebKit will handle it. The documentation acknowledges the existence
> of web browsing by mentioning the event listener on the window object, but
> then doesn’t clarify that this method is only relevant when there is no
> window object, essentially "when JavaScript is used outside a webpage
> context".
> 
> I’m not sure the right way to word this, but I think it needs to be
> mentioned.

I actually don't think this is an issue because the only APIs that provide a JSContext and a window are officially deprecated. WKWebView does not provide any JS objects to the native application.
Comment 35 Keith Miller 2019-08-21 11:06:01 PDT
Comment on attachment 376833 [details]
Patch

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

> Source/JavaScriptCore/API/JSContext.h:187
> +- (void)setUnhandledRejectionCallback:(JSValue *)function;

I'm not sure we should have this function in the ObjC API. I think it might be nicer if the API was: when creating a JSContext, if the JSContextRef does not already have an unhandledRejectionCallback set a function that calls the exceptionHandler callback. There is some downside that we can't provide the promise object. Although, I suppose we could provide that as an additional "currentArguments" value on the JSContext.

>> Source/JavaScriptCore/API/JSContext.mm:269
>> +        [self notifyException:exception];
> 
> Is it OK to not have a "return" here? Why?

Yeah, there needs to be a return here.

> Source/JavaScriptCore/API/JSContextRef.h:167
> +JS_EXPORT void JSGlobalContextSetUnhandledRejectionCallback(JSGlobalContextRef ctx, JSObjectRef function, JSValueRef* exception);

This needs an JSC_API_AVAILABLE(macos(JSC_MAC_TBA), ios(JSC_IOS_TBA)) annotation.
Comment 36 Keith Miller 2019-08-21 11:26:14 PDT
Comment on attachment 376833 [details]
Patch

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

> Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp:772
> +    if (promise->inherits<JSInternalPromise>(vm))

We normally do this via jsDynamicCast<JSInternalPromise>(vm, promise). We have optimizations for some cases there that we haven't applied to inherits and it makes it easier to search for.

> Source/JavaScriptCore/runtime/VM.h:1072
> +    Vector<Strong<JSPromise>> m_aboutToBeNotifiedRejectedPromises;

I feel like this is going to be a huge list for non-trivial async programs. if you have a loop with:

let p = new Promise((resolve, reject) => {
    ...
    reject(p)
});

p will be kept alive until the the microtasks are drained. Which means programs will OOM if they never drain all their microtasks...

I think, at least, you'll need to clear handled promises at the GC flip. See where we call finalizeUnconditionalFinalizers.
Comment 37 Ross Kirsling 2019-08-21 12:19:36 PDT
(In reply to Keith Miller from comment #36)
> > Source/JavaScriptCore/runtime/VM.h:1072
> > +    Vector<Strong<JSPromise>> m_aboutToBeNotifiedRejectedPromises;
> 
> I feel like this is going to be a huge list for non-trivial async programs.
>
> if you have a loop with:
>
> let p = new Promise((resolve, reject) => {
>     ...
>     reject(p)
> });
> 
> p will be kept alive until the the microtasks are drained. Which means
> programs will OOM if they never drain all their microtasks...

Is this concern to unique the non-WebCore case...?

WebCore uses DOMGuarded<JSPromise> instead of Strong<JSPromise>, but otherwise this is meant to be the same -- in particular, WebCore processes m_aboutToBeNotifiedRejectedPromises right after microtask checkpoint (even though it subsequently schedules the *reporting* on the event loop).

https://github.com/WebKit/webkit/blob/master/Source/WebCore/dom/RejectedPromiseTracker.cpp#L134-L145
Comment 38 Ross Kirsling 2019-08-21 12:21:40 PDT
(In reply to Ross Kirsling from comment #37)
> Is this concern to unique the non-WebCore case...?

* unique to

(BZ really needs an edit feature... 😭 I spend ages writing a comment and then introduce a typo at the last moment...)
Comment 39 Ross Kirsling 2019-08-21 12:25:13 PDT
(In reply to Keith Miller from comment #35)
> > Source/JavaScriptCore/API/JSContext.h:187
> > +- (void)setUnhandledRejectionCallback:(JSValue *)function;
> 
> I'm not sure we should have this function in the ObjC API. I think it might
> be nicer if the API was: when creating a JSContext, if the JSContextRef does
> not already have an unhandledRejectionCallback set a function that calls the
> exceptionHandler callback. There is some downside that we can't provide the
> promise object. Although, I suppose we could provide that as an additional
> "currentArguments" value on the JSContext.

Don't we want this to be opt-in though? Seems like it would be a huge shift to auto-convert unhandled rejections  to exceptions by default, no?
Comment 40 Keith Miller 2019-08-21 15:03:57 PDT
(In reply to Ross Kirsling from comment #39)
> (In reply to Keith Miller from comment #35)
> > > Source/JavaScriptCore/API/JSContext.h:187
> > > +- (void)setUnhandledRejectionCallback:(JSValue *)function;
> > 
> > I'm not sure we should have this function in the ObjC API. I think it might
> > be nicer if the API was: when creating a JSContext, if the JSContextRef does
> > not already have an unhandledRejectionCallback set a function that calls the
> > exceptionHandler callback. There is some downside that we can't provide the
> > promise object. Although, I suppose we could provide that as an additional
> > "currentArguments" value on the JSContext.
> 
> Don't we want this to be opt-in though? Seems like it would be a huge shift
> to auto-convert unhandled rejections  to exceptions by default, no?

I can see the argument for and against this version. Assuming, unhandledPromiseRejection callbacks are rare this wouldn't really be a breaking change. If they happen all the time, then it seems pretty reasonable to do so. If we find we have a compatibility issue we (on Darwin anyway) could also guard the call with a linked on or after check.
Comment 41 Keith Miller 2019-08-21 15:06:47 PDT
(In reply to Ross Kirsling from comment #37)
> (In reply to Keith Miller from comment #36)
> > > Source/JavaScriptCore/runtime/VM.h:1072
> > > +    Vector<Strong<JSPromise>> m_aboutToBeNotifiedRejectedPromises;
> > 
> > I feel like this is going to be a huge list for non-trivial async programs.
> >
> > if you have a loop with:
> >
> > let p = new Promise((resolve, reject) => {
> >     ...
> >     reject(p)
> > });
> > 
> > p will be kept alive until the the microtasks are drained. Which means
> > programs will OOM if they never drain all their microtasks...
> 
> Is this concern to unique the non-WebCore case...?
> 
> WebCore uses DOMGuarded<JSPromise> instead of Strong<JSPromise>, but
> otherwise this is meant to be the same -- in particular, WebCore processes
> m_aboutToBeNotifiedRejectedPromises right after microtask checkpoint (even
> though it subsequently schedules the *reporting* on the event loop).
> 
> https://github.com/WebKit/webkit/blob/master/Source/WebCore/dom/
> RejectedPromiseTracker.cpp#L134-L145

Hmm, then I won't worry about it for now but can you file a bug on it?
Comment 42 Ross Kirsling 2019-08-21 15:25:32 PDT
Created attachment 376931 [details]
Patch
Comment 43 Ross Kirsling 2019-08-21 15:26:08 PDT
Comment on attachment 376931 [details]
Patch

Addressed feedback modulo outstanding questions.
Comment 44 Keith Miller 2019-08-21 15:33:34 PDT
(In reply to Ross Kirsling from comment #43)
> Comment on attachment 376931 [details]
> Patch
> 
> Addressed feedback modulo outstanding questions.

Sorry, I'm a bit lost. What are the outstanding questions?
Comment 45 Ross Kirsling 2019-08-21 16:17:48 PDT
(In reply to Keith Miller from comment #44)
> Sorry, I'm a bit lost. What are the outstanding questions?

Sorry, I hadn't noticed your latest comments when I wrote that.

(In reply to Keith Miller from comment #41)
> Hmm, then I won't worry about it for now but can you file a bug on it?

Opened bug 201005.

(In reply to Keith Miller from comment #40)
> I can see the argument for and against this version. Assuming,
> unhandledPromiseRejection callbacks are rare this wouldn't really be a
> breaking change. If they happen all the time, then it seems pretty
> reasonable to do so. If we find we have a compatibility issue we (on Darwin
> anyway) could also guard the call with a linked on or after check.

Based on our IRC conversation, I guess we could move the C API to JSContextRefPrivate and postpone the ObjC API. It's a bit unfortunate for the OP of this bug though...
Comment 46 Ross Kirsling 2019-08-21 16:36:41 PDT
Created attachment 376949 [details]
Patch
Comment 47 Don Olmstead 2019-08-22 10:15:19 PDT
(In reply to Ross Kirsling from comment #45)
> (In reply to Keith Miller from comment #40)
> > I can see the argument for and against this version. Assuming,
> > unhandledPromiseRejection callbacks are rare this wouldn't really be a
> > breaking change. If they happen all the time, then it seems pretty
> > reasonable to do so. If we find we have a compatibility issue we (on Darwin
> > anyway) could also guard the call with a linked on or after check.
> 
> Based on our IRC conversation, I guess we could move the C API to
> JSContextRefPrivate and postpone the ObjC API. It's a bit unfortunate for
> the OP of this bug though...

Should we maybe fork this bug and keep this open for Apple folks to actually move it over to public and provide the Obj C interface then?
Comment 48 Keith Miller 2019-08-22 17:04:10 PDT
Comment on attachment 376949 [details]
Patch

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

r=me.

> Source/JavaScriptCore/API/tests/testapi.cpp:521
>  }

Can you add tests for:

1) promise that's rejected then handled synchronously.
2) promise that's rejected then handled in a microtask turn.
3) promise that's rejected in a microtask then handled in a later microtask.
4) promise that's rejected in a microtask then not handled.

> Source/JavaScriptCore/runtime/VM.h:1072
> +    Vector<Strong<JSPromise>> m_aboutToBeNotifiedRejectedPromises;

Can you add a FIXME with a link to the bug you filed?
Comment 49 Keith Miller 2019-08-22 17:05:59 PDT
(In reply to Don Olmstead from comment #47)
> (In reply to Ross Kirsling from comment #45)
> > (In reply to Keith Miller from comment #40)
> > > I can see the argument for and against this version. Assuming,
> > > unhandledPromiseRejection callbacks are rare this wouldn't really be a
> > > breaking change. If they happen all the time, then it seems pretty
> > > reasonable to do so. If we find we have a compatibility issue we (on Darwin
> > > anyway) could also guard the call with a linked on or after check.
> > 
> > Based on our IRC conversation, I guess we could move the C API to
> > JSContextRefPrivate and postpone the ObjC API. It's a bit unfortunate for
> > the OP of this bug though...
> 
> Should we maybe fork this bug and keep this open for Apple folks to actually
> move it over to public and provide the Obj C interface then?

I guess it might be nice for people that are already tracking this bug. We probably shouldn't close this bug at least until we have nailed down an API everyone is happy with.
Comment 50 Ross Kirsling 2019-08-23 11:19:14 PDT
Created attachment 377136 [details]
Patch for landing
Comment 51 WebKit Commit Bot 2019-08-23 11:51:26 PDT
Comment on attachment 377136 [details]
Patch for landing

Clearing flags on attachment: 377136

Committed r249058: <https://trac.webkit.org/changeset/249058>
Comment 52 WebKit Commit Bot 2019-08-23 11:51:29 PDT
All reviewed patches have been landed.  Closing bug.
Comment 53 Ross Kirsling 2019-08-23 12:18:36 PDT
Reopening until we have an ObjC API.
Comment 54 Oliver Hunt 2019-09-06 13:35:56 PDT
Wait, is this actually a public API? It looks like it's SPI?
Comment 55 Saam Barati 2019-09-06 14:09:51 PDT
(In reply to Oliver Hunt from comment #54)
> Wait, is this actually a public API? It looks like it's SPI?

it's SPI
Comment 56 Ross Kirsling 2019-09-06 14:13:28 PDT
(In reply to Oliver Hunt from comment #54)
> Wait, is this actually a public API? It looks like it's SPI?

Sorry about the confusion -- the title of this bug (which is still open) is accurate but the title of the patch that has been landed is not... :(

(I wonder if it would be worth having the wording in the blog post updated?)