webr
webr copied to clipboard
FS.mount() throws an error when trying to mount local files with WORKERFS
Following the example from https://docs.r-wasm.org/webr/latest/mounting.html#javascript-api
Here is a reprex.
Step 1, using the ghcr.io/r-wasm/webr image :
docker run -it -v /Users/colin/webr-mount-reprex:/root/output ghcr.io/r-wasm/webr R
> dir.create("test")
> setwd("test")
> write.csv(iris, "iris.csv")
> rwasm::file_packager(".", out_dir = ".", out_name = "iris")
Packaging: iris.data
> list.files()
[1] "iris.csv" "iris.data" "iris.js.metadata"
> file.copy("iris.data", "/root/output/iris.data")
[1] TRUE
> file.copy("iris.js.metadata", "/root/output/iris.js.metadata")
[1] TRUE
Step 2, move to a node js script and try to load from disk
const fs = require('fs');
const path = require('path');
const { WebR } = require('webr');
const webR = new WebR();
(async () => {
await webR.init();
const data = new Blob(
fs.readFileSync(
path.join(__dirname, 'iris.data')
)
);
console.log(data)
const metadata = JSON.parse(
fs.readFileSync(
path.join(__dirname, 'iris.js.metadata')
)
);
console.log(metadata)
await webR.FS.mkdir('/data');
const options = {
packages: [{
blob: data,
metadata: metadata,
}],
}
await webR.FS.mount("WORKERFS", options, '/data');
})();
Even if both data and metadata are console.loged, the process throws an error :
node index.js
Blob { size: 10727, type: '' }
{
files: [ { filename: '/iris.csv', start: 0, end: 4821 } ],
remote_package_size: 4821
}
node:internal/process/promises:288
triggerUncaughtException(err, true /* fromPromise */);
^
We [RuntimeError]: unreachable
at wasm:/wasm/0169de02:wasm-function[4726]:0x2ec63b
at abort (/Users/colin/webr-mount-reprex/node_modules/webr/dist/R.bin.js:1846:31535)
at assert (/Users/colin/webr-mount-reprex/node_modules/webr/dist/R.bin.js:1846:28223)
at Object.mount (/Users/colin/webr-mount-reprex/node_modules/webr/dist/R.bin.js:1846:57014)
at Object.mount (/Users/colin/webr-mount-reprex/node_modules/webr/dist/R.bin.js:1846:66786)
at SharedBufferChannelWorker.dispatch (/Users/colin/webr-mount-reprex/node_modules/webr/dist/webr-worker.js:4218:24)
at SharedBufferChannelWorker.inputOrDispatch (/Users/colin/webr-mount-reprex/node_modules/webr/dist/webr-worker.js:2613:37)
at Object.readConsole (/Users/colin/webr-mount-reprex/node_modules/webr/dist/webr-worker.js:4821:19)
at 1105043 (/Users/colin/webr-mount-reprex/node_modules/webr/dist/R.bin.js:1846:34821)
at runEmAsmFunction (/Users/colin/webr-mount-reprex/node_modules/webr/dist/R.bin.js:1846:318358)
Node.js v18.17.1
I've tried using
const options = {
packages: [{
blob: await data,
metadata: await metadata,
}],
}
But get the same result
Here is a reprex = https://github.com/ColinFay/webr-mount-reprex
Same happens if I try to reproduce the code from the documentation, using fetch :
const { WebR } = require('webr');
const webR = new WebR();
(async () => {
await webR.init();
// Create mountpoint
await webR.FS.mkdir('/data')
// Download image data
const data = await fetch('https://raw.githubusercontent.com/ColinFay/webr-mount-reprex/main/iris.data');
const metadata = await fetch('https://raw.githubusercontent.com/ColinFay/webr-mount-reprex/main/iris.js.metadata');
// Mount image data
const options = {
packages: [{
blob: await data.blob(),
metadata: await metadata.json(),
}],
}
await webR.FS.mount("WORKERFS", options, '/data');
})();
node:internal/process/promises:288
triggerUncaughtException(err, true /* fromPromise */);
^
We [RuntimeError]: unreachable
at wasm://wasm/0169de02:wasm-function[4726]:0x2ec63b
Thank you for reporting this and for the example, I can reproduce the bug at my end.
It looks like the issue is specific to using the WORKERFS Emscripten filesystem type under Node.js. The same issue does not seem to occur when running webR in a web browser, which is the use case those examples in the documentation are largely targeting. I will investigate further and report back.
In the meantime, I know it might not be quite what you had in mind, but you should be able to use the alternative NODEFS filesystem type to mount local directories directly without having to package the contents of the directory as an Emscripten filesystem image.
First, write the data you're interested in to disk in a new directory,
$ mkdir data
$ Rscript -e 'write.csv(iris, "data/iris.csv")'
Then, mount the new directory with NODEFS in index.js:
const { WebR } = require('webr');
const webR = new WebR();
(async () => {
await webR.init();
await webR.FS.mkdir('/data');
await webR.FS.mount("NODEFS", { root: './data' }, '/data');
await webR.evalRVoid("print(head(read.csv('/data/iris.csv')))")
process.exit(1);
})();
$ node index.js
X Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 1 5.1 3.5 1.4 0.2 setosa
2 2 4.9 3.0 1.4 0.2 setosa
3 3 4.7 3.2 1.3 0.2 setosa
4 4 4.6 3.1 1.5 0.2 setosa
5 5 5.0 3.6 1.4 0.2 setosa
6 6 5.4 3.9 1.7 0.4 setosa
Yeah this is what I've been doing so far (see https://colinfay.me/rethinking-packages-and-functions-preloading-in-webr-0.2.2/ for ex) but I want to package some stuff into a node module, and I feel like it would be better to upload the .data and .js.metadata files to npm than to upload a whole folder with many files :)
As of https://github.com/r-wasm/webr/commit/4655e9602c6eb1da8ded29765617e93f0a7e4e3a, to be included in the next release of webR, mounting WORKERFS images under Node.js should now work.
Note that in your original test script, the argument of new Blob(...) should be in the form of an array:
const data = new Blob([
fs.readFileSync(
path.join(__dirname, 'iris.data')
)
]);
It should also work when passing the Buffer returned by fs.readFileSync() directly for the blob key.
Here is essentially the test script I've been using. Once we cut a new release of webR and it hits npm you should be able to try it out for yourself.
const fs = require('fs');
const path = require('path');
const { WebR } = require('webr');
const webR = new WebR();
(async () => {
await webR.init();
const data = new Blob([
fs.readFileSync(
path.join(__dirname, 'iris.data')
)
])
const metadata = JSON.parse(
fs.readFileSync(
path.join(__dirname, 'iris.js.metadata')
)
);
await webR.FS.mkdir('/data');
const options = {
packages: [{
blob: data,
metadata: metadata,
}],
}
await webR.FS.mount("WORKERFS", options, '/data');
await webR.evalRVoid("print(file.info('/data/iris.csv'))")
await webR.evalRVoid("print(head(read.csv('/data/iris.csv')))")
process.exit(1);
})();