How best to do async functions and XPCOM?

Hi all

I'm redesigning a bunch of Thunderbird things to be asynchronous. I'd 
like to use Promises but a lot of the time I'll be far from a JS context 
so that doesn't really seem like an option. The best alternative I've 
come up with is to create some sort of listener object and pass it to 
the async function:

interface nsIFooOperationListener : nsISupports {
   void onOperationComplete(
     in nsresult status,
     [optional] in string errorMessage
   );
};

....

void fooFunction(..., in nsIFooOperationListener listener);

This works fine but I wonder if there's a better way, or if there's some 
established prior art I can use/borrow rather than find out the pitfalls 
myself.

TIA,
GL
0
Geoff
12/5/2019 10:20:12 PM
mozilla.dev.platform 6614 articles. 0 followers. Post Follow

10 Replies
71 Views

Similar Articles

[PageSpeed] 20

On Friday, December 6, 2019 at 9:20:21 AM UTC+11, Geoff Lankow wrote:
> Hi all
> 
> I'm redesigning a bunch of Thunderbird things to be asynchronous. I'd 
> like to use Promises but a lot of the time I'll be far from a JS context 
> so that doesn't really seem like an option. The best alternative I've 
> come up with is to create some sort of listener object and pass it to 
> the async function:
> 
> interface nsIFooOperationListener : nsISupports {
>    void onOperationComplete(
>      in nsresult status,
>      [optional] in string errorMessage
>    );
> };
> 
> ...
> 
> void fooFunction(..., in nsIFooOperationListener listener);
> 
> This works fine but I wonder if there's a better way, or if there's some 
> established prior art I can use/borrow rather than find out the pitfalls 
> myself.
> 
> TIA,
> GL

We have mozilla::MozPromise [0], similar to mozilla::dom::Promise but it doesn't rely on JS at all.

It can be a bit tricky to use, the simplest way (to start) is probably to do something like InvokeAsync(work thread, code to run that resolves or rejects the promise)->Then(target thread, on-success follow-up, on-failure follow-up) (e.g., [1]).

Good luck!
g.

[0] https://searchfox.org/mozilla-central/rev/ea63a0888d406fae720cf24f4727d87569a8cab5/xpcom/threads/MozPromise.h#98
[1] https://searchfox.org/mozilla-central/rev/ea63a0888d406fae720cf24f4727d87569a8cab5/dom/media/ChannelMediaDecoder.cpp#392
0
Gerald
12/5/2019 11:33:40 PM
On 12/5/19 5:20 PM, Geoff Lankow wrote:
> I'm redesigning a bunch of Thunderbird things to be asynchronous. I'd 
> like to use Promises but a lot of the time I'll be far from a JS context 

Is that because both the implementation of the API and the consumer of 
the API will be C++?

For what it's worth, you are never far from a JSContext; AutoJSAPI will 
give you one.  The hard part may be finding an appropriate global to use...

In general, if the consumer or implementor of the API is JS, I would use 
JS promises.  If this is all in C++ land, MozPromise works fine, afaict.

-Boris
0
Boris
12/6/2019 3:59:27 AM
On Friday, December 6, 2019 at 7:20:21 AM UTC+9, Geoff Lankow wrote:
> Hi all
> 
> I'm redesigning a bunch of Thunderbird things to be asynchronous. I'd 
> like to use Promises but a lot of the time I'll be far from a JS context 
> so that doesn't really seem like an option. The best alternative I've 
> come up with is to create some sort of listener object and pass it to 
> the async function:
> 
> interface nsIFooOperationListener : nsISupports {
>    void onOperationComplete(
>      in nsresult status,
>      [optional] in string errorMessage
>    );
> };
> 
> ...
> 
> void fooFunction(..., in nsIFooOperationListener listener);
> 
> This works fine but I wonder if there's a better way, or if there's some 
> established prior art I can use/borrow rather than find out the pitfalls 
> myself.
> 
> TIA,
> GL

I recently got a chance to play with MozPromise [0] in a pure C++ function to access Windows API. It served my purpose well except that I had to use a bit hacky way to pass `MozPromiseHolder` into a lambda expression.

[0]: https://phabricator.services.mozilla.com/D42484
0
saschanaz7
12/6/2019 11:19:44 AM
On 12/5/2019 6:33 PM, Gerald Squelart wrote:
> On Friday, December 6, 2019 at 9:20:21 AM UTC+11, Geoff Lankow wrote:
>> Hi all
>>
>> I'm redesigning a bunch of Thunderbird things to be asynchronous. I'd
>> like to use Promises but a lot of the time I'll be far from a JS context
>> so that doesn't really seem like an option. The best alternative I've
>> come up with is to create some sort of listener object and pass it to
>> the async function:
>>
>> interface nsIFooOperationListener : nsISupports {
>>     void onOperationComplete(
>>       in nsresult status,
>>       [optional] in string errorMessage
>>     );
>> };
>>
>> ...
>>
>> void fooFunction(..., in nsIFooOperationListener listener);
>>
>> This works fine but I wonder if there's a better way, or if there's some
>> established prior art I can use/borrow rather than find out the pitfalls
>> myself.
>>
>> TIA,
>> GL
> We have mozilla::MozPromise [0], similar to mozilla::dom::Promise but it doesn't rely on JS at all.
>
> It can be a bit tricky to use, the simplest way (to start) is probably to do something like InvokeAsync(work thread, code to run that resolves or rejects the promise)->Then(target thread, on-success follow-up, on-failure follow-up) (e.g., [1]).

