Skip to content

Commit 61cdcb3

Browse files
committed
Add centralized handling for calling user callbacks
This change also changes the way outstanding works keeps the runtime alive. We now use a counter rather than a single runtimeKeepalive setting so that once outstanding works is complete the runtime can shutdown. The specific need for this comes from a user who whats to do async fetch work on a pthread, and then have that thread become joinable once the fetches are done. With the old API this was not possible because setting noExitRuntime would make the thread un-joinable. Calling pthread_exit form the fetch callback also failed because the callback wasn't called from a try/catch that was setup to handle it the "unwind" exception.
1 parent 855df3e commit 61cdcb3

12 files changed

+260
-81
lines changed

emcc.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,13 @@ def default_setting(name, new_default):
14791479
if not shared.Settings.MINIMAL_RUNTIME:
14801480
# In non-MINIMAL_RUNTIME, the core runtime depends on these functions to be present. (In MINIMAL_RUNTIME, they are
14811481
# no longer always bundled in)
1482-
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$demangle', '$demangleAll', '$jsStackTrace', '$stackTrace']
1482+
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
1483+
'$keepRuntimeAlive',
1484+
'$demangle',
1485+
'$demangleAll',
1486+
'$jsStackTrace',
1487+
'$stackTrace'
1488+
]
14831489

14841490
if shared.Settings.FILESYSTEM:
14851491
# to flush streams on FS exit, we need to be able to call fflush
@@ -1599,7 +1605,7 @@ def include_and_export(name):
15991605
include_and_export('invokeEntryPoint')
16001606
if not shared.Settings.MINIMAL_RUNTIME:
16011607
# noExitRuntime does not apply to MINIMAL_RUNTIME.
1602-
include_and_export('getNoExitRuntime')
1608+
include_and_export('keepRuntimeAlive')
16031609

16041610
if shared.Settings.MODULARIZE:
16051611
if shared.Settings.EXPORT_NAME == 'Module':

src/Fetch.js

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,9 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
432432
}
433433

