Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: allow writing files with ArrayBuffers #46490

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

SRHerzog
Copy link
Contributor

@SRHerzog SRHerzog commented Feb 3, 2023

Extended most file writing functions to accept ArrayBuffers rather than only strings, buffers, or data views. Does not include functions that accept arrays of dataviews, such as writev.

Fixes: #42228

@nodejs-github-bot nodejs-github-bot added fs Issues and PRs related to the fs subsystem / file system. needs-ci PRs that need a full CI run. labels Feb 3, 2023
Copy link
Contributor

@aduh95 aduh95 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need a documentation update. Could you avoid creating a Uint8Array? It seems like ArrayBuffers could probably be passed directly to the C++ bindings and be more efficient.

lib/internal/fs/promises.js Outdated Show resolved Hide resolved
@addaleax
Copy link
Member

addaleax commented Feb 4, 2023

Could you avoid creating a Uint8Array? It seems like ArrayBuffers could probably be passed directly to the C++ bindings and be more efficient.

I would imagine both of these cases to come out roughly the same, tbh. Creating an Uint8Array for an existing ArrayBuffer is not a complex operation, and it’s also not really less efficient to unwrap an Uint8Array on the C++ side than an ArrayBuffer, so I think it’s fine to have readability as the main concern here.

@SRHerzog
Copy link
Contributor Author

SRHerzog commented Feb 6, 2023

I've just tested passing an ArrayBuffer directly to the C++ binding (changed the validation in filehandle.write to accept either a typed array or an ArrayBuffer), and it causes the program to crash.

async function validateWrite() {
  const filePathForHandle = path.resolve(tmpDir, 'tmp-write.txt');
  const fileHandle = await open(filePathForHandle, 'w+');
  const buffer = Buffer.from('Hello world'.repeat(100), 'utf8');

  await fileHandle.write(buffer.buffer, 0, buffer.length);
  const readFileData = fs.readFileSync(filePathForHandle);
  assert.deepStrictEqual(buffer, readFileData);

  await fileHandle.close();
}
../../src/node_file.cc:2026:void node::fs::WriteBuffer(const FunctionCallbackInfo<v8::Value> &): Assertion `Buffer::HasInstance(args[1])' failed.
 1: 0x102fcc05c node::Abort() [/projects/node/node/out/Release/node]
 2: 0x102fcbe90 node::AppendExceptionLine(node::Environment*, v8::Local<v8::Value>, v8::Local<v8::Message>, node::ErrorHandlingMode) [/projects/node/node/out/Release/node]
 3: 0x102fdfffc node::fs::WriteBuffer(v8::FunctionCallbackInfo<v8::Value> const&) [/projects/node/node/out/Release/node]
 ... 30 more lines in stack trace
--- CRASHED (Signal: 6) ---

@SRHerzog SRHerzog marked this pull request as ready for review February 10, 2023 19:50
@jasnell
Copy link
Member

jasnell commented Feb 11, 2023

Should this also allow for SharedArrayBuffer instances?

Copy link
Contributor

@LiviaMedeiros LiviaMedeiros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a great thing to have, thank you!

lib/internal/fs/promises.js Outdated Show resolved Hide resolved
lib/internal/fs/utils.js Outdated Show resolved Hide resolved
lib/internal/fs/promises.js Outdated Show resolved Hide resolved
Extended most file writing functions to accept ArrayBuffers rather
than only strings, buffers, or data views. Does not include
functions that accept arrays of dataviews, such as writev.

Fixes: nodejs#42228
lib/fs.js Outdated Show resolved Hide resolved
lib/internal/fs/utils.js Outdated Show resolved Hide resolved
@LiviaMedeiros LiviaMedeiros added semver-minor PRs that contain new features and should be released in the next minor version. request-ci Add this label to start a Jenkins CI on a PR. labels Mar 24, 2023
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Mar 24, 2023
Copy link
Contributor

@LiviaMedeiros LiviaMedeiros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code LGTM!
If there are no objections, it seems to be a semver-minor PRs that contain new features and should be released in the next minor version. change.

Support of ArrayBuffer and SharedArrayBuffer should be added as changelog entries in documentation, e.g. here:

node/doc/api/fs.md

Lines 655 to 662 in fdad5c2

<!-- YAML
added: v10.0.0
changes:
- version: v14.0.0
pr-url: https://github.com/nodejs/node/pull/31030
description: The `buffer` parameter won't coerce unsupported input to
buffers anymore.
-->

@@ -661,7 +661,7 @@ changes:
buffers anymore.
-->

* `buffer` {Buffer|TypedArray|DataView}
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support of SharedArrayBuffer should be documented

Suggested change
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer}
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|SharedArrayBuffer}

@@ -2163,7 +2169,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) {
/**
* Asynchronously writes data to the file.
* @param {string | Buffer | URL | number} path
* @param {string | Buffer | TypedArray | DataView} data
* @param {string | Buffer | TypedArray | DataView | ArrayBuffer} data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param {string | Buffer | TypedArray | DataView | ArrayBuffer} data
* @param {string | Buffer | TypedArray | DataView | ArrayBuffer | SharedArrayBuffer} data

@@ -887,7 +909,7 @@ const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => {
if (typeof buffer !== 'string') {
throw new ERR_INVALID_ARG_TYPE(
name,
['string', 'Buffer', 'TypedArray', 'DataView'],
['string', 'Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
['string', 'Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],
['string', 'Buffer', 'TypedArray', 'DataView', 'ArrayBuffer', 'SharedArrayBuffer'],

@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

@@ -819,7 +821,8 @@ function write(fd, buffer, offsetOrOptions, length, position, callback) {
fd = getValidatedFd(fd);

let offset = offsetOrOptions;
if (isArrayBufferView(buffer)) {
const bufferToWrite = isArrayBuffer(buffer) || isSharedArrayBuffer(buffer) ? new Uint8Array(buffer) : buffer;
Copy link
Contributor

@zbjornson zbjornson Aug 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArrayBuffers can have arbitrary length, but TypedArrays are limited to kMaxLength, so this line can fail:

> k = require("buffer").kMaxLength
4294967296
> ab = new ArrayBuffer(k * 2)
ArrayBuffer { (detached), byteLength: 8589934592 }
> new Uint8Array(ab)
Uncaught RangeError: Start offset undefined is outside the bounds of the buffer
    at new Uint8Array (<anonymous>)

That's a reason to consider passing the ArrayBuffer into C++ and unwrapping it there, or with a bit more waste, validating the int32 length earlier (currently line 845R) and making this ~new Uint8Array(buffer, offset, length).

(uv_fs_write is limited to int32 size, but the offset parameter is int64 and allows calling fs.write() in a loop to write out larger files. See libuv/libuv#3360 and linked issues.)


Also, V8 is removing the kMaxLength limit sometime soon, which would make this a non-issue. https://bugs.chromium.org/p/v8/issues/detail?id=4153

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fs Issues and PRs related to the fs subsystem / file system. needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow ArrayBuffer as argument to writeFile and friends
7 participants