flagxor
Something like Dinning Philosophers is no less tricky to express + understand with an event loop, and with JS's async await probably would most cleanly be expressed in a style that mirrors a monitors/conditionvars version.
The fact that JS has added async await suggests demand for the convenience of concurrent blocking threads. While async-await manages to separate out non-async code to a degree, once an await happens, the global state can also be arbitrarily mutated no differently than with threads.
function block() { return new Promise(function(a,b) { setTimeout(a, 0); }); }
var x = 0;
(async function foo() {
x = 1;
await block();
console.log(x);
})().then();
(async function bar() {
x = 2;
})().then();
Concurrency is hard, but event loops aren't a magic wand.This is not hypothetical, consider the present. While Firefox on iOS exists, it's just a branding skin over WebKit, due to a similar flavor of security paternalism around JITing code (only bad people write self modifying code :-). If Firefox had needed to differentiate itself originally in such a market, it's doubtful it would have had much success.
A threading free-for-all may be the wrong abstraction to use for many applications, but it has the virtue of being a decent stand-in for the hardware's actual capabilities. It's also close enough to ground truth that most other abstractions can be built on top of it. Imagine how unpleasant building a browser on top of Workers + ArrayBuffer transfer would be (especially given the lousy multi-millisecond latency of postMessage in most browsers). Also, consider that while there is often loud agreement that raw threads are dangerous, after decades of research, there's little consensus on the "right" option amongst many alternatives.
SharedArrayBuffer is nearly as powerful as the proposal, but not quite. For example, while it allows writing a multi-threaded tree processing library, it would have trouble exposing an idiomatic JS API if the trees in the library live in a single SAB (as JS has no finalizers etc. to enable release of sub-allocations of the SAB). The options are either one SAB per tree (which likely performs badly), an API where JS users need to explicitly release trees when done with them, or leaking memory. With the proposal, each tree node could be represented directly as a JS object. The proposal may not be the best way to fix this problem, but we definitely still have gaps in JS concurrency.
Agreed this would be a serious undertaking, however, and not to be lightly considered.
The proposal goes a long way to make the case this can be implemented performantly, but some deep thought should go into how it would alter / constraint future optimizations in JS JITs.
(function() {
var x = '';
setTimeout(function() { setTimeout(function() { x += 'a'; }, 6); }, 4);
setTimeout(function() { setTimeout(function() { x += 'b'; }, 5); }, 5);
setTimeout(function() { console.log(x); }, 50);
})();
It's a fair point that generally I/O is the source of the bulk of the non-determinism in most JS programs. But if that I/O non-determinism is present (as it is in most useful programs), event loops aren't a panacea to avoid peril from it.Concurrency introduces another source of non-determinism. But just as good programs use care with the ideally limited amount of code responding to events, good concurrent programs use similar care with access to shared state.
I'm unsure if the strawman syntax proposed encourages that care, but it is interesting that it can at least be done without breaking basic safety + performance. With SharedArrayBuffer on the way, we'll get all the non-determinism of parallel execution peeping through to JS without a particularly JS friendly syntax, so it's at least worth thinking about if something should be added to JS.
Certainly, if they are issued from different callbacks. :-) And in relation to how they may happen to be interleaved with I/O.
Also add to the non-determinism bucket: the order in which messages arrive from separate Workers.
> Callbacks can only come back in an unexpected order if you are using I/O.
Which abounds and takes many forms both in the browser and node.js.
> A broken callback chain is not the traditional definition of "deadlock".
Indeed, but it is the same class of developer hazard at play: waiting for an event to happen than will never come. Admittedly, only a subset of dropped callback chains correspond to the strict definition of deadlock: those where a chain is involved in marking a state/resource as available.
For example: I ignore the next button press on a ui that disables the button pending a reply because I accidentally failed to update the button state when a network request failed.
* non-determinism -> order in which setTimeouts run
* race conditions -> callbacks happen in an unexpected order
* deadlock -> broken callback chain
A little hard to imagine grafting onto JS though. :-)
Would Rust/Servo have been possible without access to the raw rope?