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

src: use libuv to get env vars #29188

Closed
wants to merge 4 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
54 changes: 47 additions & 7 deletions benchmark/process/bench-env.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,55 @@
const common = require('../common');

const bench = common.createBenchmark(main, {
n: [1e5],
n: [1e6],
operation: ['get', 'set', 'enumerate', 'query', 'delete']
Copy link
Member

Choose a reason for hiding this comment

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

I think this change means we need to add a line for operation in test/benchmark/test-benchmark-process.js.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Trott Thanks for catching, done!

});


function main({ n }) {
bench.start();
for (var i = 0; i < n; i++) {
// Access every item in object to process values.
Object.keys(process.env);
function main({ n, operation }) {
switch (operation) {
case 'get':
bench.start();
for (let i = 0; i < n; i++) {
process.env.PATH;
}
bench.end(n);
break;
case 'set':
bench.start();
for (let i = 0; i < n; i++) {
process.env.DUMMY = 'hello, world';
}
bench.end(n);
break;
case 'enumerate':
// First, normalize process.env so that benchmark results are comparable.
for (const key of Object.keys(process.env))
delete process.env[key];
for (let i = 0; i < 64; i++)
process.env[Math.random()] = Math.random();

n /= 10; // Enumeration is comparatively heavy.
bench.start();
for (let i = 0; i < n; i++) {
// Access every item in object to process values.
Object.keys(process.env);
}
bench.end(n);
break;
case 'query':
bench.start();
for (let i = 0; i < n; i++) {
'PATH' in process.env;
}
bench.end(n);
break;
case 'delete':
bench.start();
for (let i = 0; i < n; i++) {
delete process.env.DUMMY;
}
bench.end(n);
break;
}
bench.end(n);
}
84 changes: 28 additions & 56 deletions src/node_env_var.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@

#include <time.h> // tzset(), _tzset()

#ifdef __APPLE__
#include <crt_externs.h>
#define environ (*_NSGetEnviron())
#elif !defined(_MSC_VER)
extern char** environ;
#endif

namespace node {
using v8::Array;
using v8::Boolean;
Expand Down Expand Up @@ -123,12 +116,6 @@ int32_t RealEnvStore::Query(Isolate* isolate, Local<String> property) const {
Mutex::ScopedLock lock(per_process::env_var_mutex);

node::Utf8Value key(isolate, property);
#ifdef _WIN32
if (key[0] == L'=')
return static_cast<int32_t>(v8::ReadOnly) |
static_cast<int32_t>(v8::DontDelete) |
static_cast<int32_t>(v8::DontEnum);
#endif

char val[2];
size_t init_sz = sizeof(val);
Expand All @@ -138,6 +125,14 @@ int32_t RealEnvStore::Query(Isolate* isolate, Local<String> property) const {
return -1;
}

#ifdef _WIN32
if (key[0] == L'=') {
return static_cast<int32_t>(v8::ReadOnly) |
static_cast<int32_t>(v8::DontDelete) |
static_cast<int32_t>(v8::DontEnum);
}
#endif

return 0;
}

Expand All @@ -151,54 +146,31 @@ void RealEnvStore::Delete(Isolate* isolate, Local<String> property) {

Local<Array> RealEnvStore::Enumerate(Isolate* isolate) const {
Mutex::ScopedLock lock(per_process::env_var_mutex);
#ifdef __POSIX__
int env_size = 0;
while (environ[env_size]) {
env_size++;
}
std::vector<Local<Value>> env_v(env_size);

for (int i = 0; i < env_size; ++i) {
const char* var = environ[i];
const char* s = strchr(var, '=');
const int length = s ? s - var : strlen(var);
env_v[i] = String::NewFromUtf8(isolate, var, NewStringType::kNormal, length)
.ToLocalChecked();
}
#else // _WIN32
std::vector<Local<Value>> env_v;
WCHAR* environment = GetEnvironmentStringsW();
if (environment == nullptr)
return Array::New(isolate); // This should not happen.
WCHAR* p = environment;
while (*p) {
WCHAR* s;
if (*p == L'=') {
// If the key starts with '=' it is a hidden environment variable.
p += wcslen(p) + 1;
continue;
} else {
s = wcschr(p, L'=');
}
if (!s) {
s = p + wcslen(p);
}
const uint16_t* two_byte_buffer = reinterpret_cast<const uint16_t*>(p);
const size_t two_byte_buffer_len = s - p;
v8::MaybeLocal<String> rc = String::NewFromTwoByte(
isolate, two_byte_buffer, NewStringType::kNormal, two_byte_buffer_len);
if (rc.IsEmpty()) {
uv_env_item_t* items;
int count;

OnScopeLeave cleanup([&]() { uv_os_free_environ(items, count); });
CHECK_EQ(uv_os_environ(&items, &count), 0);

MaybeStackBuffer<Local<Value>, 256> env_v(count);
int env_v_index = 0;
for (int i = 0; i < count; i++) {
#ifdef _WIN32
// If the key starts with '=' it is a hidden environment variable.
// The '\0' check is a workaround for the bug behind
// https://github.com/libuv/libuv/pull/2473 and can be removed later.
if (items[i].name[0] == '=' || items[i].name[0] == '\0') continue;
#endif
MaybeLocal<String> str = String::NewFromUtf8(
isolate, items[i].name, NewStringType::kNormal);
if (str.IsEmpty()) {
isolate->ThrowException(ERR_STRING_TOO_LONG(isolate));
FreeEnvironmentStringsW(environment);
return Local<Array>();
}
env_v.push_back(rc.ToLocalChecked());
p = s + wcslen(s) + 1;
env_v[env_v_index++] = str.ToLocalChecked();
}
FreeEnvironmentStringsW(environment);
#endif

return Array::New(isolate, env_v.data(), env_v.size());
return Array::New(isolate, env_v.out(), env_v_index);
}

std::shared_ptr<KVStore> KVStore::Clone(v8::Isolate* isolate) const {
Expand Down
3 changes: 2 additions & 1 deletion test/benchmark/test-benchmark-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ const runBenchmark = require('../common/benchmark');
runBenchmark('process',
[
'n=1',
'type=raw'
'type=raw',
'operation=enumerate',
], { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 });