The problem with MozPromise is that it doesn't integrate well if you use 
XPIDL interfaces, so you have this annoying issue that if you want to 
use XPIDL integration, you have to use mozilla::dom::Promise, which is 
annoying to use from C++. A third wrinkle, especially now that async 
functions has landed in Rust, is if you want to try to use 
std::future::Future in Rust, which isn't going to convert terribly well 
to either form.

It may be worth spending some time building some wrappers to integrate 
between all of our various async function frameworks...

-- 
Joshua Cranmer
Thunderbird and DXR developer
Source code archæologist

0
UTF
12/9/2019 4:40:52 AM
Also sprach Joshua Cranmer:

> The problem with MozPromise is that it doesn't integrate well if you =
use XPIDL interfaces, so you have this annoying issue that if you want =
to use XPIDL integration, you have to use mozilla::dom::Promise, which =
is annoying to use from C++. A third wrinkle, especially now that async =
functions has landed in Rust, is if you want to try to use =
std::future::Future in Rust, which isn't going to convert terribly well =
to either form.

In this context it should also be mentioned that the Rust XPCOM bindings =
do not support DOM promises quite yet:

	https://bugzilla.mozilla.org/show_bug.cgi?id=3D1512319

0
Andreas
12/9/2019 9:08:22 PM
On Fri, Dec 06, 2019 at 11:20:12AM +1300, Geoff Lankow wrote:
>Hi all
>
>I'm redesigning a bunch of Thunderbird things to be asynchronous. I'd 
>like to use Promises but a lot of the time I'll be far from a JS 
>context so that doesn't really seem like an option. The best 
>alternative I've come up with is to create some sort of listener 
>object and pass it to the async function:
>
>interface nsIFooOperationListener : nsISupports {
>  void onOperationComplete(
>    in nsresult status,
>    [optional] in string errorMessage
>  );
>};
>
>...
>
>void fooFunction(..., in nsIFooOperationListener listener);
>
>This works fine but I wonder if there's a better way, or if there's 
>some established prior art I can use/borrow rather than find out the 
>pitfalls myself.

In general, if you're thinking about using an XPIDL interface for something, 
you're probably going down the wrong track. If you're only going to be 
dealing with C++, then you should generally use a pure C++ interface 
(probably just MozPromise in this case). If you're going to need to interact 
with JS at any point, you should probably just use dom::Promise. You can 
always use AutoJSAPI to get a JSContext, and create the Promise in the 
shared JSM global.
0
Kris
12/10/2019 8:30:29 PM
On Sun, Dec 08, 2019 at 11:40:52PM -0500, Joshua Cranmer 🐧 wrote:
>The problem with MozPromise is that it doesn't integrate well if you 
>use XPIDL interfaces, so you have this annoying issue that if you want 
>to use XPIDL integration, you have to use mozilla::dom::Promise, which 
>is annoying to use from C++.

In what way is dom::Promise annoying to use from C++? The entire purpose of 
dom::Promise is to make JS promises easy to use from C++.
0
Kris
12/10/2019 8:31:29 PM
Thanks to all who replied, including Kris whose reply seems to have not 
made it back to the list. As per usual when I ask for help I've been 
immediately distracted by something more urgent and not got back to the 
problem at hand. :-/

As some pointed out this would be a lot easier if I was exclusively in 
JS-land or in C++-land, but I'm not. Nor am I starting something new 
from scratch, which also be easier.

I'm modernising some existing components while trying to avoid wholesale 
rewriting of major parts of the program. There are multiple pieces in 
both languages and they all interact. Also some of this code is *really* 
old in Mozilla terms, which is, um, helpful…

GL
0
Geoff
12/11/2019 1:21:53 AM
On 12/10/19 3:31 PM, Kris Maglione wrote:
> In what way is dom::Promise annoying to use from C++?

The one thing I know about that's pretty annoying is if you receive the 
promise from someone else and want to add reactions to it. 
PromiseNativeHandler kinda works, but then you get JS::Values and have 
to extract the things you care about from them manually, which is a bit 
of a pain.

-Boris
0
Boris
12/12/2019 2:30:06 PM
On Thu, Dec 12, 2019 at 09:30:06AM -0500, Boris Zbarsky wrote:
>On 12/10/19 3:31 PM, Kris Maglione wrote:
>>In what way is dom::Promise annoying to use from C++?
>
>The one thing I know about that's pretty annoying is if you receive 
>the promise from someone else and want to add reactions to it. 
>PromiseNativeHandler kinda works, but then you get JS::Values and have 
>to extract the things you care about from them manually, which is a 
>bit of a pain.

Note that you can also use `ThenWithCycleCollectedArgs` and 
`ThenWithoutCycleCollection` to add reactions from native code, which is 
a lot easier than `PromiseNativeHandler`. You do still need to manually 
deal with JS::Values, though.
0
Kris
1/6/2020 9:47:44 PM
Reply: