Skip to content

Commit 77996b4

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. In addition, it doesn't always make sense to call `pthread_exit`, (e.g. in cases where you want C++ descrutors on the stack to be called, or in cases where C++ threads are being used and pthread_exit` does not exist, or at least doesn't makes sense). With this solution the thread will exit automatically when returning from the fetch callback, no explict thread exit call needed.
1 parent efede79 commit 77996b4

13 files changed

+389
-89
lines changed

emcc.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,7 +1473,13 @@ def default_setting(name, new_default):
14731473
if not shared.Settings.MINIMAL_RUNTIME:
14741474
# In non-MINIMAL_RUNTIME, the core runtime depends on these functions to be present. (In MINIMAL_RUNTIME, they are
14751475
# no longer always bundled in)
1476-
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$demangle', '$demangleAll', '$jsStackTrace', '$stackTrace']
1476+
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
1477+
'$keepRuntimeAlive',
1478+
'$demangle',
1479+
'$demangleAll',
1480+
'$jsStackTrace',
1481+
'$stackTrace'
1482+
]
14771483

14781484
if shared.Settings.FILESYSTEM:
14791485
# to flush streams on FS exit, we need to be able to call fflush
@@ -1590,7 +1596,7 @@ def include_and_export(name):
15901596
include_and_export('invokeEntryPoint')
15911597
if not shared.Settings.MINIMAL_RUNTIME:
15921598
# noExitRuntime does not apply to MINIMAL_RUNTIME.
1593-
include_and_export('getNoExitRuntime')
1599+
include_and_export('keepRuntimeAlive')
15941600

15951601
if shared.Settings.MODULARIZE:
15961602
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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3771,13 +3771,14 @@ 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() {
3775-
#if !MINIMAL_RUNTIME
3776-
noExitRuntime = true;
3777-
#endif
3777+
runtimeKeepalivePush();
37783778
throw 'unwind';
37793779
},
37803780

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

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