434434
function startFetch(fetch, successcb, errorcb, progresscb, readystatechangecb) {
435-
#if !MINIMAL_RUNTIME
436435
// Avoid shutting down the runtime since we want to wait for the async
437436
// response.
438-
noExitRuntime = true;
439-
#endif
437+
runtimeKeepalivePush();
440438

441439
var fetch_attr = fetch + {{{ C_STRUCTS.emscripten_fetch_t.__attributes }}};
442440
var requestMethod = UTF8ToString(fetch_attr);
@@ -458,29 +456,39 @@ function startFetch(fetch, successcb, errorcb, progresscb, readystatechangecb) {
458456
#if FETCH_DEBUG
459457
console.log('fetch: operation success. e: ' + e);
460458
#endif
461-
if (onsuccess) {{{ makeDynCall('vi', 'onsuccess') }}}(fetch);
462-
else if (successcb) successcb(fetch);
459+
runtimeKeepalivePop();
460+
callUserCallback(function() {
461+
if (onsuccess) {{{ makeDynCall('vi', 'onsuccess') }}}(fetch);
462+
else if (successcb) successcb(fetch);
463+
});
463464
};
464465

465466
var reportProgress = function(fetch, xhr, e) {
466-
if (onprogress) {{{ makeDynCall('vi', 'onprogress') }}}(fetch);
467-
else if (progresscb) progresscb(fetch);
467+
callUserCallback(function() {
468+
if (onprogress) {{{ makeDynCall('vi', 'onprogress') }}}(fetch);
469+
else if (progresscb) progresscb(fetch);
470+
});
468471
};
469472

470473
var reportError = function(fetch, xhr, e) {
471474
#if FETCH_DEBUG
472475
console.error('fetch: operation failed: ' + e);
473476
#endif
474-
if (onerror) {{{ makeDynCall('vi', 'onerror') }}}(fetch);
475-
else if (errorcb) errorcb(fetch);
477+
runtimeKeepalivePop();
478+
callUserCallback(function() {
479+
if (onerror) {{{ makeDynCall('vi', 'onerror') }}}(fetch);
480+
else if (errorcb) errorcb(fetch);
481+
});
476482
};
477483

478484
var reportReadyStateChange = function(fetch, xhr, e) {
479485
#if FETCH_DEBUG
480486
console.log('fetch: ready state change. e: ' + e);
481487
#endif
482-
if (onreadystatechange) {{{ makeDynCall('vi', 'onreadystatechange') }}}(fetch);
483-
else if (readystatechangecb) readystatechangecb(fetch);
488+
callUserCallback(function() {
489+
if (onreadystatechange) {{{ makeDynCall('vi', 'onreadystatechange') }}}(fetch);
490+
else if (readystatechangecb) readystatechangecb(fetch);
491+
});
484492
};
485493

486494
var performUncachedXhr = function(fetch, xhr, e) {
@@ -499,15 +507,21 @@ function startFetch(fetch, successcb, errorcb, progresscb, readystatechangecb) {
499507
#if FETCH_DEBUG
500508
console.log('fetch: IndexedDB store succeeded.');
501509
#endif
502-
if (onsuccess) {{{ makeDynCall('vi', 'onsuccess') }}}(fetch);
503-
else if (successcb) successcb(fetch);
510+
runtimeKeepalivePop();
511+
callUserCallback(function() {
512+
if (onsuccess) {{{ makeDynCall('vi', 'onsuccess') }}}(fetch);
513+
else if (successcb) successcb(fetch);
514+
});
504515
};
505516
var storeError = function(fetch, xhr, e) {
506517
#if FETCH_DEBUG
507518
console.error('fetch: IndexedDB store failed.');
508519
#endif
509-
if (onsuccess) {{{ makeDynCall('vi', 'onsuccess') }}}(fetch);
510-
else if (successcb) successcb(fetch);
520+
runtimeKeepalivePop();
521+
callUserCallback(function() {
522+
if (onsuccess) {{{ makeDynCall('vi', 'onsuccess') }}}(fetch);
523+
else if (successcb) successcb(fetch);
524+
});
511525
};
512526
fetchCacheData(Fetch.dbInstance, fetch, xhr.response, storeSuccess, storeError);
513527
};

src/library.js

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3771,13 +3771,16 @@ LibraryManager.library = {
37713771
},
37723772

37733773
// Callable in pthread without __proxy needed.
3774+
emscripten_exit_with_live_runtime__sig: 'v',
3775+
emscripten_exit_with_live_runtime__deps: ['$runtimeKeepalivePush'],
37743776
emscripten_exit_with_live_runtime: function() {
37753777
#if !MINIMAL_RUNTIME
3776-
noExitRuntime = true;
3778+
runtimeKeepalivePush();
37773779
#endif
37783780
throw 'unwind';
37793781
},
37803782

3783+
emscripten_force_exit__deps: ['$runtimeKeepaliveCounter'],
37813784
emscripten_force_exit__proxy: 'sync',
37823785
emscripten_force_exit__sig: 'vi',
37833786
emscripten_force_exit: function(status) {
@@ -3788,9 +3791,98 @@ LibraryManager.library = {
37883791
#endif
37893792
#if !MINIMAL_RUNTIME
37903793
noExitRuntime = false;
3794+
runtimeKeepaliveCounter = 0;
37913795
#endif
37923796
exit(status);
37933797
},
3798+
3799+
#if !MINIMAL_RUNTIME
3800+
$runtimeKeepaliveCounter: 0,
3801+
3802+
$keepRuntimeAlive__deps: ['$runtimeKeepaliveCounter'],
3803+
$keepRuntimeAlive: function() {
3804+
return noExitRuntime || runtimeKeepaliveCounter > 0;
3805+
},
3806+
3807+
// Callable in pthread without __proxy needed.
3808+
$runtimeKeepalivePush__sig: 'v',
3809+
$runtimeKeepalivePush__deps: ['$runtimeKeepaliveCounter'],
3810+
$runtimeKeepalivePush: function() {
3811+
runtimeKeepaliveCounter += 1;
3812+
#if RUNTIME_DEBUG
3813+
err('runtimeKeepalivePush -> counter=' + runtimeKeepaliveCounter);
3814+
#endif
3815+
},
3816+
3817+
$runtimeKeepalivePop__sig: 'v',
3818+
$runtimeKeepalivePop__deps: ['$runtimeKeepaliveCounter'],
3819+
$runtimeKeepalivePop: function() {
3820+
#if ASSERTIONS
3821+
assert(runtimeKeepaliveCounter > 0);
3822+
#endif
3823+
runtimeKeepaliveCounter -= 1;
3824+
#if RUNTIME_DEBUG
3825+
err('runtimeKeepalivePop -> counter=' + runtimeKeepaliveCounter);
3826+
#endif
3827+
},
3828+
3829+
3830+
// Used to call user callbacks from the embedder / event loop. For example
3831+
// setTimeout or any other kind of event handler that calls into user case
3832+
// needs to use this wrapper.
3833+
//
3834+
// The job of this wrapper is the handle emscripten-specfic exceptions such
3835+
// as ExitStatus and 'unwind' and prevent these from escaping to the top
3836+
// level.
3837+
$callUserCallback__deps: ['$maybeExit'],
3838+
$callUserCallback: function(func) {
3839+
if (ABORT) {
3840+
#if ASSERTIONS
3841+
err('user callback triggered after application aborted. Ignoring.');
3842+
return;
3843+
#endif
3844+
}
3845+
try {
3846+
func();
3847+
} catch (e) {
3848+
if (e instanceof ExitStatus) {
3849+
return;
3850+
} else if (e !== 'unwind') {
3851+
// And actual unexpected user-exectpion occured
3852+
if (e && typeof e === 'object' && e.stack) err('exception thrown: ' + [e, e.stack]);
3853+
throw e;
3854+
}
3855+
}
3856+
maybeExit();
3857+
},
3858+
3859+
$maybeExit__deps: ['exit'],
3860+
$maybeExit: function() {
3861+
#if RUNTIME_DEBUG
3862+
err('maybeExit: user callback done: runtimeKeepaliveCounter=' + runtimeKeepaliveCounter);
3863+
#endif
3864+
if (!keepRuntimeAlive()) {
3865+
#if RUNTIME_DEBUG
3866+
err('maybeExit: calling exit() implicitly after user callback completed: ' + EXITSTATUS);
3867+
#endif
3868+
try {
3869+
_exit(EXITSTATUS);
3870+
} catch (e) {
3871+
if (e instanceof ExitStatus) {
3872+
return;
3873+
}
3874+
throw e;
3875+
}
3876+
}
3877+
},
3878+
#else
3879+
// MINIMAL_RUNTIME doesn't support the runtimeKeepalive stuff
3880+
$runtimeKeepalivePush: function() {},
3881+
$runtimeKeepalivePop: function() {},
3882+
$callUserCallback: function(func) {
3883+
func();
3884+
},
3885+
#endif
37943886
};
37953887

37963888
function autoAddDeps(object, name) {

src/library_bootstrap.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ assert(false, "library_bootstrap.js only designed for use with BOOTSTRAPPING_STR
1313

1414
assert(!LibraryManager.library);
1515
LibraryManager.library = {
16-
$callRuntimeCallbacks: function() {}
16+
$callRuntimeCallbacks: function() {},
17+
$keepRuntimeAlive: function() { return false }
1718
};

0 commit comments

Comments
 (0)