Skip to content
Open
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
11 changes: 11 additions & 0 deletions doc/changelogs/NSOLID_CHANGELOG_V6_NODE_V22.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

<!--lint disable maximum-line-length no-literal-urls prohibited-strings-->

## 2026-04-20, Version 22.22.2-nsolid-v6.2.3 'Jod'

### Commits

* \[[`6f06f667cf`](https://github.com/nodesource/nsolid/commit/6f06f667cf)] - **lib**: fixes from previous bogus merge (Santiago Gimeno) [#452](https://github.com/nodesource/nsolid/pull/452)
* \[[`8f70e6e88a`](https://github.com/nodesource/nsolid/commit/8f70e6e88a)] - **lib,src,test**: fix race during tracing toggles (Santiago Gimeno) [#441](https://github.com/nodesource/nsolid/pull/441)
* \[[`b7e988579c`](https://github.com/nodesource/nsolid/commit/b7e988579c)] - **lib,test**: fix js linting issues (Santiago Gimeno) [#452](https://github.com/nodesource/nsolid/pull/452)
* \[[`efeb7a9986`](https://github.com/nodesource/nsolid/commit/efeb7a9986)] - **src**: fix cpp linting issues (Santiago Gimeno) [#452](https://github.com/nodesource/nsolid/pull/452)
* \[[`906103526c`](https://github.com/nodesource/nsolid/commit/906103526c)] - **src**: replace duplicate loop hook regs (Santiago Gimeno) [#444](https://github.com/nodesource/nsolid/pull/444)
* \[[`6975f4f7a9`](https://github.com/nodesource/nsolid/commit/6975f4f7a9)] - **test**: fix linting in test-nsolid-file-handle-count (Santiago Gimeno) [#441](https://github.com/nodesource/nsolid/pull/441)

## 2026-03-25, Version 22.22.2-nsolid-v6.2.2 'Jod'

### Commits
Expand Down
3 changes: 3 additions & 0 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const {
SafePromisePrototypeFinally,
Symbol,
Uint8Array,
uncurryThis,
} = primordials;

const { fs: constants } = internalBinding('constants');
Expand All @@ -29,6 +30,8 @@ const {

const binding = internalBinding('fs');
const { Buffer } = require('buffer');
const { isBuffer: BufferIsBuffer } = Buffer;
const BufferToString = uncurryThis(Buffer.prototype.toString);

const {
AbortError,
Expand Down
16 changes: 13 additions & 3 deletions lib/internal/nsolid_trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ const TRACE_ID_PART = '(?![0]{32})[\\da-f]{32}';
const PARENT_ID_PART = '(?![0]{16})[\\da-f]{16}';
const FLAGS_PART = '[\\da-f]{2}';
const TRACE_PARENT_REGEX = new RegExp(`^\\s?(${VERSION_PART})-(${TRACE_ID_PART})-(${PARENT_ID_PART})-(${FLAGS_PART})(-.*)?\\s?$`);
let currentTraceFlags = 0;


function generateSpan(type) {
return nsolidApi.traceFlags[0] & type;
return getTraceFlags() & type;
}

function getTraceFlags() {
if (nsolidApi.traceFlags !== undefined) {
currentTraceFlags = nsolidApi.traceFlags[0];
}
return currentTraceFlags;
}

function parseTraceParent(traceParent) {
Expand Down Expand Up @@ -69,13 +77,15 @@ function extractSpanContextFromHttpHeaders(context, headers) {
}

const nsolidTracer = new EventEmitter();
binding.setToggleTracingFn(() => {
nsolidTracer.emit('flagsUpdated');
binding.setToggleTracingFn((flags) => {
currentTraceFlags = flags;
nsolidTracer.emit('flagsUpdated', flags);
});


module.exports = {
extractSpanContextFromHttpHeaders,
generateSpan,
getTraceFlags,
nsolidTracer,
};
12 changes: 6 additions & 6 deletions lib/internal/otel/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const {
ArrayIsArray,
} = primordials;

const binding = internalBinding('nsolid_api');
const {
codes: {
ERR_INVALID_ARG_TYPE,
Expand All @@ -28,16 +27,16 @@ function register(api) {
// TODO(santigimeno): perform some kind of validation that the api is actually
// the OTEL api.
api_ = api;
const { nsolidTracer } = require('internal/nsolid_trace');
nsolidTracer.on('flagsUpdated', () => {
if (binding.trace_flags[0]) {
const { getTraceFlags, nsolidTracer } = require('internal/nsolid_trace');
nsolidTracer.on('flagsUpdated', (flags) => {
if (flags) {
enableApi();
} else {
disableApi();
}
});

if (binding.trace_flags[0]) {
if (getTraceFlags()) {
return enableApi();
}

Expand All @@ -55,7 +54,8 @@ function registerInstrumentations(instrumentations) {
instrumentations);
}

if (binding.trace_flags[0]) {
const { getTraceFlags } = require('internal/nsolid_trace');
if (getTraceFlags()) {
enableInsts(instrumentations);
} else {
disableInsts(instrumentations);
Expand Down
5 changes: 4 additions & 1 deletion lib/internal/otel/trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const { nsolid_consts } = binding;
const {
getApi,
} = require('internal/otel/core');
const {
getTraceFlags,
} = require('internal/nsolid_trace');

const {
newInternalSpanId,
Expand Down Expand Up @@ -251,7 +254,7 @@ Span.prototype._pushSpanDataUint64 = function(type, name) {
class Tracer {
startSpan(name, options = {}, context) {
const api = getApi();
if (binding.trace_flags[0] === 0) {
if (!options.internal && getTraceFlags() === 0) {
return api.trace.wrapSpanContext(api.INVALID_SPAN_CONTEXT);
}

Expand Down
2 changes: 1 addition & 1 deletion src/node_metadata.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "node_metadata.h"
#include <algorithm>
#include "node_metadata.h"
#include <zmq.h>
#include "acorn_version.h"
#include "ada.h"
Expand Down
2 changes: 1 addition & 1 deletion src/node_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
#define NSOLID_MINOR_VERSION 2
#define NSOLID_PATCH_VERSION 3

#define NSOLID_VERSION_IS_RELEASE 0
#define NSOLID_VERSION_IS_RELEASE 1

#ifndef NODE_STRINGIFY
#define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)
Expand Down
27 changes: 22 additions & 5 deletions src/nsolid/nsolid_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "node_url.h"
#include "v8-fast-api-calls.h"

#include <algorithm>
#include <cmath>

#if defined(__linux__)
Expand Down Expand Up @@ -967,10 +968,12 @@ void EnvList::OnBlockedLoopHook(
void* data,
internal::on_block_loop_hook_proxy_sig proxy,
internal::deleter_sig deleter) {
blocked_hooks_list_.push_back(
blocked_hooks_list_.replace_if(
[proxy](const BlockedLoopStor& stor) {
return stor.cb == proxy;
},
{ threshold, proxy, nsolid::internal::user_data(data, deleter) });
if (threshold < min_blocked_threshold_)
min_blocked_threshold_ = threshold;
refresh_min_blocked_threshold();
}

void EnvList::OnUnblockedLoopHook(
Expand All @@ -979,7 +982,10 @@ void EnvList::OnUnblockedLoopHook(
internal::deleter_sig deleter) {
// Using BlockedLoopStor because it's easier than duplicating a bunch of code,
// but that means some value needs to be passed in for threshold.
unblocked_hooks_list_.push_back(
unblocked_hooks_list_.replace_if(
[proxy](const BlockedLoopStor& stor) {
return stor.cb == proxy;
},
{ 0, proxy, nsolid::internal::user_data(data, deleter) });
}

Expand Down Expand Up @@ -1010,6 +1016,17 @@ nlohmann::json EnvList::CurrentConfigJSON() {
}


void EnvList::refresh_min_blocked_threshold() {
uint64_t min_threshold = UINT64_MAX;

blocked_hooks_list_.for_each([&min_threshold](const BlockedLoopStor& stor) {
min_threshold = std::min(min_threshold, stor.threshold);
});

min_blocked_threshold_ = min_threshold;
}


void EnvList::AddEnv(Environment* env) {
SharedEnvInst envinst_sp = EnvInst::Create(env);

Expand Down Expand Up @@ -1812,7 +1829,7 @@ void EnvList::update_tracing_flags(SharedEnvInst envinst_sp, uint32_t flags) {
HandleScope handle_scope(isolate);
Context::Scope context_scope(env->context());
Local<Value> argv[] = {
v8::Boolean::New(isolate, flags > 0)
Uint32::New(isolate, flags)
};

// We don't care if Call throws or exits. So ignore the return value.
Expand Down
1 change: 1 addition & 0 deletions src/nsolid/nsolid_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ class EnvList {
void fill_trace_id_q();

void update_continuous_profiler(bool enabled, uint64_t interval);
void refresh_min_blocked_threshold();

#ifdef __POSIX__
static void signal_handler_(int signum, siginfo_t* info, void* ucontext);
Expand Down
12 changes: 12 additions & 0 deletions src/nsolid/thread_safe.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ struct TSList {
list_.push_back(std::move(data));
return --list_.end();
}
template <typename Match>
inline bool replace_if(Match match, DataType&& data) {
nsuv::ns_mutex::scoped_lock lock(lock_);
for (auto it = list_.begin(); it != list_.end(); ++it) {
if (!match(*it))
continue;
*it = std::move(data);
return true;
}
list_.push_back(std::move(data));
return false;
}
inline void for_each(std::function<void(const DataType&)> fn) {
nsuv::ns_mutex::scoped_lock lock(lock_);
std::for_each(list_.begin(), list_.end(), fn);
Expand Down
45 changes: 39 additions & 6 deletions test/addons/nsolid-tracing/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ class Trace {
Tracer* tracer_ = nullptr;
std::queue<std::string> spans_;
json expected_traces_ = {};
bool at_exit_registered_ = false;
bool check_expected_traces_ = true;

static constexpr uint32_t kDefaultTraceFlags =
kSpanDns | kSpanHttpClient | kSpanHttpServer | kSpanCustom;


// NOLINTNEXTLINE(runtime/references)
Expand Down Expand Up @@ -166,6 +171,10 @@ static void at_exit_cb() {
fprintf(stderr, "traces_array: %s\n", traces_array.dump(4).c_str());
// fprintf(stderr, "expected_traces: %s\n", expected_traces_.dump(4).c_str());

if (!check_expected_traces_) {
return;
}

assert(traces_array.size() == expected_traces_.size());
for (auto i = traces_array.begin(); i != traces_array.end(); ++i) {
for (auto j = expected_traces_.begin();
Expand Down Expand Up @@ -242,22 +251,46 @@ static void ExpectedTrace(const v8::FunctionCallbackInfo<v8::Value>& args) {
}

static void SetupTracing(const v8::FunctionCallbackInfo<v8::Value>& args) {
assert(0 == args.Length());
assert(args.Length() <= 1);
v8::Isolate* isolate = args.GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
node::nsolid::SharedEnvInst envinst = node::nsolid::GetLocalEnvInst(context);
if (node::nsolid::IsMainThread(envinst)) {
tracer_ = Tracer::CreateInstance(kSpanDns |
kSpanHttpClient |
kSpanHttpServer |
kSpanCustom, got_trace);
atexit(at_exit_cb);
if (tracer_ != nullptr) {
return;
}

uint32_t flags = kDefaultTraceFlags;
if (args.Length() == 1) {
assert(args[0]->IsUint32());
flags = args[0].As<v8::Uint32>()->Value();
}

tracer_ = Tracer::CreateInstance(flags, got_trace);
if (!at_exit_registered_) {
atexit(at_exit_cb);
at_exit_registered_ = true;
}
}
}

static void StopTracing(const v8::FunctionCallbackInfo<v8::Value>& args) {
assert(0 == args.Length());
delete tracer_;
tracer_ = nullptr;
}

static void SkipExpectedTracesCheck(
const v8::FunctionCallbackInfo<v8::Value>& args) {
assert(0 == args.Length());
check_expected_traces_ = false;
}

NODE_MODULE_INIT(/* exports, module, context */) {
NODE_SET_METHOD(exports, "expectedTrace", ExpectedTrace);
NODE_SET_METHOD(exports, "setupTracing", SetupTracing);
NODE_SET_METHOD(exports, "stopTracing", StopTracing);
NODE_SET_METHOD(exports, "skipExpectedTracesCheck", SkipExpectedTracesCheck);
#define V(Name, Val, Str) \
NODE_DEFINE_CONSTANT(exports, Name);
NSOLID_SPAN_TYPES(V)
Expand Down
75 changes: 75 additions & 0 deletions test/addons/nsolid-tracing/test-otel-fetch-enable-disable-race.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Flags: --dns-result-order=ipv4first
'use strict';

const common = require('../../common');
const assert = require('assert');
const http = require('http');
const { once } = require('events');

const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
const binding = require(bindingPath);

const concurrency = 16;
const fetchRounds = 150;
const toggleRounds = 400;

async function main() {
binding.skipExpectedTracesCheck();
binding.setupTracing(binding.kSpanHttpClient);

const server = http.createServer((req, res) => {
req.resume();
res.writeHead(200, { 'content-type': 'text/plain' });
setImmediate(() => res.end('ok'));
});

server.listen(0, '127.0.0.1');
await once(server, 'listening');
const { port } = server.address();
const url = `http://127.0.0.1:${port}/`;

const failures = [];

async function runFetches() {
for (let i = 0; i < fetchRounds; i++) {
const batch = Array.from({ length: concurrency }, async () => {
try {
const res = await fetch(url, {
method: 'POST',
body: 'payload',
});
await res.text();
} catch (err) {
failures.push(err);
}
});
await Promise.all(batch);
}
}

async function toggleTracing() {
for (let i = 0; i < toggleRounds; i++) {
binding.stopTracing();
await new Promise(setImmediate);
binding.setupTracing(binding.kSpanHttpClient);
await new Promise(setImmediate);
}
}

try {
await Promise.all([
runFetches(),
toggleTracing(),
]);
} finally {
binding.stopTracing();
server.close();
await once(server, 'close');
}

assert.deepStrictEqual(failures, []);
}

main().then(common.mustCall()).catch((err) => {
throw err;
});
Loading
Loading