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: move getValidMode to c++ for better performance #49970

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions benchmark/fs/bench-accessSync.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ const tmpfile = tmpdir.resolve(`.existing-file-${process.pid}`);
fs.writeFileSync(tmpfile, 'this-is-for-a-benchmark', 'utf8');

const bench = common.createBenchmark(main, {
type: ['existing', 'non-existing', 'non-flat-existing'],
type: ['existing', 'non-existing', 'non-flat-existing', 'invalid-mode'],
n: [1e5],
});

function main({ n, type }) {
let path;
let path, mode;

switch (type) {
case 'existing':
Expand All @@ -26,14 +26,18 @@ function main({ n, type }) {
case 'non-existing':
path = tmpdir.resolve(`.non-existing-file-${process.pid}`);
break;
case 'invalid-mode':
path = __filename;
mode = -1;
break;
default:
new Error('Invalid type');
}

bench.start();
for (let i = 0; i < n; i++) {
try {
fs.accessSync(path);
fs.accessSync(path, mode);
} catch {
// do nothing
}
Expand Down
12 changes: 8 additions & 4 deletions benchmark/fs/bench-copyFileSync.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ const tmpdir = require('../../test/common/tmpdir');
tmpdir.refresh();

const bench = common.createBenchmark(main, {
type: ['invalid', 'valid'],
type: ['invalid-mode', 'invalid-path', 'valid'],
n: [1e4],
});

function main({ n, type }) {
tmpdir.refresh();
const dest = tmpdir.resolve(`copy-file-bench-${process.pid}`);
let path;
let path, mode;

switch (type) {
case 'invalid':
case 'invalid-path':
path = tmpdir.resolve(`.existing-file-${process.pid}`);
break;
case 'invalid-mode':
path = __filename;
mode = -1;
break;
case 'valid':
path = __filename;
break;
Expand All @@ -28,7 +32,7 @@ function main({ n, type }) {
bench.start();
for (let i = 0; i < n; i++) {
try {
fs.copyFileSync(path, dest);
fs.copyFileSync(path, dest, mode);
} catch {
// do nothing
}
Expand Down
6 changes: 1 addition & 5 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ const {
getOptions,
getValidatedFd,
getValidatedPath,
getValidMode,
handleErrorFromBinding,
possiblyTransformPath,
preprocessSymlinkDestination,
Expand Down Expand Up @@ -226,7 +225,6 @@ function access(path, mode, callback) {
}

path = getValidatedPath(path);
mode = getValidMode(mode, 'access');
callback = makeCallback(callback);

const req = new FSReqCallback();
Expand All @@ -243,7 +241,6 @@ function access(path, mode, callback) {
*/
function accessSync(path, mode) {
path = getValidatedPath(path);
mode = getValidMode(mode, 'access');

binding.access(pathModule.toNamespacedPath(path), mode);
}
Expand Down Expand Up @@ -2962,7 +2959,6 @@ function copyFile(src, dest, mode, callback) {

src = pathModule._makeLong(src);
dest = pathModule._makeLong(dest);
mode = getValidMode(mode, 'copyFile');
callback = makeCallback(callback);

const req = new FSReqCallback();
Expand All @@ -2985,7 +2981,7 @@ function copyFileSync(src, dest, mode) {
binding.copyFile(
pathModule.toNamespacedPath(src),
pathModule.toNamespacedPath(dest),
getValidMode(mode, 'copyFile'),
mode,
);
}

Expand Down
3 changes: 0 additions & 3 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ const {
getStatFsFromBinding,
getStatsFromBinding,
getValidatedPath,
getValidMode,
preprocessSymlinkDestination,
stringToFlags,
stringToSymlinkType,
Expand Down Expand Up @@ -559,7 +558,6 @@ async function readFileHandle(filehandle, options) {
async function access(path, mode = F_OK) {
path = getValidatedPath(path);

mode = getValidMode(mode, 'access');
return binding.access(pathModule.toNamespacedPath(path), mode,
kUsePromises);
}
Expand All @@ -574,7 +572,6 @@ async function cp(src, dest, options) {
async function copyFile(src, dest, mode) {
src = getValidatedPath(src, 'src');
dest = getValidatedPath(dest, 'dest');
mode = getValidMode(mode, 'copyFile');
return binding.copyFile(pathModule.toNamespacedPath(src),
pathModule.toNamespacedPath(dest),
mode,
Expand Down
49 changes: 0 additions & 49 deletions lib/internal/fs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const {
FunctionPrototypeCall,
Number,
NumberIsFinite,
MathMin,
MathRound,
ObjectIs,
ObjectSetPrototypeOf,
Expand Down Expand Up @@ -62,17 +61,9 @@ const {
const pathModule = require('path');
const kType = Symbol('type');
const kStats = Symbol('stats');
const assert = require('internal/assert');

const {
fs: {
F_OK = 0,
W_OK = 0,
R_OK = 0,
X_OK = 0,
COPYFILE_EXCL,
COPYFILE_FICLONE,
COPYFILE_FICLONE_FORCE,
O_APPEND,
O_CREAT,
O_EXCL,
Expand Down Expand Up @@ -107,26 +98,6 @@ const {
},
} = internalBinding('constants');

// The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be
// available on specific systems. They can be used in combination as well
// (F_OK | R_OK | W_OK | X_OK).
const kMinimumAccessMode = MathMin(F_OK, W_OK, R_OK, X_OK);
const kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK;

const kDefaultCopyMode = 0;
// The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or
// COPYFILE_FICLONE_FORCE. They can be used in combination as well
// (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE).
const kMinimumCopyMode = MathMin(
kDefaultCopyMode,
COPYFILE_EXCL,
COPYFILE_FICLONE,
COPYFILE_FICLONE_FORCE,
);
const kMaximumCopyMode = COPYFILE_EXCL |
COPYFILE_FICLONE |
COPYFILE_FICLONE_FORCE;

// Most platforms don't allow reads or writes >= 2 GiB.
// See https://github.com/libuv/libuv/pull/1501.
const kIoMaxLength = 2 ** 31 - 1;
Expand Down Expand Up @@ -795,7 +766,6 @@ const validateCpOptions = hideStackFrames((options) => {
validateBoolean(options.preserveTimestamps, 'options.preserveTimestamps');
validateBoolean(options.recursive, 'options.recursive');
validateBoolean(options.verbatimSymlinks, 'options.verbatimSymlinks');
options.mode = getValidMode(options.mode, 'copyFile');
if (options.dereference === true && options.verbatimSymlinks === true) {
throw new ERR_INCOMPATIBLE_OPTION_PAIR('dereference', 'verbatimSymlinks');
}
Expand Down Expand Up @@ -888,24 +858,6 @@ const validateRmdirOptions = hideStackFrames(
return options;
});

const getValidMode = hideStackFrames((mode, type) => {
let min = kMinimumAccessMode;
let max = kMaximumAccessMode;
let def = F_OK;
if (type === 'copyFile') {
min = kMinimumCopyMode;
max = kMaximumCopyMode;
def = mode || kDefaultCopyMode;
} else {
assert(type === 'access');
}
if (mode == null) {
return def;
}
validateInteger(mode, 'mode', min, max);
return mode;
});

const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => {
if (typeof buffer !== 'string') {
throw new ERR_INVALID_ARG_TYPE(
Expand Down Expand Up @@ -949,7 +901,6 @@ module.exports = {
getOptions,
getValidatedFd,
getValidatedPath,
getValidMode,
handleErrorFromBinding,
nullCheck,
possiblyTransformPath,
Expand Down
94 changes: 83 additions & 11 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ using v8::HandleScope;
using v8::Int32;
using v8::Integer;
using v8::Isolate;
using v8::Just;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
Expand All @@ -89,6 +90,31 @@ constexpr char kPathSeparator = '/';
const char* const kPathSeparator = "\\/";
#endif

// F_OK etc. constants
#ifdef _WIN32
#include "uv.h"
#else
#include <unistd.h>
#endif

// The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be
// available on specific systems. They can be used in combination as well
// (F_OK | R_OK | W_OK | X_OK).
constexpr int kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK;
constexpr int kMinimumAccessMode = std::min({F_OK, W_OK, R_OK, X_OK});

constexpr int kDefaultCopyMode = 0;
// The copy modes can be any of UV_FS_COPYFILE_EXCL, UV_FS_COPYFILE_FICLONE or
// UV_FS_COPYFILE_FICLONE_FORCE. They can be used in combination as well
// (US_FS_COPYFILE_EXCL | US_FS_COPYFILE_FICLONE |
// US_FS_COPYFILE_FICLONE_FORCE).
constexpr int kMinimumCopyMode = std::min({kDefaultCopyMode,
UV_FS_COPYFILE_EXCL,
UV_FS_COPYFILE_FICLONE,
UV_FS_COPYFILE_FICLONE_FORCE});
constexpr int kMaximumCopyMode =
UV_FS_COPYFILE_EXCL | UV_FS_COPYFILE_FICLONE | UV_FS_COPYFILE_FICLONE_FORCE;

std::string Basename(const std::string& str, const std::string& extension) {
// Remove everything leading up to and including the final path separator.
std::string::size_type pos = str.find_last_of(kPathSeparator);
Expand Down Expand Up @@ -854,6 +880,41 @@ void FromNamespacedPath(std::string* path) {
#endif
}

static inline Maybe<int> GetValidMode(Environment* env,
Local<Value> mode_v,
uv_fs_type type) {
if (!mode_v->IsInt32() && !mode_v->IsNullOrUndefined()) {
THROW_ERR_INVALID_ARG_TYPE(env, "mode must be int32 or null/undefined");
return Nothing<int>();
}

int min = kMinimumAccessMode;
int max = kMaximumAccessMode;
int def = F_OK;

CHECK(type == UV_FS_ACCESS || type == UV_FS_COPYFILE);

if (type == UV_FS_COPYFILE) {
min = kMinimumCopyMode;
max = kMaximumCopyMode;
def = mode_v->IsNullOrUndefined() ? kDefaultCopyMode
: mode_v.As<Int32>()->Value();
}

if (mode_v->IsNullOrUndefined()) {
return Just(def);
}

const int mode = mode_v.As<Int32>()->Value();
if (mode < min || mode > max) {
THROW_ERR_OUT_OF_RANGE(
env, "mode is out of range: >= %d && <= %d", min, max);
return Nothing<int>();
}

return Just(mode);
}
andremralves marked this conversation as resolved.
Show resolved Hide resolved

void AfterMkdirp(uv_fs_t* req) {
FSReqBase* req_wrap = FSReqBase::from_req(req);
FSReqAfterScope after(req_wrap, req);
Expand Down Expand Up @@ -973,8 +1034,9 @@ void Access(const FunctionCallbackInfo<Value>& args) {
const int argc = args.Length();
CHECK_GE(argc, 2);

CHECK(args[1]->IsInt32());
int mode = args[1].As<Int32>()->Value();
Maybe<int> maybe_mode = GetValidMode(env, args[1], UV_FS_ACCESS);
int mode;
if (!maybe_mode.To(&mode)) return;

BufferValue path(isolate, args[0]);
CHECK_NOT_NULL(*path);
Expand Down Expand Up @@ -2086,6 +2148,10 @@ static void CopyFile(const FunctionCallbackInfo<Value>& args) {
const int argc = args.Length();
CHECK_GE(argc, 3);

Maybe<int> maybe_mode = GetValidMode(env, args[2], UV_FS_COPYFILE);
int mode;
if (!maybe_mode.To(&mode)) return;

BufferValue src(isolate, args[0]);
CHECK_NOT_NULL(*src);
THROW_IF_INSUFFICIENT_PERMISSIONS(
Expand All @@ -2096,25 +2162,31 @@ static void CopyFile(const FunctionCallbackInfo<Value>& args) {
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());

CHECK(args[2]->IsInt32());
const int flags = args[2].As<Int32>()->Value();

if (argc > 3) { // copyFile(src, dest, flags, req)
if (argc > 3) { // copyFile(src, dest, mode, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
FS_ASYNC_TRACE_BEGIN2(UV_FS_COPYFILE,
req_wrap_async,
"src",
TRACE_STR_COPY(*src),
"dest",
TRACE_STR_COPY(*dest))
AsyncDestCall(env, req_wrap_async, args, "copyfile",
*dest, dest.length(), UTF8, AfterNoArgs,
uv_fs_copyfile, *src, *dest, flags);
} else { // copyFile(src, dest, flags)
AsyncDestCall(env,
req_wrap_async,
args,
"copyfile",
*dest,
dest.length(),
UTF8,
AfterNoArgs,
uv_fs_copyfile,
*src,
*dest,
mode);
} else { // copyFile(src, dest, mode)
FSReqWrapSync req_wrap_sync("copyfile", *src, *dest);
FS_SYNC_TRACE_BEGIN(copyfile);
SyncCallAndThrowOnError(
env, &req_wrap_sync, uv_fs_copyfile, *src, *dest, flags);
env, &req_wrap_sync, uv_fs_copyfile, *src, *dest, mode);
FS_SYNC_TRACE_END(copyfile);
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/parallel/test-fs-access.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ fs.accessSync(readWriteFile, mode);
{ [Symbol.toPrimitive]() { return fs.constants.R_OK; } },
[1],
'r',
NaN,
Infinity,
].forEach((mode, i) => {
console.log(mode, i);
assert.throws(
Expand All @@ -183,8 +185,6 @@ fs.accessSync(readWriteFile, mode);
[
-1,
8,
Infinity,
NaN,
].forEach((mode, i) => {
console.log(mode, i);
assert.throws(
Expand Down
Loading