diff --git a/benchmark/vfs/bench-fs-dispatch.js b/benchmark/vfs/bench-fs-dispatch.js new file mode 100644 index 00000000000000..4dc3a75111b8ae --- /dev/null +++ b/benchmark/vfs/bench-fs-dispatch.js @@ -0,0 +1,56 @@ +'use strict'; + +// Measures VFS dispatch overhead on fs ops against real-filesystem paths +// (paths no VFS claims). Layers=0 means the VFS module is never loaded. + +const common = require('../common.js'); +const fs = require('fs'); +const path = require('path'); + +const bench = common.createBenchmark(main, { + n: [3e5], + op: ['statSync', 'existsSync', 'accessSync', 'readFileSync'], + layers: [0, 1, 2, 5, 10], +}, { + flags: ['--experimental-vfs', '--no-warnings'], +}); + +function mountLayers(count) { + const vfs = require('node:vfs'); + const handles = []; + for (let i = 0; i < count; i++) { + const v = vfs.create(); + v.mount(); + handles.push(v); + } + return handles; +} + +function main({ n, op, layers }) { + const handles = layers > 0 ? mountLayers(layers) : null; + + const target = layers === 0 ? __filename : path.join(__dirname, path.basename(__filename)); + + for (let i = 0; i < 1000; i++) { + if (op === 'statSync') fs.statSync(target); + else if (op === 'existsSync') fs.existsSync(target); + else if (op === 'accessSync') fs.accessSync(target); + else fs.readFileSync(target); + } + + bench.start(); + if (op === 'statSync') { + for (let i = 0; i < n; i++) fs.statSync(target); + } else if (op === 'existsSync') { + for (let i = 0; i < n; i++) fs.existsSync(target); + } else if (op === 'accessSync') { + for (let i = 0; i < n; i++) fs.accessSync(target); + } else { + for (let i = 0; i < n; i++) fs.readFileSync(target); + } + bench.end(n); + + if (handles) { + for (const v of handles) v.unmount(); + } +} diff --git a/doc/api/vfs.md b/doc/api/vfs.md index 90b8e9c303125a..b089244f80f0f6 100644 --- a/doc/api/vfs.md +++ b/doc/api/vfs.md @@ -61,6 +61,11 @@ callback-based, and promise-based file system methods that mirror the shape of the [`node:fs`][] API. All paths are POSIX-style and absolute (starting with `/`). +By default, the file tree is private to the VFS instance. To expose +it through the global `node:fs` module, `require()`, and `import`, +call [`vfs.mount()`][]; call [`vfs.unmount()`][] (or rely on a +`using` declaration) to detach again. + ## `vfs.create([provider][, options])` + +* Returns: {string} The absolute mount point. + +Mounts the virtual file system and returns the resulting mount point. +After mounting, files in the VFS can be accessed through the +`node:fs` module and resolved through `require()` and `import` +using paths under the returned mount point. + +Mount points always live inside a reserved namespace, +`${os.devNull}/vfs/`. Because [`os.devNull`][] is a +character device (POSIX) or a device-namespace path (Windows) that +cannot have child file system entries, no real file-system path can +exist under this namespace: virtual paths never conflate with (or +shadow) real paths, and the layer that owns a path is visible in the +path itself. + +```cjs +const vfs = require('node:vfs'); +const fs = require('node:fs'); + +const myVfs = vfs.create(); +myVfs.writeFileSync('/data.txt', 'Hello'); +const mountPoint = myVfs.mount(); +// e.g. '/dev/null/vfs/0' + +fs.readFileSync(`${mountPoint}/data.txt`, 'utf8'); // 'Hello' +``` + +Each `VirtualFileSystem` instance may be mounted at most once at a +time. Attempting to mount an already-mounted instance throws +`ERR_INVALID_STATE`. Because each instance mounts inside its own +per-layer namespace, mounts from different instances can never +overlap. + +The VFS supports the [Explicit Resource Management][] proposal. Use +a `using` declaration to unmount automatically when leaving scope: + +```cjs +const vfs = require('node:vfs'); +const fs = require('node:fs'); + +let mountPoint; +{ + using myVfs = vfs.create(); + myVfs.writeFileSync('/data.txt', 'Hello'); + mountPoint = myVfs.mount(); + + fs.readFileSync(`${mountPoint}/data.txt`, 'utf8'); // 'Hello' +} // VFS is automatically unmounted here + +fs.existsSync(`${mountPoint}/data.txt`); // false +``` + +### `vfs.unmount()` + + + +Unmounts the virtual file system. After unmounting, virtual files +are no longer reachable through `node:fs`, `require()`, or `import`. +The same instance may be mounted again by calling `mount()`. + +This method is idempotent: calling `unmount()` on a VFS that is not +currently mounted has no effect. + +### `vfs.mounted` + + + +* {boolean} + +`true` while the VFS is mounted; `false` otherwise. + +### `vfs.mountPoint` + + + +* {string | null} + +The current mount point as an absolute string (the value returned by +the last [`vfs.mount()`][] call), or `null` when the VFS is not +mounted. + ### `vfs.provider`