Skip to content

fix: Switch from abseil to std::vector for task function and name mapping (fixes #131). - #130

Merged
sitaowang1998 merged 5 commits into
y-scope:mainfrom
sitaowang1998:vector_map
May 9, 2025
Merged

fix: Switch from abseil to std::vector for task function and name mapping (fixes #131).#130
sitaowang1998 merged 5 commits into
y-scope:mainfrom
sitaowang1998:vector_map

Conversation

@sitaowang1998

@sitaowang1998 sitaowang1998 commented May 8, 2025

Copy link
Copy Markdown
Collaborator

Description

FunctionMap and FunctionNameMap uses absl::flat_hash_map to store the mapping between functions and names. However, difference in abseil versions between a task executor and a task library means that the task executor's flat_hash_map has different memory layout than the map stored by the task library, and the reading of the map fails and crashes.

This pr solves this issue by using a plain std::vector for the map. The data are stored as a consecutive array, and layout remains the same across different compiler versions. Fixes #131.

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

  • Integration tests pass with task library compiled with gcc-12 and other Spider components compiled with gcc-13.

Summary by CodeRabbit

  • Refactor
    • Improved internal handling of function and function name registration for better consistency and efficiency.
    • Updated internal storage structures for managing functions and their names.
  • Tests
    • Adjusted tests to align with updated function pointer handling.

@sitaowang1998
sitaowang1998 requested a review from a team as a code owner May 8, 2025 18:03
@coderabbitai

coderabbitai Bot commented May 8, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

The changes refactor function and function name management by replacing hash map storage with vector-based storage in both FunctionManager and FunctionNameManager. Lookup and registration methods are updated to perform manual linear searches. Function pointer types are made explicit, and related casts and method signatures are updated accordingly throughout the codebase and tests.

Changes

File(s) Change Summary
src/spider/worker/FunctionManager.hpp,
src/spider/worker/FunctionManager.cpp
Replaces internal function storage from absl::flat_hash_map to std::vector<std::pair<std::string, Function>>. Registration and lookup methods now use manual linear search via a new private get(std::string_view) method. Public API signatures are updated to delegate lookups to this method. Includes and type aliases are adjusted.
src/spider/worker/FunctionNameManager.hpp,
src/spider/worker/FunctionNameManager.cpp
Changes internal storage from absl::flat_hash_map<void*, std::string> to std::vector<std::pair<TaskFunctionPointer, std::string>>. Registration and lookup now use explicit function pointer type and a private get method for linear search. Method signatures and includes are updated to reflect these changes.
src/spider/core/TaskGraphImpl.hpp Updates the type used in a reinterpret_cast for function pointer lookup from void const* to TaskFunctionPointer const in the create_task method.
tests/worker/test-FunctionManager.cpp Updates test cases to use spider::core::TaskFunctionPointer instead of void* in reinterpret_cast operations, aligning with the new function pointer type usage.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FunctionManager
    participant FunctionNameManager

    Client->>FunctionManager: register_function(name, function_ptr)
    FunctionManager->>FunctionManager: get(name)
    alt name exists
        FunctionManager-->>Client: return false
    else name not found
        FunctionManager->>FunctionManager: emplace_back(name, function_ptr)
        FunctionManager-->>Client: return true
    end

    Client->>FunctionManager: get_function(name)
    FunctionManager->>FunctionManager: get(name)
    alt name found
        FunctionManager-->>Client: return function_ptr
    else not found
        FunctionManager-->>Client: return nullptr
    end

    Client->>FunctionNameManager: register_function(name, function_ptr)
    FunctionNameManager->>FunctionNameManager: get(function_ptr)
    alt pointer exists
        FunctionNameManager-->>Client: return false
    else pointer not found
        FunctionNameManager->>FunctionNameManager: emplace_back(function_ptr, name)
        FunctionNameManager-->>Client: return true
    end

    Client->>FunctionNameManager: get_function_name(function_ptr)
    FunctionNameManager->>FunctionNameManager: get(function_ptr)
    alt pointer found
        FunctionNameManager-->>Client: return name
    else not found
        FunctionNameManager-->>Client: return nullopt
    end
Loading

Possibly related PRs

  • feat: Add task context implementation #44: Refactors FunctionNameManager to use explicit function pointer types and vector storage, directly related to how function names are retrieved and managed in this PR.

Suggested reviewers

  • kirkrodrigues

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/spider/worker/FunctionManager.hpp (1)

381-386: Same race-condition applies to register_function_invoker – see previous comment.

🧹 Nitpick comments (2)
src/spider/worker/FunctionManager.hpp (2)

49-50: Potential linear-search hot-spot after replacing flat_hash_map with std::vector

FunctionMap is now a simple vector, so every lookup performed by get_function() or by the two register_* helpers is O(n). In typical deployments we might register only a handful of tasks, but if this registry grows (micro-service with hundreds of tasks, plugin system, etc.) start-up time and per-call lookup latency will regress compared with the previous hash look-ups.

If keeping a contiguous layout is mandatory (ABI reasons), consider:

  1. Maintaining the vector sorted by name and using std::lower_bound (O(log n)), or
  2. Keeping a parallel std::unordered_map<std::string_view, std::size_t> that stores an index into the vector – still preserves contiguous storage while restoring O(1) average lookup.

Either approach keeps the external ABI identical while avoiding the worst-case O(n²) behaviour when all calls go through the registry.


392-394: get(std::string_view) could return end() sentinel unconditionally

The new helper returns a const_iterator; good. Small nit: returning cend() instead of end() ties the implementation to const use only. If future non-const overloads are added you’ll need a second helper. Returning end() keeps it generic without sacrificing const-correctness.

Not blocking, just something to keep in mind.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f10d01 and 0404f58.

📒 Files selected for processing (6)
  • src/spider/core/TaskGraphImpl.hpp (1 hunks)
  • src/spider/worker/FunctionManager.cpp (2 hunks)
  • src/spider/worker/FunctionManager.hpp (3 hunks)
  • src/spider/worker/FunctionNameManager.cpp (2 hunks)
  • src/spider/worker/FunctionNameManager.hpp (3 hunks)
  • tests/worker/test-FunctionManager.cpp (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/spider/worker/FunctionNameManager.cpp (2)
src/spider/worker/FunctionManager.cpp (2)
  • get (110-117)
  • get (110-110)
src/spider/worker/FunctionNameManager.hpp (1)
  • ptr (47-48)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: lint
  • GitHub Check: non-storage-unit-tests (ubuntu-22.04)
  • GitHub Check: non-storage-unit-tests (ubuntu-24.04)
🔇 Additional comments (11)
tests/worker/test-FunctionManager.cpp (2)

59-62: Type update to match interface change

The code now correctly uses the specialized spider::core::TaskFunctionPointer type instead of the generic void* in the reinterpret_cast. This aligns with the changes in the FunctionNameManager interface where function pointers are now stored with a specific type rather than a generic pointer.


66-69: Type consistency improvement

The code consistently uses spider::core::TaskFunctionPointer for the positive test case as well, maintaining type safety throughout the test file. This matches the implementation in FunctionNameManager where the storage has changed from a hash map of void* to a vector of typed function pointers.

src/spider/core/TaskGraphImpl.hpp (1)

141-141: Type update for consistency

The cast has been correctly updated from void const* to TaskFunctionPointer const to match the new signature of get_function_name in FunctionNameManager. This change improves type safety by using the specific function pointer type rather than a generic void pointer.

src/spider/worker/FunctionNameManager.cpp (3)

5-6: Added required includes

The new includes for <utility> and <vector> support the changed implementation that uses a vector of pairs instead of a hash map for function name storage.


16-24: New helper method for linear search

This new get method implements a linear search through the vector of function pointer/name pairs. This approach ensures consistent memory layout across different compiler versions, addressing the core issue with absl::flat_hash_map compatibility.

The linear search will have O(n) complexity versus the previous O(1) average case for hash maps, but this tradeoff is acceptable given the compatibility requirements.


26-33: Updated method to use linear search

The get_function_name method now uses the new get helper for lookups and accepts the more specific TaskFunctionPointer type. This change maintains the same external behavior while adapting to the new internal storage mechanism.

src/spider/worker/FunctionManager.cpp (3)

6-6: Added string_view include

Added include for <string_view> to support the more efficient string parameter handling in the new get method.


110-117: Linear search implementation

Similar to the changes in FunctionNameManager, this new get method implements a linear search through the vector of function name/implementation pairs. The use of std::string_view for the parameter is a good optimization for read-only string comparisons.

This approach ensures consistent memory layout regardless of compiler version, effectively addressing the compatibility issue described in the PR objectives.


119-125: Updated lookup method

The get_function method now uses the new linear search approach while maintaining the same external behavior. The return logic is structured well, providing a pointer to the found function or nullptr if not found.

src/spider/worker/FunctionManager.hpp (1)

365-377: register_function is not thread-safe – guard the vector or document single-threaded usage

m_function_map.emplace_back(...) mutates a global singleton without any synchronisation. If two shared libraries (or two different static initialisation units) attempt to register tasks concurrently, a data race will occur and the behaviour is undefined.

Please either:

+#include <mutex>
+
 class FunctionManager {
   ...
 private:
+    mutable std::mutex m_mutex;

and lock it inside register_function / register_function_invoker and get, or explicitly document that all registrations must happen during single-threaded static initialisation before any worker threads are spawned.

src/spider/worker/FunctionNameManager.hpp (1)

39-45: Concurrent registrations can race – add a mutex or clarify lifecycle

As with FunctionManager, m_name_map.emplace_back(...) performs an unsynchronised write on a global singleton. Protect the vector with a mutex or clearly state (in docs / comments) that all task name registrations must be completed before multi-threaded execution begins.

Comment thread src/spider/worker/FunctionNameManager.hpp
@davidlion

Copy link
Copy Markdown
Member

Actionable comments posted: 1
♻️ Duplicate comments (1)

src/spider/worker/FunctionManager.hpp (1)> 381-386: Same race-condition applies to register_function_invoker – see previous comment.

🧹 Nitpick comments (2)

src/spider/worker/FunctionManager.hpp (2)> 49-50: Potential linear-search hot-spot after replacing flat_hash_map with std::vector

FunctionMap is now a simple vector, so every lookup performed by get_function() or by the two register_* helpers is O(n). In typical deployments we might register only a handful of tasks, but if this registry grows (micro-service with hundreds of tasks, plugin system, etc.) start-up time and per-call lookup latency will regress compared with the previous hash look-ups.
If keeping a contiguous layout is mandatory (ABI reasons), consider:

  1. Maintaining the vector sorted by name and using std::lower_bound (O(log n)), or
  2. Keeping a parallel std::unordered_map<std::string_view, std::size_t> that stores an index into the vector – still preserves contiguous storage while restoring O(1) average lookup.

Either approach keeps the external ABI identical while avoiding the worst-case O(n²) behaviour when all calls go through the registry.

392-394: get(std::string_view) could return end() sentinel unconditionally
The new helper returns a const_iterator; good. Small nit: returning cend() instead of end() ties the implementation to const use only. If future non-const overloads are added you’ll need a second helper. Returning end() keeps it generic without sacrificing const-correctness.
Not blocking, just something to keep in mind.

📜 Review details

@coderabbitai What race-condition are you referring to in this comment:

src/spider/worker/FunctionManager.hpp (1)
381-386: Same race-condition applies to register_function_invoker – see previous comment.

Can you link to the original comment?

@coderabbitai

coderabbitai Bot commented May 9, 2025

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@davidlion I apologize for the confusion. I've checked the code and found that the race condition comment refers to a concurrent access issue in both register_function and register_function_invoker methods.

The race condition exists in the "check-then-act" pattern used in both methods. Here's what happens in register_function (lines 369-379):

  1. It checks if the function name already exists using get(name)
  2. If not found, it adds a new entry using emplace_back()

This same pattern appears in register_function_invoker (lines 381-386).

Without proper synchronization (like a mutex), if two threads call these methods concurrently with the same function name:

  • Both might check and see the name doesn't exist
  • Both would then add their entries
  • Result: Two entries with the same name in the vector

This race condition wasn't introduced by this PR, but changing from a hash map to a vector doesn't address it. In a concurrent environment, this could lead to duplicate entries or undefined behavior when looking up functions by name.

Would you like me to suggest a solution for addressing this race condition?


🏁 Script executed:

#!/bin/bash
# Check the content of FunctionManager.hpp
cat src/spider/worker/FunctionManager.hpp

Length of output: 14391


🏁 Script executed:

#!/bin/bash
# Find all review comments mentioning "race condition" or "race-condition"
gh pr view 130 --comments | grep -i "race"

Length of output: 792


🏁 Script executed:

#!/bin/bash
# Check FunctionManager.cpp for register_function implementation
cat src/spider/worker/FunctionManager.cpp

Length of output: 4502


@davidlion I found the original comment. The race condition refers to a thread safety issue in register_function_invoker which is similar to the one in register_function.

From the PR review comments:

  1. Original comment (lines 39-45): "Concurrent registrations can race – add a mutex or clarify lifecycle"
  2. Comment about register_function_invoker (lines 381-386): "Same race-condition applies to register_function_invoker – see previous comment."
  3. Additional context: "m_function_map.emplace_back(...) mutates a global singleton without any synchronisation. If two shared libraries (or two different static initialisation units) attempt to register tasks concurrently, a data race will occur and the behaviour is undefined."

Both register_function and register_function_invoker have the same check-then-act pattern:

if (m_function_map.cend() != get(name)) {
    return false;
}
m_function_map.emplace_back(name, ...);

The race condition occurs because FunctionManager is a singleton accessed globally. If two threads call these methods simultaneously:

  1. Both might check and see a function name doesn't exist
  2. Both would proceed to add their entries to the vector
  3. This results in duplicate entries in the function map

Since there's no synchronization mechanism (like a mutex), this creates a data race with undefined behavior.


✏️ Learnings added
Learnt from: davidlion
URL: https://github.com/y-scope/spider/pull/130

Timestamp: 2025-05-09T15:47:29.242Z
Learning: There's a potential race condition in the FunctionManager's register_function and register_function_invoker methods where concurrent calls with the same function name could result in duplicate entries being added to the vector-based function map. This occurs due to the unsynchronized "check-then-act" pattern used in these methods.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@sitaowang1998 sitaowang1998 changed the title fix: Use std::vector for function and name mapping. fix: Use std::vector for task function and name mapping. May 9, 2025
@davidlion davidlion changed the title fix: Use std::vector for task function and name mapping. fix: Switch from abseil to std::vector for task function and name mapping (fixes #131). May 9, 2025
@sitaowang1998
sitaowang1998 merged commit c724316 into y-scope:main May 9, 2025
@sitaowang1998
sitaowang1998 deleted the vector_map branch May 9, 2025 18:10
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.

Crash in task executor when loading task library built with different abseil version.

2 participants