3 ms·
As a native programmer who meddles with the horrors of thread safety and & and &mut, and occasionally dabbles in high-level (JS/Dart) asynchronity, async functi
by nyanpasu64 5y ago
As a native programmer who meddles with the horrors of thread safety and & and &mut, and occasionally dabbles in high-level (JS/Dart) asynchronity, async functions fill me with much of the same fear and caution. await looks like merely a nonblocking function call, but means arbitrary code executes and can and will mutate arbitrary state under your feet. Shared state across coroutines is nearly as dangerous as shared state across threads, and (I conjecture) far more pervasive.
In the code in question (https://web.archive.org/web/20220113153505/https://web.dev/file-system-access/#drag-and-drop-integration https://web.archive.org/web/20220113153505/https://web.dev/f..., now changed in https://web.dev/file-system-access/#drag-and-drop-integration https://web.dev/file-system-access/#drag-and-drop-integratio...):
elem.addEventListener('drop', async (e) => {
// Prevent navigation.
e.preventDefault();
// Process all of the items.
for (const item of e.dataTransfer.items) {
// Careful: `kind` will be 'file' for both file
// _and_ directory entries.
if (item.kind === 'file') {
const entry = await item.getAsFileSystemHandle();
if (entry.kind === 'directory') {
handleDirectoryEntry(entry);
} else {
handleFileEntry(entry);
}
}
}
});
I probably wouldn't have guessed that `e.dataTransfer.items` gets cleared at the first await (since I'm not a proficient web developer), but I would've been extremely wary of this code in general. Additionally (not tied to async-await but race conditions in general), is `item.getAsFileSystemHandle()` a TOCTTOU vulnerability where the type of an item can change between folders and files and symlinks etc., while this code is running?
Rust's & vs. &mut system largely eliminates shared state hazards in both threading and asynchronity (&mut is exclusive/unaliased and can't be mutated by other threads or event loop jobs, and & is difficult and unidiomatic to mutate), though it doesn't solve async cancellation errors (https://carllerche.com/2021/06/17/six-ways-to-make-async-rust-easier/ https://carllerche.com/2021/06/17/six-ways-to-make-async-rus..., discussed at https://news.ycombinator.com/item?id=27542504 https://news.ycombinator.com/item?id=27542504), or filesystem TOCTTOU (https://blog.rust-lang.org/2022/01/20/cve-2022-21658.html https://blog.rust-lang.org/2022/01/20/cve-2022-21658.html as well as user code).
Qt event loop reentrancy is fun(tm) as well. It looks like a blocking call, but spawns a nested event loop which can do anything (but rarely enough to lull you into a false sense of complacency), resulting in segfaults like https://github.com/Nheko-Reborn/nheko/issues/656 https://github.com/Nheko-Reborn/nheko/issues/656 (workaround at https://github.com/Nheko-Reborn/nheko/commit/570d00b000bd558592af317746fa3639fb022fa4 https://github.com/Nheko-Reborn/nheko/commit/570d00b000bd558..., I didn't look into it). And Qt lacks "easy" await syntax and a framework based on calling red functions (though I didn't look into C++20 coroutines yet, perhaps https://www.qt.io/blog/asynchronous-apis-in-qt-6 https://www.qt.io/blog/asynchronous-apis-in-qt-6 or https://github.com/mhogomchungu/tasks https://github.com/mhogomchungu/tasks or https://blog.blackquill.cc/asynchronous-qtquick-uis-and-their-implementation-the-toolbox#thennables https://blog.blackquill.cc/asynchronous-qtquick-uis-and-thei...?).
- eyelidlessness 5y ago> Shared state across coroutines is nearly as dangerous as shared state across threads, and (I conjecture) far more pervasive. The danger is roughly equal, conceptually shared concurrent state has all of the same corruption risks regardless of additional concurrency considerations (fault tolerance, CAP, resource management, etc). The latter is probably harder to quantify, but I suspect the answer very closely hews to the age of the overall codebase and the exposure of contributors to different approaches. The reasons I suspect that: 1. The event loop/cooperative concurrency model isn’t restricted to JS, but it’s so heavily weighted towards JS by volume that those trends will dominate any quantitative change. 2. The convergence of rest/spread syntax, async/await syntax, and React: all of these arrived or became widely adopted around the same time, and promoted a more functional style even if it wasn’t recognized or called that. 3. Older codebases, even if they aggressively adapt to those idiomatic changes, retain and even go on producing more shared concurrent state because the risk is hard to know and untangling it is hard to prioritize. (I live this every day in projects I’ve inherited, and I’d expect the same with projects of similar age if they’d lived this long.) I would bet these temporal assumptions roughly line up for other environments with similar concurrency models and similar evolutionary paths, depending mostly on how rapidly they churn.