Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

JS API change: remove JS_SetBranchCallback, change JS_*OperationCallback*

196 views
Skip to first unread message

Brendan Eich

unread,
Feb 8, 2009, 12:33:56 AM2/8/09
to g...@mozilla.com
Hi,

We plan on changing the branch and operation callback APIs in
SpiderMonkey. The long-standing branch callback, and the recently
added (Firefox 3) operation callback API, will be removed and replaced
with the following entry points:

extern JS_PUBLIC_API(JSOperationCallback)
JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback);

extern JS_PUBLIC_API(JSOperationCallback)
JS_GetOperationCallback(JSContext *cx);

extern JS_PUBLIC_API(void)
JS_TriggerOperationCallback(JSContext *cx);

Please note that this new API does not allow specifying any operation
limits. An operation callback is now merely a function associated with
a context that will be called some time after
JS_TriggerOperationCallback() is invoked on that context.

The operation callback is always invoked from the thread associated
with the context, and execution on that context pauses until the
callback returns. As previously, the callback can return false to
abort code execution (for example when the callback decides that too
much time has elapsed executing code on this context).

The JS engine now automatically yields active requests on the context
immediately prior to invoking the callback. It is no longer necessary
to manually yield the request from within the callback in
multithreaded embeddings.

Callbacks may be triggered from any thread, or even from inside signal
and interrupt handlers, as they merely update a flag in memory.

Multithreaded embeddings are advised to use a timer thread to
implement a watchdog. The timer thread can use
JS_TriggerOperationCallback to ensure that the operation callback is
invoked on a regular basis, permitting the callback to abort execution
if appropriate.

For single-threaded embeddings that cannot use signals or interrupts
to trigger operation callbacks on a regular basis we have added the
following API of last-resort. Its use is discouraged (use signals or a
watchdog thread if you can); this API is not available to
multithreaded embeddings:

extern JS_PUBLIC_API(JSHeartbeatCallback)
JS_SetHeartbeatCallback(JSRuntime *rt, JSHeartbeatCallback
hbCallback);

extern JS_PUBLIC_API(JSHeartbeatCallback)
JS_GetHeartbeatCallback(JSRuntime *rt);

This API installs a heartbeat callback that will be called from the JS
engine on a regular basis. The invocation frequency is machine
dependent and varies depending on whether code is run by the
interpreter or is executed as machine code compiled by the tracing
JIT. The heartbeat callback may trigger an operation callback.

Removing the old branch and operation callback APIs speeds up the
execution of JIT-compiled tight loops by up to 25%. Over a set of
mixed benchmark programs we have observed an average speedup of 2%.

We SpiderMonkey hackers take JS API compatibility seriously,
preserving it by default and adding new entry points while almost
never removing old ones. This has been an element of SpiderMonkey's
success for over ten years in Mozilla open source, even longer
counting Netscape-licensed versions.

But with the TraceMonkey work to add a JIT to SpiderMonkey, some APIs
have to go. We hope embedders will agree that the branch callback was
always too blunt an instrument, and that the performance and
flexibility gained by the new operation callback proposed here and
implemented in

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

are worth the compatibility break.

If you have any comments or questions please let us know.

Andreas Gal Brendan Eich Igor
Bukanov

Brendan Eich

unread,
Feb 9, 2009, 5:40:38 PM2/9/09
to
> For single-threaded embeddings that cannot use signals or interrupts
> to trigger operation callbacks on a regular basis we have added the
> following API of last-resort. Its use is discouraged (use signals or a
> watchdog thread if you can); this API is not available to
> multithreaded embeddings:
>
> extern JS_PUBLIC_API(JSHeartbeatCallback)
> JS_SetHeartbeatCallback(JSRuntime *rt, JSHeartbeatCallback
> hbCallback);
>
> extern JS_PUBLIC_API(JSHeartbeatCallback)
> JS_GetHeartbeatCallback(JSRuntime *rt);
>
> This API installs a heartbeat callback that will be called from the JS
> engine on a regular basis. The invocation frequency is machine
> dependent and varies depending on whether code is run by the
> interpreter or is executed as machine code compiled by the tracing
> JIT. The heartbeat callback may trigger an operation callback.

Igor pointed out in

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

