tools: lint thread_local in src

Several Environments can share a thread, so state in src/ that belongs
to one of them cannot be kept in a thread_local. Add a cpplint rule
that rejects thread_local in src/ unless the declaration is marked with
NOLINTNEXTLINE(runtime/thread_local), and mark the existing uses, which
are per-thread by design.

Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66411
Refs: https://github.com/nodejs/node/pull/66239
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
Shelley Vohr authored and Node.js GitHub Bot committed 2026-10-07 10:56:09 +00:00
1 parent 07500390fb
commit 06c6e36ee5
9 files changed
+33

No files matched your search

+1
View File
@@ -1048,6 +1048,7 @@ Maybe<void> InitializePrimordials(Local<Context> context,
// in the first place. However, creating BuiltinLoader instances is
// relatively cheap and all the scripts that we may want to run at
// startup are always present in it.
// NOLINTNEXTLINE(runtime/thread_local)
thread_local builtins::BuiltinLoader builtin_loader;
// Primordials can always be just eagerly compiled.
builtin_loader.SetEagerCompile();
+1
View File
@@ -1472,6 +1472,7 @@ void Environment::ClosePerEnvHandles() {
close_and_finish(reinterpret_cast<uv_handle_t*>(&task_queues_async_));
}
// NOLINTNEXTLINE(runtime/thread_local)
thread_local int handle_cleanup_depth = 0;
void Environment::CleanupHandles() {
+2
View File
@@ -179,6 +179,7 @@ struct dl_wrap {
static Mutex dlhandles_mutex;
static std::unordered_set<dl_wrap*, dl_wrap::hash, dl_wrap::equal>
dlhandles;
// NOLINTNEXTLINE(runtime/thread_local)
static thread_local std::string dlerror_storage;
char* wrapped_dlerror() {
@@ -285,6 +286,7 @@ using v8::Value;
// Globals per process
static node_module* modlist_internal;
static node_module* modlist_linked;
// NOLINTNEXTLINE(runtime/thread_local)
static thread_local node_module* thread_local_modpending;
// This is set by node::Init() which is used by embedders
+2
View File
@@ -24,8 +24,10 @@ using v8::Number;
using v8::Object;
using v8::Value;
// NOLINTNEXTLINE(runtime/thread_local)
thread_local std::unordered_map<FastStringKey, int, FastStringKey::Hash>
generic_usage_counters;
// NOLINTNEXTLINE(runtime/thread_local)
thread_local std::unordered_map<FastStringKey, int, FastStringKey::Hash>
v8_fast_api_call_counts;
+2
View File
@@ -190,9 +190,11 @@ static std::string GetErrorSource(Isolate* isolate,
}
static std::atomic<bool> is_in_oom{false};
// NOLINTNEXTLINE(runtime/thread_local)
static thread_local std::atomic<bool> is_retrieving_js_stacktrace{false};
// This is thread-local because it only guards re-entrancy within the current
// thread's uncaught-exception path; no cross-thread synchronization is needed.
// NOLINTNEXTLINE(runtime/thread_local)
static thread_local bool is_in_uncaught_exception = false;
MaybeLocal<StackTrace> GetCurrentStackTrace(Isolate* isolate, int frame_count) {
if (isolate == nullptr) {
+1
View File
@@ -286,6 +286,7 @@ class InternalCallbackScope {
// Non-zero while an Environment on this thread is closing its handles with JS
// disallowed isolate-wide; InternalCallbackScope re-allows it for the other
// Environments whose callbacks run in those loop turns.
// NOLINTNEXTLINE(runtime/thread_local)
extern thread_local int handle_cleanup_depth;
class DebugSealHandleScope {
+1
View File
@@ -32,6 +32,7 @@ using v8::Undefined;
using v8::Value;
namespace quic {
// NOLINTNEXTLINE(runtime/thread_local)
thread_local int DebugIndentScope::indent_ = 0;
Path::Path(const SocketAddress& local, const SocketAddress& remote) {
+1
View File
@@ -395,6 +395,7 @@ class DebugIndentScope final {
}
private:
// NOLINTNEXTLINE(runtime/thread_local)
static thread_local int indent_;
};
+22
View File
@@ -350,6 +350,7 @@ _ERROR_CATEGORIES = [
"runtime/printf_format",
"runtime/references",
"runtime/string",
"runtime/thread_local",
"runtime/threadsafe_fn",
"runtime/vlog",
"runtime/v8_persistent",
@@ -7446,6 +7447,25 @@ def CheckStringValueUsage(filename, lines, error):
'Use node::TwoByteValue instead.')
def CheckThreadLocalUsage(filename, lines, error):
"""Logs an error if thread_local is used in src/.
Args:
filename: The name of the current file.
lines: An array of strings, each representing a line of the file.
error: The function to call with any errors found.
"""
if not (filename.startswith('src/') or filename.startswith('src\\')):
return
for linenum, line in enumerate(lines):
if re.search(r'\bthread_local\b', line.split('//', 1)[0]):
error(filename, linenum, 'runtime/thread_local', 5,
'Several Environments can share a thread, so keep state that '
'belongs to one on the Environment or its BindingData. Mark '
'intentionally per-thread state with '
'NOLINTNEXTLINE(runtime/thread_local).')
def ProcessLine(
filename,
file_extension,
@@ -7609,6 +7629,8 @@ def ProcessFileData(filename, file_extension, lines, error, extra_check_function
CheckStringValueUsage(filename, lines, error)
CheckThreadLocalUsage(filename, lines, error)
def ProcessConfigOverrides(filename):
"""Loads the configuration files and processes the config overrides.