Skip to content

Comment on Using files with browsers, in reality

Comments

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/f..., now changed in 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-rus..., discussed at https://news.ycombinator.com/item?id=27542504), or filesystem TOCTTOU (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 (workaround at 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 or https://github.com/mhogomchungu/tasks or https://blog.blackquill.cc/asynchronous-qtquick-uis-and-thei...?).

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.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.