Skip to content

Commit

Permalink
fs: add v8 fast api to existsSync
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Sep 10, 2023
1 parent 783cc2f commit 0afabd5
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 46 deletions.
25 changes: 25 additions & 0 deletions benchmark/fs/bench-existsSync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const common = require('../common');
const fs = require('fs');
const tmpdir = require('../../test/common/tmpdir');
tmpdir.refresh();

const paths = [
__filename,
tmpdir.resolve(`.non-existing-file-${process.pid}`),
];

const bench = common.createBenchmark(main, {
n: [1e6],
});

function main({ n }) {
bench.start();
for (let i = 0; i < n; i++) {
for (let j = 0; j < paths.length; j++) {
fs.existsSync(paths[j]);
}
}
bench.end(n);
}
23 changes: 3 additions & 20 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const {
validateObject,
validateString,
} = require('internal/validators');
const { readFileSyncUtf8 } = require('internal/fs/read/utf8');
const syncFs = require('internal/fs/sync');

let truncateWarn = true;
let fs;
Expand Down Expand Up @@ -290,23 +290,7 @@ ObjectDefineProperty(exists, kCustomPromisifiedSymbol, {
* @returns {boolean}
*/
function existsSync(path) {
try {
path = getValidatedPath(path);
} catch {
return false;
}
const ctx = { path };
const nPath = pathModule.toNamespacedPath(path);
binding.access(nPath, F_OK, undefined, ctx);

// In case of an invalid symlink, `binding.access()` on win32
// will **not** return an error and is therefore not enough.
// Double check with `binding.stat()`.
if (isWindows && ctx.errno === undefined) {
binding.stat(nPath, false, undefined, ctx);
}

return ctx.errno === undefined;
return syncFs.exists(path);
}

function readFileAfterOpen(err, fd) {
Expand Down Expand Up @@ -462,8 +446,7 @@ function readFileSync(path, options) {

// TODO(@anonrig): Do not handle file descriptor ownership for now.
if (!isUserFd && (options.encoding === 'utf8' || options.encoding === 'utf-8')) {
path = getValidatedPath(path);
return readFileSyncUtf8(pathModule.toNamespacedPath(path), stringToFlags(options.flag));
return syncFs.readFileUtf8(path, options.flag);
}

const fd = isUserFd ? path : fs.openSync(path, options.flag, 0o666);
Expand Down
25 changes: 0 additions & 25 deletions lib/internal/fs/read/utf8.js

This file was deleted.

38 changes: 38 additions & 0 deletions lib/internal/fs/sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

const pathModule = require('path');
const { handleErrorFromBinding, getValidatedPath, stringToFlags } = require('internal/fs/utils');

const syncBinding = internalBinding('fs_sync');

/**
* @param {string} path
* @param {number} flag
* @return {string}
*/
function readFileUtf8(path, flag) {
path = pathModule.toNamespacedPath(getValidatedPath(path));
const response = syncBinding.readFileUtf8(path, stringToFlags(flag));

if (typeof response === 'string') {
return response;
}

const { 0: errno, 1: syscall } = response;
handleErrorFromBinding({ errno, syscall, path });
}

function exists(path) {
try {
path = getValidatedPath(path);
} catch {
return false;
}

return syncBinding.exists(pathModule.toNamespacedPath(path));
}

module.exports = {
readFileUtf8,
exists,
};
55 changes: 55 additions & 0 deletions src/node_file_sync.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "node_constants.h"
#include "node_file_sync.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
Expand All @@ -16,16 +17,22 @@

#include <fcntl.h>

#if !defined(_MSC_VER)
#include <unistd.h>
#endif

namespace node {
namespace fs_sync {

using v8::Array;
using v8::CFunction;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Int32;
using v8::Integer;
using v8::Isolate;
using v8::FastOneByteString;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
Expand Down Expand Up @@ -115,6 +122,50 @@ void BindingData::Deserialize(v8::Local<v8::Context> context,
CHECK_NOT_NULL(binding);
}

bool BindingData::ExistsInternal(const std::string_view path) {
uv_fs_t req;
auto make = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
FS_SYNC_TRACE_BEGIN(access);
int err = uv_fs_access(nullptr, &req, path.data(), F_OK, nullptr);
FS_SYNC_TRACE_END(access);

#ifdef _WIN32
// In case of an invalid symlink, `binding.access()` on win32
// will **not** return an error and is therefore not enough.
// Double check with `stat()`.
if (err != 0) {
FS_SYNC_TRACE_BEGIN(stat);
err = uv_fs_stat(nullptr, &req, path.data, nullptr);
FS_SYNC_TRACE_END(stat);
}
#endif // _WIN32

return err == 0;
}

void BindingData::Exists(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();

const int argc = args.Length();
CHECK_GE(argc, 1);

BufferValue path(isolate, args[0]);
CHECK_NOT_NULL(*path);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());

return ExistsInternal(path.ToStringView());
}

bool BindingData::FastExists(Local<Value> receiver,
const FastOneByteString& path) {
// TODO(@anonrig): Add "THROW_IF_INSUFFICIENT_PERMISSIONS"
return ExistsInternal(std::string_view(path.data, path.length));
}

CFunction BindingData::fast_exists_(CFunction::Make(FastExists));

void BindingData::ReadFileUtf8(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
auto isolate = env->isolate();
Expand Down Expand Up @@ -186,6 +237,7 @@ void BindingData::ReadFileUtf8(const FunctionCallbackInfo<Value>& args) {
void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
Local<ObjectTemplate> target) {
Isolate* isolate = isolate_data->isolate();
SetFastMethodNoSideEffect(isolate, target, "exists", Exists, &fast_exists_);
SetMethodNoSideEffect(isolate, target, "readFileUtf8", ReadFileUtf8);
}

Expand All @@ -199,6 +251,9 @@ void BindingData::CreatePerContextProperties(Local<Object> target,

void BindingData::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(Exists);
registry->Register(FastExists);
registry->Register(fast_exists_.GetTypeInfo());
registry->Register(ReadFileUtf8);
}

Expand Down
8 changes: 8 additions & 0 deletions src/node_file_sync.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class BindingData : public SnapshotableObject {
SET_SELF_SIZE(BindingData)
SET_MEMORY_INFO_NAME(BindingData)

static void Exists(const v8::FunctionCallbackInfo<v8::Value>& args);
static bool FastExists(v8::Local<v8::Value> receiver, const v8::FastOneByteString& path);

static void ReadFileUtf8(const v8::FunctionCallbackInfo<v8::Value>& args);

static void CreatePerIsolateProperties(IsolateData* isolate_data,
Expand All @@ -40,6 +43,11 @@ class BindingData : public SnapshotableObject {
v8::Local<v8::Context> context,
void* priv);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

private:
static v8::CFunction fast_exists_;

static bool BindingData::ExistsInternal(const std::string_view path);
};

} // namespace fs_sync
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-bootstrap-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const expectedModules = new Set([
'NativeModule internal/webstreams/queuingstrategies',
'NativeModule internal/blob',
'NativeModule internal/fs/utils',
'NativeModule internal/fs/read/utf8',
'NativeModule internal/fs/sync',
'NativeModule fs',
'Internal Binding options',
'NativeModule internal/options',
Expand Down

0 comments on commit 0afabd5

Please sign in to comment.