how this API is not close enough to substitute for the branch callback
or old (as in, Firefox 3 era, not yet shipped in a standalone
SpiderMonkey) operation callback, and it won't stop nearly-infinite
loops in native code. The best way forward is signals if not threads,
so we are withdrawing the heartbeat API.

/be

nickg

unread,
Feb 11, 2009, 5:31:43 PM2/11/09
to
Team MozJS,

I was a bit surprised by the API change, but I swapped out the old
operation count branching mechanism (I never understood exactly what
the SetOperationLimit mapped to anyways) to use signals in 10minutes.
It works great!

I wrote up some notes on a single-thread+signals mechanism:

http://wiki.client9.com/wiki.pl/SpiderMonkeyRunaways

and some sample code to test it out here

http://svn.client9.com/client9code/spidermonkey/micro_opt.cc

$ # 1 second timeout
$ ./a.out
---- WITH JIT
Array(1<<27).sort() : 1010861
while (1) {} : 1017258
for (var i= 0; i < 10000000; ++i) {} : 366390
for (var i= 0; i < 10000000; i++) {} : 366423
for (var i= 0; i < 10000000; i += 1) {} : 570077
for (var i= 0; i < 10000000; i = i + 1) {} : 564697
var i = 0; while (i < 10000000) { ++i; } : 369668
function f() { f(); } f(); : 2848
---- WITHOUT JIT
Array(1<<27).sort() : 1011369
while (1) {} : 1008704
for (var i= 0; i < 10000000; ++i) {} : 360710
for (var i= 0; i < 10000000; i++) {} : 369606
for (var i= 0; i < 10000000; i += 1) {} : 562706
for (var i= 0; i < 10000000; i = i + 1) {} : 562393
var i = 0; while (i < 10000000) { ++i; } : 365057
function f() { f(); } f(); : 2394

(ok the for loops complete before the 1s time out)

(FYI -- looks like the JIT isn't being triggered by trivial for loops
anymore -- it was yesterday)


thanks again,

--nickg

Wes Garland

unread,
Feb 22, 2009, 11:54:01 AM2/22/09
to Brendan Eich, g...@mozilla.com, dev-tech-...@lists.mozilla.org
Brendan:

As long as you are changing the API, would you consider adding some small
promises?
- JS_(Get|Set|Trigger)OperationCallback() are safe to run inside a signal
handler
- JS_(Get|Set|Trigger)OperationCallback() are safe to run from a thread
which is NOT the JS_SetContextThread() thread

These routines currently look safe to run and do not even have the normal
CHECK_REQUEST(cx) guards in them, so I think the promises I'm asking for are
already implemented, just not future-proofed from a documentation point of
view.

So, to be clear, I'd like to be able to safely write

int mySignalHandler(sig)
{
gOldCB = JS_GetOperationCallback(someCX);
JS_SetOperationCallback(someCX, myCB);
JS_TriggerOperationCallback(someCX);
}

JSBool myCB(JSContext *cx)
{
JS_SetOperationCallback(cx, gOldCB);
return JS_CallFunction(cx, ....);
}

in an environment with multiple threads of JavaScript executing, and have
the thread executing someCX's JS code run myCB shortly after
mySignalHandler() runs. It is not guaranteed that the thread catching the
signal will be the same thread that owns someCX (in all likelihood, signals
will be caught with a totally separate thread running sigwait(3C));

Notes
- I don't expect signal safety inside JS_ASSERT, that would be silly
- JS(Get|Set|Trigger) thread synchronization is a problem left for the
API-user and as such omitted from my example

Wes

--
Wesley W. Garland
Director, Product Development
PageMail, Inc.
+1 613 542 2787 x 102

Jason Orendorff

unread,
Feb 25, 2009, 3:01:09 PM2/25/09
to Brendan Eich, g...@mozilla.com
On 2/7/09 11:33 PM, Brendan Eich wrote:
> extern JS_PUBLIC_API(JSOperationCallback)
> JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback);
>
> extern JS_PUBLIC_API(void)
> JS_TriggerOperationCallback(JSContext *cx);

This is a much nicer API, but I wonder why it's still called "operation
callback". How would a patch to rename this to "async callback" be
received?

Also, it seems funny that the callback is sometimes called even when the
application didn't trigger it. It seems like we could easily make it
speak only when spoken to. Why not?

-j

0 new messages