Breaking change: sync methods for AccessHandles

Breaking change: sync methods for AccessHandles

The origin private file system provides access to a special kind of file that is highly optimized for performance, for example, by offering in-place and exclusive write access to a file’s content. Developers can get access to such files by calling createSyncAccessHandle(), which is a method exposed on FileSystemFileHandle objects. This call results in a FileSystemSyncAccessHandle.

The createSyncAccessHandle() method and therefore FileSystemSyncAccessHandle is only exposed in Workers. It is not available in the main thread.

FileSystemSyncAccessHandle is a file primitive that provides performant access to local files. One of its main use cases is applications porting C/C++ code to Wasm; however, asynchronous calls are not fully supported on Wasm yet, and using the Asyncify library as an alternative has substantially degraded performance. Making all the methods of the FileSystemSyncAccessHandle synchronous matches the synchronous, POSIX-like file API Wasm-based application expect; making the API more ergonomic while bringing substantial performance gains.

What’s new?

FileSystemSyncAccessHandle exposes the following methods that used to be asynchronous, but that are synchronous as of Chromium 108.

  • truncate(newSize): Resizes the file associated with the access handle to be newSize bytes long. If newSize is larger than the current file size, it pads the file with null bytes; otherwise it truncates the file.
  • getSize(): Returns the size of the file associated with the access handle in bytes.
  • flush(): Ensures that the contents of the file associated with the access handle contain all the modifications done through write().
  • close(): Flushes the access handle and then closes it. Closing an access handle disables any further operations on it and releases the lock on the entry associated with the access handle.
// In a `Worker`:
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('test', { create: true });
// `createSyncAccessHandle()` is still async.
const accessHandle = await fileHandle.createSyncAccessHandle();
// Both `read()` and `write()` were sync before.
accessHandle.read(/* ... */);
accessHandle.write(/* ... */);

// New: synchronous as of Chromium 108.
console.log(accessHandle.getSize());
accessHandle.truncate(123);
accessHandle.flush();
accessHandle.close();

What do I need to do?

Note that changing methods from asynchronous to synchronous is a web-exposed change with potential breakage. While using await in synchronous methods is a no-op, any use of Promise.then() will break. If you chain a then() call on the result of any of the previously asynchronous and now synchronous methods, you need to change your code.

// (✅) This won't break, but you better remove the superfluous `await`:
await accessHandle.flush();
// ✅ Correct:
accessHandle.flush();
// ⛔️ This will break, and you need to restructure your code:
accessHandle.flush().then(/* Follow-up code */);
// ✅ Correct:
accessHandle.flush();
/* Follow-up code */

This post is also available in: Breaking change: sync methods for AccessHandlesEnglish