Skip to content

Commit 9732171

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 noExitRuntime variable so that once outstanding works is complete the thread can be shutdown if needed. The specific need for this comes the use case of running fetch thread and wanting to exit the thread once the fetch completes. Without this change `pthread_exit` from a fetch callback generated an `uncaught unwind` message on the console. Also, if one wants the fetch thread to become joinable there was no way other than calling `pthread_exit` which has no parallel in the C++ threads world and has issues around calling C++ destructors on the stack. With this solution the thread will exit automatically when returning from the fetch callback.
1 parent 855df3e commit 9732171

13 files changed

+389
-87
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: 101 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,106 @@ 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+
// An actual unexpected user-exception occurred
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+
#if USE_PTHREADS
3861+
'pthread_exit',
3862+
#endif
3863+
],
3864+
$maybeExit: function() {
3865+
#if RUNTIME_DEBUG
3866+
err('maybeExit: user callback done: runtimeKeepaliveCounter=' + runtimeKeepaliveCounter);
3867+
#endif
3868+
if (!keepRuntimeAlive()) {
3869+
#if RUNTIME_DEBUG
3870+
err('maybeExit: calling exit() implicitly after user callback completed: ' + EXITSTATUS);
3871+
#endif
3872+
try {
3873+
#if USE_PTHREADS
3874+
if (ENVIRONMENT_IS_PTHREAD) _pthread_exit(EXITSTATUS);
3875+
else
3876+
#endif
3877+
_exit(EXITSTATUS);
3878+
} catch (e) {
3879+
if (e instanceof ExitStatus) {
3880+
return;
3881+
}
3882+
throw e;
3883+
}
3884+
}
3885+
},
3886+
#else
3887+
// MINIMAL_RUNTIME doesn't support the runtimeKeepalive stuff
3888+
$runtimeKeepalivePush: function() {},
3889+
$runtimeKeepalivePop: function() {},
3890+
$callUserCallback: function(func) {
3891+
func();
3892+
},
3893+
#endif
37943894
};
37953895

37963896
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)