emscripten icon indicating copy to clipboard operation
emscripten copied to clipboard

Is OPFS backend supported? how to use it?

Open ali4864 opened this issue 3 months ago • 8 comments

In Filesystem-API there is not any information about origin private file system (OPFS) but we have wasmfs_create_opfs_backend(), what is the standard way to mount a persistence path using OPFS? Is it an experimental feature?

ali4864 avatar Oct 01 '25 10:10 ali4864

I believe the backend is fully functional and used in production. @tlively would know best, and about the status of documentation for it.

kripken avatar Oct 01 '25 15:10 kripken

Yes, it is production-ready. There unfortunately are not great docs for it, but you can see how to create and mount the backend in the tests.

tlively avatar Oct 02 '25 22:10 tlively

@tlively The generated JavaScript file without -sASYNCIFY=2 argument will not work because of the promises! and by using -sASYNCIFY=2 our code will not be production-ready, indeed emcc give emcc: warning: -sASYNCIFY=2 (JSPI) is still experimental [-Wexperimental]. Can we ignore this warning?

ali4864 avatar Oct 08 '25 15:10 ali4864

I'm not sure what promises you're referring to. Are you using -sASYNCIFY=1 in your normal builds? IIRC, WasmFS doesn't support ASYNCIFY=1. @brendandahl, can you speak to how experimental the JSPI support is?

tlively avatar Oct 08 '25 17:10 tlively

I think the problem is as the follow:

parentHandle is undefined!

async function wasmfsOPFSGetOrCreateFile(parent, name, create) {
    let parentHandle = wasmfsOPFSDirectoryHandles.get(parent);
    let fileHandle;
    try {
        fileHandle = await parentHandle.getFileHandle(name, {
            create
        })
....

because the above function is called after let root = await navigator.storage.getDirectory(); while wasmfsOPFSDirectoryHandles.allocated.push(root) is not call yet!

async function __wasmfs_opfs_init_root_directory(ctx) {
    if (wasmfsOPFSDirectoryHandles.allocated.length == 1) {
        let root = await navigator.storage.getDirectory();
        wasmfsOPFSDirectoryHandles.allocated.push(root)
    }
    wasmfsOPFSProxyFinish(ctx)
}

If you check the call stack in your debugger, you will see some calls without 'await' or '.then()'.

Regarding -sASYNCIFY=1, I have tested it (I'm not sure whether I used it properly or not!) but it doesn't work.

ali4864 avatar Oct 09 '25 03:10 ali4864

There's a few tests for JSPI and OPFS, but I don't think it's seen much use so there may be bugs.

emcc give emcc: warning: -sASYNCIFY=2 (JSPI) is still experimental [-Wexperimental]. Can we ignore this warning?

You can ignore this, but I'd note not all browsers support JSPI yet.

brendandahl avatar Oct 09 '25 20:10 brendandahl

I think the problem is as the follow:

parentHandle is undefined!

Do you have a small example that reproduces the problem?

Regarding -sASYNCIFY=1, I have tested it (I'm not sure whether I used it properly or not!) but it doesn't work.

I don't think we've added support for asyncify=1. We could probably get it working, but ASYNCIFY=2 is the preferred way.

brendandahl avatar Oct 09 '25 20:10 brendandahl

Do you have a small example that reproduces the problem?

A sample for reproducing the problem:

test.cpp

#include <iostream>
#include <fstream>
#include <emscripten/wasmfs.h>

using namespace std;

int main(int argc, char const *argv[])
{
    backend_t backend = wasmfs_create_opfs_backend();

    wasmfs_create_directory("/opfs", 0777, backend);

    ofstream output("/opfs/test.txt");

    output << "Hello World!" << endl;
    output.flush();
    output.close();
}

build command: emcc test.cpp -o test.js -sWASMFS -O3

to import test.js use either <script src="test.js"></script> or importScripts("test.js") inside a worker.

test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
<script src="test.js"></script>
<script>
    // to test with a worker
    // var worker = new Worker("worker.js");
</script>
</html>

worker.js (if you test with worker)

importScripts("test.js")

ali4864 avatar Oct 10 '25 04:10 ali4864