Skip to content

src: add AddCodeEventHook API for code events #327

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

Open
wants to merge 2 commits into
base: node-v22.x-nsolid-v5.x
Choose a base branch
from

Conversation

santigimeno
Copy link
Member

@santigimeno santigimeno commented Jun 10, 2025

Introduces a new API to register code event hooks in nsolid. This
includes new handler classes, queueing, and infrastructure to allow
extensible code event observation and callbacks.

Also:

src: a couple of improvements in TSList API
    
Adds a for_each method to TSList that passes the current list size and
item to the callback. Updates erase to return the new size after removal.
These changes improve thread-safe iteration and management of hooks.

Fixes: https://github.com/nodesource/ebpf-roadmap/issues/7

Summary by CodeRabbit

  • New Features

    • Introduced support for registering hooks that listen to JavaScript code events, allowing callbacks to be triggered on code changes.
    • Added APIs for managing code event hooks, including registration, removal, and callback handling.
    • Provided a native addon and JavaScript interface for working with code event hooks and receiving event details.
    • Implemented asynchronous dispatching of code event notifications to registered hooks with detailed event information.
    • Enhanced thread-safe list operations to report size during iteration and return updated size on element removal.
  • Bug Fixes

    • Not applicable.
  • Tests

    • Added comprehensive tests for code event hook functionality, including registration, callback invocation, and cleanup.
    • Expanded test coverage for thread-safe list operations, verifying correct iteration and element removal behavior.

@santigimeno santigimeno self-assigned this Jun 10, 2025
Copy link

coderabbitai bot commented Jun 10, 2025

Walkthrough

This change introduces a new infrastructure for registering and handling code event hooks in the NSolid runtime. It adds new source and header files for a code event handler, updates build configuration, extends core classes to support asynchronous code event dispatching, and provides a public API for registering hooks. Comprehensive tests and a Node.js addon are included to validate and expose the feature.

Changes

File(s) Change Summary
node.gyp Added src/nsolid/nsolid_code_event_handler.cc to build sources.
src/nsolid/nsolid_code_event_handler.cc
src/nsolid/nsolid_code_event_handler.h
Introduced NSolidCodeEventHandler class to process and forward V8 code events.
src/nsolid.h Added CodeEventInfo struct, CodeEventHook class, and template API for registering code event hooks.
src/nsolid.cc Implemented CodeEventHook logic, lifecycle management, and internal hook registration function.
src/nsolid/nsolid_api.cc
src/nsolid/nsolid_api.h
Extended EnvInst and EnvList to manage code event handler setup, hook registration, and event dispatching.
src/nsolid/thread_safe.h Updated TSList API: for_each now provides list size; erase returns new size.
test/addons/nsolid-code-event-hook/binding.cc
test/addons/nsolid-code-event-hook/binding.gyp
test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js
Added Node.js addon and test for registering and handling code event hooks from JavaScript.
test/cctest/test_nsolid_thread_safe.cc Added tests for new TSList behaviors: for_each with size and erase returning new size.

Sequence Diagram(s)

sequenceDiagram
    participant JS as JavaScript
    participant Addon as Native Addon
    participant NSolid as NSolid Runtime
    participant EnvList as EnvList
    participant EnvInst as EnvInst
    participant Handler as NSolidCodeEventHandler

    JS->>Addon: registerCodeEventHook(callback)
    Addon->>NSolid: AddCodeEventHook(cb, data)
    NSolid->>EnvList: AddCodeEventHook(cb, data)
    EnvList->>EnvInst: setup_code_event_handler()
    EnvInst->>Handler: (creates NSolidCodeEventHandler)

    Handler-->>Handler: (V8 triggers code event)
    Handler->>EnvList: on_code_event_q_.enqueue(CodeEventInfo)
    EnvList->>Addon: got_code_event(CodeEventInfo)
    Addon->>JS: callback(functionName, threadId)
Loading

Poem

🐇
In the warren of code, a new hook appears,
Catching events as V8 engineers.
With threads and queues, events now flow,
To JavaScript callbacks, in a seamless show.
Rabbits rejoice, for tracing is neat—
Code hops ahead, on nimble feet!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a new API for registering and handling code event hooks in nsolid, along with several infrastructure updates to support asynchronous code event callbacks. Key changes include:

  • New TSList APIs to iterate with size parameters and return updated list sizes on erasure.
  • Integration of code event hook registration/handling in both C++ (nsolid APIs, code event handler, and event dispatching) and JavaScript (addon tests).
  • Updates to binding, build files, and API header modifications to support code event hooks.

Reviewed Changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/cctest/test_nsolid_thread_safe.cc Added tests for new TSList for_each and erase APIs.
test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js Added tests for the new code event hook API in a multi-threaded context.
test/addons/nsolid-code-event-hook/binding.gyp Added binding definition for the new addon.
test/addons/nsolid-code-event-hook/binding.cc Implemented code event callback and hook registration logic in C++.
src/nsolid/thread_safe.h Added for_each overload and modified erase to return updated list size.
src/nsolid/nsolid_code_event_handler.h & .cc Introduced the NSolidCodeEventHandler for handling code events.
src/nsolid/nsolid_api.h & .cc Updated API to include code event handler setup and hook management.
src/nsolid/nsolid.cc Added CodeEventHook implementation and adjustments to hook lifecycle.
node.gyp Updated build configuration to include the new code event handler module.
Comments suppressed due to low confidence (2)

src/nsolid/nsolid.cc:625

  • Calling 'delete this;' in CodeEventHook::Dispose can be error-prone if not handled carefully. Consider clarifying the ownership semantics in documentation or refactoring to use a smart pointer based approach to avoid potential misuse.
delete this;

src/nsolid/nsolid_api.cc:1984

  • The conditional use of 'std::move(info)' when size equals 1 versus passing by value when multiple hooks are present could lead to inconsistent data ownership. Consider using a consistent strategy when dispatching CodeEventInfo to hooks to avoid potential issues with moved-from objects.
code_event_hook_list_.for_each([&info, &envinst_sp](auto& stor, size_t size) {

Copy link

@windsurf-bot windsurf-bot bot left a comment

Choose a reason for hiding this comment

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

1 file skipped due to size limits:
  • src/nsolid/nsolid_api.cc

💡 To request another review, post a new comment with "/windsurf-review".

Comment on lines +603 to +606
~Impl() {
// Remove the hook from the TSList in EnvList
EnvList::Inst()->RemoveCodeEventHook(hook_);
}
Copy link

Choose a reason for hiding this comment

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

The destructor of CodeEventHook::Impl unconditionally calls EnvList::Inst()->RemoveCodeEventHook(hook_), but there's no check if hook_ is a valid iterator. If Setup() fails or is never called, this could lead to undefined behavior when removing an invalid iterator.

Copy link
Member Author

Choose a reason for hiding this comment

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

This cannot ever happen afaict

Comment on lines +674 to +685
CodeEventHook* add_code_event_hook_(void* data,
code_event_hook_proxy_sig proxy,
deleter_sig deleter) {
CodeEventHook* hook = new (std::nothrow) CodeEventHook();
if (hook == nullptr) {
return nullptr;
}
hook->DoSetup(proxy, deleter, data);
return hook;
}
Copy link

Choose a reason for hiding this comment

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

The add_code_event_hook_ function allocates a CodeEventHook with new but doesn't check if DoSetup() might fail. If DoSetup() fails, the function would still return a partially initialized hook object that could cause issues when used.

Copy link
Member Author

Choose a reason for hiding this comment

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

DoSetup cannot fail

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (10)
test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js (1)

1-1: Remove redundant 'use strict' directive.

The static analysis tool correctly identifies that the 'use strict' directive is redundant since JavaScript modules are automatically in strict mode.

-'use strict';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)

src/nsolid.cc (2)

625-627: Document the ownership model for Dispose().

The Dispose() method deletes the object itself (delete this), which requires careful documentation about ownership transfer and usage patterns to prevent use-after-free errors.

Consider adding a comment to clarify the ownership model:

 void CodeEventHook::Dispose() {
+  // This method transfers ownership to the callee and deletes the object.
+  // The caller must not use this object after calling Dispose().
   delete this;
 }

674-683: Consider using exceptions or error codes for allocation failures.

The function returns nullptr on allocation failure, but the caller might not check for this condition.

Consider either:

  1. Using exceptions (if allowed in the codebase)
  2. Adding an error code parameter
  3. At minimum, documenting the nullptr return value in the header file

Current implementation is acceptable if callers are expected to check for nullptr.

src/nsolid/nsolid_api.cc (3)

891-898: Async queue lambda captures info by forwarding-reference – use const ref

CodeEventInfo can potentially be large. The lambda already moves it when
delivering, so capturing by forwarding ref (&&) brings no extra value and
may lead to a double move/invalid use if extended.

-      +[](CodeEventInfo&& info, EnvList* envlist) {
-        envlist->got_code_event(std::move(info));
+      +[](CodeEventInfo info, EnvList* envlist) {
+        envlist->got_code_event(std::move(info));
       },

A plain pass-by-value here is cheaper and clearer.


1033-1056: Minor: potential redundant map copy during last-hook removal

When the last hook is removed we copy env_map_ even if it is already empty.
A quick guard can avoid unnecessary work in the common “no envs” case:

if (size == 0) {
  decltype(env_map_) env_map;
  {
    ns_mutex::scoped_lock lock(map_lock_);
    if (env_map_.empty()) return;
    env_map = env_map_;
  }
  ...
}

1498-1508: DCHECK_EQ may fire if invoked via EventLoop command

setup_code_event_handler() is scheduled with CommandType::InterruptOnly
which is executed inside V8’s interrupt callback. At that point
Isolate::TryGetCurrent() can legitimately return the same isolate but on a
different native thread (depending on how interrupts are delivered). Consider
removing or downgrading the DCHECK_EQ to avoid false positives in RELEASE +
NODE_DEBUG builds.

src/nsolid.h (2)

660-672: Consider making the struct fields const where appropriate.

Since CodeEventInfo is likely used to pass immutable event data to callbacks, consider making appropriate fields const to prevent accidental modification:

 struct CodeEventInfo {
-  uint64_t thread_id;
-  uint64_t timestamp;
-  v8::CodeEventType type;
-  uintptr_t code_start;
-  uintptr_t new_code_start;
-  size_t code_len;
-  std::string fn_name;
-  std::string script_name;
-  int script_line;
-  int script_column;
-  std::string comment;
+  const uint64_t thread_id;
+  const uint64_t timestamp;
+  const v8::CodeEventType type;
+  const uintptr_t code_start;
+  const uintptr_t new_code_start;
+  const size_t code_len;
+  const std::string fn_name;
+  const std::string script_name;
+  const int script_line;
+  const int script_column;
+  const std::string comment;
 };

679-686: Add documentation for the Dispose() method lifecycle.

While the brief describes that Dispose() removes the hook and releases resources, it would be helpful to document whether:

  • The method is idempotent (can be called multiple times safely)
  • The method blocks until any in-progress callbacks complete
  • The object becomes invalid immediately after calling Dispose()
src/nsolid/nsolid_api.h (2)

248-250: Consider adding documentation for the new methods.

These methods are part of the public interface of EnvInst. Consider adding documentation to explain:

  • When these methods should be called
  • Thread safety guarantees
  • Any preconditions or postconditions

653-653: Consider documenting the threading model for code event dispatch.

The method got_code_event takes an rvalue reference, suggesting it's designed for efficient event forwarding. Document:

  • Which thread(s) call this method
  • How it interacts with the async queue
  • Performance considerations
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2492fc8 and 62ac188.

📒 Files selected for processing (12)
  • node.gyp (1 hunks)
  • src/nsolid.cc (2 hunks)
  • src/nsolid.h (7 hunks)
  • src/nsolid/nsolid_api.cc (8 hunks)
  • src/nsolid/nsolid_api.h (11 hunks)
  • src/nsolid/nsolid_code_event_handler.cc (1 hunks)
  • src/nsolid/nsolid_code_event_handler.h (1 hunks)
  • src/nsolid/thread_safe.h (2 hunks)
  • test/addons/nsolid-code-event-hook/binding.cc (1 hunks)
  • test/addons/nsolid-code-event-hook/binding.gyp (1 hunks)
  • test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js (1 hunks)
  • test/cctest/test_nsolid_thread_safe.cc (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js (1)
test/common/index.js (1)
  • buildType (155-157)
src/nsolid/nsolid_code_event_handler.cc (3)
src/nsolid/nsolid_code_event_handler.h (3)
  • NSolidCodeEventHandler (14-14)
  • NSolidCodeEventHandler (15-15)
  • code_event (17-17)
src/util.h (1)
  • Utf8Value (548-548)
src/nsolid/nsolid_api.cc (2)
  • Inst (914-917)
  • Inst (914-914)
test/addons/nsolid-code-event-hook/binding.cc (3)
src/nsolid.cc (10)
  • CodeEventHook (620-621)
  • CodeEventHook (623-623)
  • cb (610-615)
  • cb (610-612)
  • GetThreadId (56-64)
  • GetThreadId (56-56)
  • GetLocalEnvInst (43-45)
  • GetLocalEnvInst (43-43)
  • GetLocalEnvInst (47-49)
  • GetLocalEnvInst (47-47)
src/nsolid.h (14)
  • CodeEventHook (679-702)
  • CodeEventHook (690-690)
  • CodeEventHook (693-693)
  • RunCommand (523-526)
  • RunCommand (1602-1626)
  • RunCommand (1602-1605)
  • cb (658-658)
  • thread_id (852-852)
  • thread_id (852-852)
  • GetThreadId (404-404)
  • GetLocalEnvInst (385-385)
  • GetLocalEnvInst (391-391)
  • AddCodeEventHook (1842-1860)
  • AddCodeEventHook (1842-1842)
src/nsolid/nsolid_api.cc (8)
  • RunCommand (157-202)
  • RunCommand (157-160)
  • GetCurrent (433-436)
  • GetCurrent (433-433)
  • GetCurrent (439-442)
  • GetCurrent (439-439)
  • AddCodeEventHook (1007-1031)
  • AddCodeEventHook (1007-1010)
src/nsolid/nsolid_api.cc (3)
src/nsolid/nsolid_api.h (23)
  • data (80-80)
  • data (81-81)
  • data (515-518)
  • data (520-523)
  • data (525-528)
  • data (529-532)
  • data (539-542)
  • data (544-546)
  • envinst (340-341)
  • envinst (342-343)
  • envinst (344-345)
  • envinst (346-347)
  • it (547-547)
  • it (620-620)
  • envinst_sp (195-198)
  • envinst_sp (334-335)
  • envinst_sp (662-662)
  • envinst_sp (670-670)
  • envinst_sp (671-671)
  • envinst_sp (672-672)
  • envinst_sp (674-674)
  • envinst_sp (678-678)
  • envinst_sp (679-679)
src/nsolid.h (5)
  • AddCodeEventHook (1842-1860)
  • AddCodeEventHook (1842-1842)
  • RunCommand (523-526)
  • RunCommand (1602-1626)
  • RunCommand (1602-1605)
src/nsolid.cc (2)
  • deleter (463-463)
  • deleter (611-611)
🪛 Biome (1.9.4)
test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: test-linux (ubuntu-24.04-arm)
  • GitHub Check: test-linux (ubuntu-24.04)
  • GitHub Check: test-macOS
  • GitHub Check: coverage-windows
  • GitHub Check: build-docs
  • GitHub Check: coverage-linux-without-intl
  • GitHub Check: coverage-linux
  • GitHub Check: lint-js-and-md
  • GitHub Check: lint-cpp
  • GitHub Check: build-tarball
🔇 Additional comments (25)
src/nsolid/thread_safe.h (4)

149-155: LGTM! Well-designed enhancement for thread-safe iteration with size awareness.

The new for_each overload that provides the current list size to the callback is a thoughtful addition. Capturing the size once before iteration ensures consistency throughout the callback execution, which is crucial for the code event hook infrastructure.


156-160: Excellent improvement to return the new size after element removal.

Returning the updated list size from the erase method provides valuable feedback for managing hook lifecycles and is more informative than the previous void return type.


189-195: Consistent implementation in the pointer specialization.

The changes are properly mirrored in the pointer template specialization, maintaining API consistency across both variants of TSList.


196-200: Good consistency in the pointer specialization's erase method.

The pointer specialization correctly returns the new size after erasure, maintaining consistency with the main template implementation.

test/addons/nsolid-code-event-hook/binding.gyp (1)

1-9: Clean and correct build configuration for the test addon.

The binding.gyp file follows standard node-gyp conventions with appropriate debug symbols enabled for testing. The configuration correctly references the binding.cc source file and establishes the "binding" target.

node.gyp (1)

517-517: Proper integration of the new code event handler into the build system.

The source file is correctly added to the nsolid_sources list, following the established pattern for NSolid components and maintaining consistency with the directory structure.

src/nsolid/nsolid_code_event_handler.h (1)

1-28: Excellent design for the NSolid code event handler.

This header file demonstrates several best practices:

  • Proper header guards and conditional compilation for internal use only
  • Clean inheritance from v8::CodeEventHandler with appropriate method override
  • Virtual destructor following best practices for polymorphic base classes
  • Well-designed constructor taking necessary context (isolate and thread ID)
  • Clear separation between public interface and private implementation details

The design properly encapsulates the functionality needed for capturing and processing V8 code events within the NSolid framework.

test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js (1)

3-91: Comprehensive and well-structured test for code event hooks.

This test provides excellent coverage of the code event hook functionality:

  • Thorough testing scope: Tests registration, callback invocation, selective unregistration, and complete cleanup
  • Thread-aware testing: Uses worker threads to verify thread-specific hook behavior and thread ID correctness
  • State management: Proper state tracking and reset mechanisms for reliable test execution
  • Asynchronous handling: Appropriate use of setTimeout for asynchronous event testing
  • Robust assertions: Verifies both positive cases (hooks firing) and negative cases (hooks not firing after unregistration)

The test design effectively validates the entire lifecycle of code event hooks and ensures proper isolation between different hook instances.

src/nsolid/nsolid_code_event_handler.cc (2)

1-39: LGTM! Clean implementation of the code event handler.

The implementation correctly:

  • Inherits from v8::CodeEventHandler and overrides the Handle method
  • Extracts all relevant information from the CodeEvent object
  • Creates a well-structured CodeEventInfo with proper move semantics
  • Enqueues the event for asynchronous processing

34-34:

✅ Verification successful

Verify that GetComment() never returns null.

The code directly passes the result of code_event->GetComment() to std::string constructor. If GetComment() can return a null pointer, this would cause undefined behavior.


🏁 Script executed:

#!/bin/bash
# Check the V8 header files or documentation for GetComment() behavior
rg -A 5 "GetComment" --glob "*.h" --glob "*.cc" deps/v8

Length of output: 5140


Confirmed GetComment() always returns a non-null C-string

V8’s own code and tests assume it never yields a null pointer:

  • In deps/v8/src/api/api.cc:

    const char* CodeEvent::GetComment() {
      return reinterpret_cast<i::CodeEvent*>(this)->comment;
    }

    The internal comment field is always initialized to point at a valid string (empty or otherwise).

  • In deps/v8/src/codegen/code-comments.cc:

    const char* CodeCommentsIterator::GetComment() const {
      const char* comment_string = …;
      CHECK_EQ(GetCommentSize(), strlen(comment_string) + 1);
      return comment_string;
    }

    The CHECK_EQ guarantees comment_string is non-null and null-terminated.

  • V8 unit tests (e.g. logging/log-unittest.cc) call std::string(code_event->GetComment()) without guarding against null, and would crash if GetComment() could ever return nullptr.

No defensive null check is needed before constructing std::string from GetComment().

test/cctest/test_nsolid_thread_safe.cc (2)

53-72: Well-structured test for the enhanced for_each method.

The test properly verifies that:

  • The callback receives both the value and the current list size
  • The size remains consistent (3) during iteration
  • Values are visited in the expected order

74-83: Good test coverage for the erase method returning size.

The test correctly verifies that erase returns the updated list size after each removal.

src/nsolid.cc (1)

600-618: Clean implementation of the CodeEventHook internal management.

The Impl class properly encapsulates the hook lifecycle:

  • Adds the hook to EnvList on setup
  • Removes it on destruction
  • Follows RAII principles
src/nsolid/nsolid_api.cc (2)

1120-1123: Dependent on previous fix – keep eye on enable logic

This check is correct only when the race in AddCodeEventHook() is fixed.
Otherwise size() could still be zero here and the handler would never be
enabled for this environment.


844-852: 🛠️ Refactor suggestion

EnvInst::setup_code_event_handler() leaks the old handler on re-enable

If a handler was created previously and Disable() was called, code_event_handler_ still holds the (disabled) instance.
Calling setup_code_event_handler() again will therefore not create a fresh handler nor re-enable the old one.

 void EnvInst::setup_code_event_handler() {
-  if (!code_event_handler_) {
-    code_event_handler_ =
-      std::make_unique<NSolidCodeEventHandler>(isolate_, thread_id_);
-  }
-
-  code_event_handler_->Enable();
+  if (!code_event_handler_)
+    code_event_handler_ =
+        std::make_unique<NSolidCodeEventHandler>(isolate_, thread_id_);
+
+  code_event_handler_->Enable();            // idempotent – safe to call twice
 }

Consider resetting the unique-ptr in disable_code_event_handler() (see next comment) so that a brand-new handler is constructed next time, avoiding surprises caused by a lingering disabled instance.

Likely an incorrect or invalid review comment.

src/nsolid.h (5)

290-290: LGTM!

The function pointer signature is correctly defined to handle code events with the appropriate parameters.


320-320: LGTM!

The proxy function declaration follows the established pattern for other hook proxies in the codebase.


356-358: LGTM!

The internal function declaration correctly returns a CodeEventHook* and takes the necessary parameters for hook registration.


691-692: LGTM!

The friend declaration is necessary to allow the internal function to construct CodeEventHook objects while keeping the constructor private.


1930-1935: LGTM!

The proxy implementation correctly moves the CodeEventInfo to avoid unnecessary copying of the potentially large struct.

src/nsolid/nsolid_api.h (5)

21-21: LGTM!

The include is necessary for the new async queue functionality used for code event dispatching.


427-427: LGTM!

The unique pointer correctly manages the lifetime of the code event handler.


504-507: LGTM!

The struct follows the established pattern for hook storage with proper callback and user data management.


757-758: LGTM!

The async queue and hook list are appropriately placed for managing code event distribution to registered hooks.


544-547:

✅ Verification successful

Verify thread safety of the hook list operations.

The methods operate on TSList which should be thread-safe based on the naming convention. However, ensure that the iterator returned by AddCodeEventHook remains valid across concurrent operations.


🏁 Script executed:

#!/bin/bash
# Check TSList implementation for thread safety guarantees
rg -A 10 "class TSList" src/nsolid/thread_safe.h

Length of output: 50


We need to locate the TSList definition to verify its thread-safety guarantees. Let’s search the entire repository:


🏁 Script executed:

#!/bin/bash
# Search for TSList class definition and usages across the repo
rg -n "class TSList" -n .
rg -n "TSList<" -n .

Length of output: 2633


Continuing verification: locate the full TSList implementation to confirm iterator safety.

Adjust search if needed based on the results.


🏁 Script executed:

#!/bin/bash
# Show TSList implementation around its declaration
rg -n -A20 -B5 "struct TSList<DataType\*>" src/nsolid/thread_safe.h

Length of output: 918


Continuing verification: extract the full TSList<DataType*> specialization (including any erase/remove methods) to confirm that all list operations are mutex-protected.


🏁 Script executed:

#!/bin/bash
# Print lines 170–350 of thread_safe.h to capture the entire TSList<DataType*> block
sed -n '170,350p' src/nsolid/thread_safe.h

Length of output: 1235


Thread safety and iterator validity confirmed

The TSList<DataType*> specialization in src/nsolid/thread_safe.h uses a single nsuv::ns_mutex to guard all push_back, erase, size, and for_each operations.

  • push_back acquires the lock, appends the element, and returns a std::list iterator. According to C++ guarantees for std::list, this iterator remains valid across subsequent insertions.
  • Only calling erase(iterator) (i.e. via RemoveCodeEventHook) will invalidate that specific iterator—this matches the intended removal semantics.

No further changes are required.

Comment on lines +1712 to +1713
envlist->on_code_event_q_.reset();
if (envlist->continuous_profiler_) {
envlist->continuous_profiler_.reset();
}

Copy link

@coderabbitai coderabbitai bot Jun 10, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Queue reset before drain – possible event drop

on_code_event_q_.reset() is called unconditionally during shutdown. If
there are still pending code events they will be destroyed silently, skipping
user hooks and making debugging harder.

Consider:

  1. Draining the queue synchronously (dequeue until empty) before reset.
  2. Or calling reset() after confirming thread_loop_ has stopped
    processing callbacks.
🤖 Prompt for AI Agents
In src/nsolid/nsolid_api.cc around lines 1712 to 1716, the on_code_event_q_
queue is reset unconditionally during shutdown, which can cause pending code
events to be dropped silently. To fix this, modify the shutdown sequence to
first drain the queue synchronously by dequeuing all events until it is empty
before calling reset(), or ensure that thread_loop_ has fully stopped processing
callbacks before resetting the queue. This prevents loss of events and ensures
user hooks are executed properly.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think this is relevant once EnvList is gone

Copy link

Choose a reason for hiding this comment

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

@santigimeno, understood! If EnvList is planned to be removed in future refactoring, then improving the queue draining behavior in the current implementation wouldn't provide much value. Thanks for the clarification on the architectural direction.

Adds a for_each method to TSList that passes the current list size and
item to the callback.
Updates erase to return the new size after removal.
These changes improve thread-safe iteration and management of hooks.
@santigimeno santigimeno force-pushed the santi/jit_code_event_handler branch from 62ac188 to ba37b75 Compare June 10, 2025 11:00
Introduces a new API to register code event hooks in nsolid. This
includes new handler classes, queueing, and infrastructure to allow
extensible code event observation and callbacks.
@santigimeno santigimeno force-pushed the santi/jit_code_event_handler branch from ba37b75 to 16f39c0 Compare June 10, 2025 11:02
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
test/addons/nsolid-code-event-hook/binding.cc (1)

53-58: ⚠️ Potential issue

Add error handling for V8 operations.

The code uses ToLocalChecked() without error handling, which will crash the process if the operations fail.

-      Local<Value> argv[2] = {
-        String::NewFromUtf8(isolate, info.fn_name.c_str()).ToLocalChecked(),
-        Number::New(isolate, info.thread_id),
-      };
-      // Call the callback in the current context
-      cb->Call(isolate->GetCurrentContext(),
-               Undefined(isolate), 2, argv).ToLocalChecked();
+      v8::TryCatch try_catch(isolate);
+      Local<String> fn_name;
+      if (!String::NewFromUtf8(isolate, info.fn_name.c_str()).ToLocal(&fn_name)) {
+        // Handle string creation failure
+        return;
+      }
+      Local<Value> argv[2] = {
+        fn_name,
+        Number::New(isolate, info.thread_id),
+      };
+      // Call the callback in the current context
+      Local<Value> ret;
+      if (!cb->Call(isolate->GetCurrentContext(),
+                    Undefined(isolate), 2, argv).ToLocal(&ret)) {
+        // Handle exception if needed
+        if (try_catch.HasCaught()) {
+          try_catch.ReThrow();
+        }
+      }
🧹 Nitpick comments (3)
test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js (3)

1-1: Remove redundant 'use strict' directive.

JavaScript modules are automatically in strict mode.

-'use strict';
-
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)


9-10: Improve thread check readability.

The condition is checking if the process is a worker thread but not launched with the correct PID argument.

-if (!isMainThread && +process.argv[2] !== process.pid)
+// Skip if this is a worker thread that wasn't launched by the main test
+if (!isMainThread && +process.argv[2] !== process.pid)

46-73: Consider making the test more deterministic.

The test uses multiple fixed 500ms timeouts which could lead to flaky tests on heavily loaded systems. Consider using a more deterministic approach.

Would you like me to suggest an implementation using promises or event-based synchronization instead of fixed timeouts to make the test more reliable?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62ac188 and 16f39c0.

📒 Files selected for processing (12)
  • node.gyp (1 hunks)
  • src/nsolid.cc (2 hunks)
  • src/nsolid.h (7 hunks)
  • src/nsolid/nsolid_api.cc (8 hunks)
  • src/nsolid/nsolid_api.h (11 hunks)
  • src/nsolid/nsolid_code_event_handler.cc (1 hunks)
  • src/nsolid/nsolid_code_event_handler.h (1 hunks)
  • src/nsolid/thread_safe.h (2 hunks)
  • test/addons/nsolid-code-event-hook/binding.cc (1 hunks)
  • test/addons/nsolid-code-event-hook/binding.gyp (1 hunks)
  • test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js (1 hunks)
  • test/cctest/test_nsolid_thread_safe.cc (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • test/addons/nsolid-code-event-hook/binding.gyp
  • node.gyp
  • src/nsolid/nsolid_code_event_handler.cc
  • src/nsolid/nsolid_code_event_handler.h
  • src/nsolid/thread_safe.h
  • test/cctest/test_nsolid_thread_safe.cc
  • src/nsolid.cc
  • src/nsolid/nsolid_api.cc
  • src/nsolid.h
  • src/nsolid/nsolid_api.h
🧰 Additional context used
🧠 Learnings (1)
test/addons/nsolid-code-event-hook/binding.cc (1)
Learnt from: santigimeno
PR: nodesource/nsolid#327
File: test/addons/nsolid-code-event-hook/binding.cc:64-78
Timestamp: 2025-06-10T09:52:49.401Z
Learning: In the nsolid codebase, test files in test/addons/ consistently use assert() for input validation and runtime checks, not CHECK() macros. This is the established testing convention.
🧬 Code Graph Analysis (2)
test/addons/nsolid-code-event-hook/binding.cc (4)
src/nsolid.cc (12)
  • CodeEventHook (620-621)
  • CodeEventHook (623-623)
  • cb (610-615)
  • cb (610-612)
  • GetThreadId (56-64)
  • GetThreadId (56-56)
  • GetLocalEnvInst (43-45)
  • GetLocalEnvInst (43-43)
  • GetLocalEnvInst (47-49)
  • GetLocalEnvInst (47-47)
  • IsMainThread (91-96)
  • IsMainThread (91-91)
src/nsolid.h (15)
  • CodeEventHook (679-702)
  • CodeEventHook (690-690)
  • CodeEventHook (693-693)
  • RunCommand (523-526)
  • RunCommand (1602-1626)
  • RunCommand (1602-1605)
  • cb (658-658)
  • thread_id (852-852)
  • thread_id (852-852)
  • GetThreadId (404-404)
  • GetLocalEnvInst (385-385)
  • GetLocalEnvInst (391-391)
  • AddCodeEventHook (1842-1860)
  • AddCodeEventHook (1842-1842)
  • IsMainThread (426-426)
src/nsolid/nsolid_api.cc (8)
  • RunCommand (157-202)
  • RunCommand (157-160)
  • GetCurrent (433-436)
  • GetCurrent (433-433)
  • GetCurrent (439-442)
  • GetCurrent (439-439)
  • AddCodeEventHook (1002-1028)
  • AddCodeEventHook (1002-1005)
test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js (1)
  • assert (4-4)
test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js (1)
test/common/index.js (1)
  • buildType (155-157)
🪛 Biome (1.9.4)
test/addons/nsolid-code-event-hook/nsolid-code-event-hook.js

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)

⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: test-tarball-linux
  • GitHub Check: test-linux (ubuntu-24.04)
  • GitHub Check: test-linux (ubuntu-24.04-arm)
  • GitHub Check: test-macOS
  • GitHub Check: coverage-linux-without-intl
  • GitHub Check: coverage-linux
  • GitHub Check: coverage-windows
🔇 Additional comments (1)
test/addons/nsolid-code-event-hook/binding.cc (1)

38-61: ⚠️ Potential issue

Add thread safety for js_callback_per_thread access.

The CodeEventCallback function accesses holder->js_callback_per_thread without holding the mutex, while RegisterJSCodeEventCallback modifies it under the mutex lock. This creates a potential data race.

Since the map access happens in a lambda that runs on the event loop, you need to ensure thread-safe access:

 void CodeEventCallback(std::shared_ptr<node::nsolid::EnvInst> envinst,
                        const node::nsolid::CodeEventInfo& info,
                        HookHolder* holder) {
   assert(0 == node::nsolid::RunCommand(
       envinst,
       node::nsolid::CommandType::EventLoop,
       [holder](node::nsolid::SharedEnvInst envinst,
                const node::nsolid::CodeEventInfo& info) {
+    nsuv::ns_mutex::scoped_lock lock(hooks_mutex_);
     // Call JS callback with function name
     if (!holder->js_callback_per_thread[info.thread_id].IsEmpty()) {
       Isolate* isolate = Isolate::GetCurrent();
       v8::HandleScope scope(isolate);
       Local<Function> cb =
         holder->js_callback_per_thread[info.thread_id].Get(isolate);
+      lock.unlock();  // Release lock before calling JS
       Local<Value> argv[2] = {
         String::NewFromUtf8(isolate, info.fn_name.c_str()).ToLocalChecked(),
         Number::New(isolate, info.thread_id),
       };
       // Call the callback in the current context
       cb->Call(isolate->GetCurrentContext(),
                Undefined(isolate), 2, argv).ToLocalChecked();
     }
   }, info));
 }

Likely an incorrect or invalid review comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant