Skip to content

Conversation

@facontidavide
Copy link
Collaborator

@facontidavide facontidavide commented Feb 3, 2026

Summary

This PR significantly improves test suite quality and increases code coverage from 69.5% to 77.1% (+7.6 percentage points).

New Test Files

File Tests Coverage Impact
gtest_basic_types.cpp 32 toStr(), convertFromString(), splitString()
gtest_if_then_else.cpp 9 IfThenElseNode: 0% -> 100%
gtest_while_do_else.cpp 11 WhileDoElseNode: 0% -> 100%
gtest_loggers.cpp 23 All 4 loggers: 0% -> 84-100%
gtest_loop.cpp 15 loop_node.h: 65.9% -> 98.8%

Enhanced Existing Tests

File New Tests Improvements
gtest_decorator.cpp 8 ForceFailure, ForceSuccess, Inverter, KeepRunningUntilFailure
gtest_reactive.cpp 7 Halt propagation, condition re-evaluation, fallback edge cases

Bug Fixes (discovered by new tests)

  • FileLogger2 race condition: Fixed TSAN failures caused by callbacks firing before writer thread was ready. Added atomic ready flag for proper synchronization.
  • int64_t/uint64_t conversion on Windows: Fixed cross-platform bug where convertFromString<int64_t> and convertFromString<uint64_t> used long/unsigned long (32-bit on Windows) instead of proper 64-bit types.

Code Quality Fixes

  • Removed debug std::cout statements from gtest_coroutines.cpp and script_parser_test.cpp
  • Deleted empty unused gtest_scripting.cpp
  • Fixed timing fragility with named constants
  • Reduced test durations from 200-500ms to 10-50ms

Coverage Metrics

Metric Before After Change
Line Coverage 69.5% 77.1% +7.6%
Function Coverage 71.0% 74.6% +3.6%
Total Tests 362 442 +80

Key File Improvements

File Before After
bt_cout_logger.cpp 0% 100%
bt_file_logger_v2.cpp 0% 98.4%
bt_sqlite_logger.cpp 0% 90.1%
bt_minitrace_logger.cpp 0% 84.6%
loop_node.h 65.9% 98.8%
if_then_else_node.cpp 0% 100%
while_do_else_node.cpp 0% 100%
reactive_fallback.cpp 69.2% 74.4%

All 442 tests pass.

Summary by CodeRabbit

  • Tests

    • Added extensive new test coverage for control-flow nodes, reactive behavior, loops, coroutines, logging, basic types, and utilities.
  • Improvements

    • More robust logging subsystem with deferred startup and synchronized background writing for safer, more reliable log output.
  • Bug Fixes

    • Removed noisy diagnostic output from a test helper for cleaner test logs.
  • Chores

    • Ignore coverage_report directory in version control.

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (6)
  • .github/workflows/cmake_ubuntu.yml.disabled
  • .github/workflows/cmake_windows.yml.disabled
  • .github/workflows/pixi.yaml.disabled
  • .github/workflows/pre-commit.yaml.disabled
  • .github/workflows/ros2-rolling.yaml.disabled
  • .github/workflows/ros2.yaml.disabled

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
📝 Walkthrough

Walkthrough

Adds multiple GoogleTest suites, refactors loggers to use deferred subscription with a dedicated writer thread and synchronization, tightens integer parsing types, and updates repository ignore rules.

Changes

Cohort / File(s) Summary
Test Registration
tests/CMakeLists.txt
Adds new test sources to BT_TESTS (gtest_basic_types.cpp, gtest_if_then_else.cpp, gtest_loggers.cpp, gtest_loop.cpp, gtest_while_do_else.cpp).
New & Expanded Tests
tests/gtest_basic_types.cpp, tests/gtest_if_then_else.cpp, tests/gtest_loggers.cpp, tests/gtest_loop.cpp, tests/gtest_while_do_else.cpp, tests/gtest_reactive.cpp
Introduces five new comprehensive GTest suites and extends reactive/decorator/coroutine tests; covers conversions, control nodes (IfThenElse, WhileDoElse, Loop), loggers, and reactive behaviors.
Test adjustments & helpers
tests/gtest_coroutines.cpp, tests/gtest_decorator.cpp, tests/script_parser_test.cpp
Adds timing constants, test helper methods (wasHalted, setRequiredTime), removes a debug print, and adjusts test timings/expectations.
Logger refactor (deferred subscription + writer thread)
include/behaviortree_cpp/loggers/abstract_logger.h, include/behaviortree_cpp/loggers/bt_file_logger_v2.h, src/loggers/bt_file_logger_v2.cpp, src/loggers/bt_sqlite_logger.cpp
Adds protected default ctor and subscribeToTreeChanges for deferred subscription; renames PImpl→Pimpl; adds file mutex, writer thread, atomic loop flag and synchronized writer loop; defers subscription in SqliteLogger; deletes move ops where applicable; adjusts enqueue/flush synchronization.
Type parsing precision
src/basic_types.cpp
Changes local accumulator types to int64_t/uint64_t for integer parsing to match from_chars target types.
Repo config
.gitignore
Adds coverage_report/* to ignore rules.

Sequence Diagram(s)

sequenceDiagram
    participant TestRunner as Test Runner
    participant Tree as BehaviorTree::Tree
    participant Logger as FileLogger2/SqliteLogger
    participant Writer as WriterThread
    participant Storage as File/Database

    Note over Logger,Writer: Construction (deferred subscription)
    TestRunner->>Logger: construct Logger (calls StatusChangeLogger())
    Logger->>Writer: start writer_thread
    Writer-->>Logger: writer ready
    Logger->>Tree: subscribeToTreeChanges(tree.rootNode())
    Tree-->>Logger: status change callbacks (enqueue Transition)

    alt status change occurs
        Logger->>Writer: enqueue Transition (notify)
        Writer->>Logger: pop batch under lock
        Writer->>Storage: write batch, flush (file_mutex held)
        Storage-->>Writer: write complete
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #1047: Overlapping logger refactor (PImpl→Pimpl rename, writer thread and deferred subscription changes) — strong code-level relation.

Poem

🐇 I hopped through tests in eager rows,
Deferred my watch until the writer glows.
Threads hum softly, mutexes align,
Counters tally, conversions fine.
Hop—these trees now pass in time!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Improve test suite quality and coverage' directly aligns with the main objectives: expanding test coverage from 69.5% to 77.1%, adding 80 new tests across multiple files, and fixing bugs discovered through testing.
Description check ✅ Passed The PR description is comprehensive and well-structured. It includes a detailed summary section with tables showing new test files, enhanced existing tests, bug fixes, coverage metrics, and key file improvements. However, it does not follow the provided template which specifies instructions about comments, API/ABI changes, unit tests for bugs, and linting/clang-tidy requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch improve-test-suite-coverage

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 1

🤖 Fix all issues with AI agents
In `@tests/gtest_while_do_else.cpp`:
- Around line 139-165: The test named ConditionChanges_HaltsElse doesn't
actually exercise halt-on-condition-change because it calls tickWhileRunning()
once and the else branch completes; change the test to drive the tree with
tickOnce() across multiple ticks using an asynchronous action for TestB that
returns NodeStatus::RUNNING on the first tick and only completes later, register
ToggleCondition to flip from FAILURE to SUCCESS on the second tick, then assert
that When the condition flips the running else branch (TestB) receives a halt
and stops running and the then-branch (TestA) is executed; update references in
the test to use tickOnce(), the async TestB action (returns RUNNING then
halted), ToggleCondition, TestA/TestB and assert halt behavior via counters
and/or flags to prove haltChild() was invoked.
🧹 Nitpick comments (3)
tests/gtest_decorator.cpp (1)

59-59: Consider using a named constant for consistency.

Line 59 uses a hardcoded milliseconds(20) while the rest of the file now uses named constants like ACTION_SHORT_MS (which is also 20ms). For consistency with the timing constants pattern established in this file, consider using ACTION_SHORT_MS here.

♻️ Suggested refactor
-  RepeatTestAsync() : root("repeat", 3), action("action", milliseconds(20))
+  RepeatTestAsync() : root("repeat", 3), action("action", ACTION_SHORT_MS)
tests/gtest_basic_types.cpp (1)

186-191: Verify empty string split behavior.

The test asserts that splitting an empty string returns an empty vector. While this is reasonable behavior, some implementations return a vector with one empty string. Consider adding a comment documenting this expected behavior.

📝 Suggested documentation
 TEST(BasicTypes, SplitString_Empty)
 {
   auto parts = splitString("", ',');
-  // Empty string results in empty vector
+  // Empty string results in empty vector (not a single empty element)
   ASSERT_EQ(parts.size(), 0u);
 }
tests/gtest_loggers.cpp (1)

40-44: TearDown may fail silently if files are still open.

On some platforms, std::filesystem::remove_all may throw or fail silently if files are still open (e.g., if a test fails before closing a logger). Consider wrapping in a try-catch to prevent test framework issues.

🛡️ Suggested defensive cleanup
   void TearDown() override
   {
     // Clean up test files
-    std::filesystem::remove_all(test_dir);
+    try
+    {
+      std::filesystem::remove_all(test_dir);
+    }
+    catch(const std::filesystem::filesystem_error&)
+    {
+      // Ignore cleanup errors - may occur if files are locked
+    }
   }

Comment on lines +139 to +165
TEST_F(WhileDoElseTest, ConditionChanges_HaltsElse)
{
// When condition changes from false to true, else branch should be halted
int condition_counter = 0;
factory.registerSimpleCondition("ToggleCondition", [&condition_counter](BT::TreeNode&) {
return (condition_counter++ == 0) ? NodeStatus::FAILURE : NodeStatus::SUCCESS;
});

const std::string xml_text = R"(
<root BTCPP_format="4">
<BehaviorTree>
<WhileDoElse>
<ToggleCondition/>
<TestA/>
<TestB/>
</WhileDoElse>
</BehaviorTree>
</root>)";

auto tree = factory.createTreeFromText(xml_text);

// First tick - condition false, executes else (TestB)
auto status = tree.tickWhileRunning();
ASSERT_EQ(status, NodeStatus::SUCCESS);
ASSERT_EQ(counters[0], 0); // TestA not executed
ASSERT_EQ(counters[1], 1); // TestB executed
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find the WhileDoElse implementation
fd -type f -name "*.cpp" -o -name "*.h" -o -name "*.hpp" | xargs rg -l "WhileDoElse" | head -20

Repository: BehaviorTree/BehaviorTree.CPP

Length of output: 446


🏁 Script executed:

# Look at the test file to understand the full context
cat tests/gtest_while_do_else.cpp

Repository: BehaviorTree/BehaviorTree.CPP

Length of output: 9044


🏁 Script executed:

# Search for the WhileDoElse class implementation
fd -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) | xargs rg -l "class WhileDoElse"

Repository: BehaviorTree/BehaviorTree.CPP

Length of output: 345


🏁 Script executed:

# Read the WhileDoElse implementation
cat include/behaviortree_cpp/controls/while_do_else_node.h

Repository: BehaviorTree/BehaviorTree.CPP

Length of output: 2166


🏁 Script executed:

# Also check the cpp implementation
cat src/controls/while_do_else_node.cpp

Repository: BehaviorTree/BehaviorTree.CPP

Length of output: 2476


Test name is misleading—doesn't verify the claimed halt-on-condition-change behavior.

The test name ConditionChanges_HaltsElse suggests it verifies halting of a running else branch when the condition toggles from false to true. However, tickWhileRunning() executes the tree to completion in a single call, and the condition is evaluated once per tick. Within that single tick, the condition returns FAILURE (first counter check), executes the else branch, completes successfully, and returns. The toggle condition is never actually reached because the tree terminates after the first tick.

The WhileDoElse implementation does support halting running branches when conditions change (via haltChild() calls), but this test doesn't demonstrate it. To verify this behavior, use an async action with tickOnce() calls to show a running branch being halted when the condition changes on a subsequent tick.

🤖 Prompt for AI Agents
In `@tests/gtest_while_do_else.cpp` around lines 139 - 165, The test named
ConditionChanges_HaltsElse doesn't actually exercise halt-on-condition-change
because it calls tickWhileRunning() once and the else branch completes; change
the test to drive the tree with tickOnce() across multiple ticks using an
asynchronous action for TestB that returns NodeStatus::RUNNING on the first tick
and only completes later, register ToggleCondition to flip from FAILURE to
SUCCESS on the second tick, then assert that When the condition flips the
running else branch (TestB) receives a halt and stops running and the
then-branch (TestA) is executed; update references in the test to use
tickOnce(), the async TestB action (returns RUNNING then halted),
ToggleCondition, TestA/TestB and assert halt behavior via counters and/or flags
to prove haltChild() was invoked.

facontidavide and others added 4 commits February 3, 2026 17:06
Test Coverage Improvements:
- Add gtest_basic_types.cpp: Tests for toStr(), convertFromString(), splitString()
- Add gtest_if_then_else.cpp: Tests for IfThenElseNode control node
- Add gtest_while_do_else.cpp: Tests for WhileDoElseNode control node
- Add gtest_loggers.cpp: Tests for StdCoutLogger, FileLogger2, MinitraceLogger, SqliteLogger
- Add decorator tests: ForceFailure, ForceSuccess, Inverter, KeepRunningUntilFailure

Critical Fixes from Test Review:
- Remove debug std::cout statements from gtest_coroutines.cpp and script_parser_test.cpp
- Delete empty unused gtest_scripting.cpp (script_parser_test.cpp provides coverage)
- Fix timing fragility: Replace magic numbers with named constants
- Reduce test durations from 200-500ms to 10-50ms for faster CI execution

All 420 tests pass.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
New tests:
- gtest_loop.cpp: 15 tests for LoopNode decorator
  - Static queue parsing (int, double, string, bool)
  - Empty queue handling (if_empty parameter)
  - Dynamic queue from blackboard
  - Vector input conversion (Issue #969)
  - Child failure behavior
  - Restart after completion
  - convertFromString tests for SharedQueue types

- gtest_reactive.cpp: 7 additional tests
  - ReactiveSequence first child fails
  - ReactiveSequence halt on condition change
  - ReactiveFallback first child succeeds
  - ReactiveFallback all children fail
  - ReactiveSequence all children succeed
  - ReactiveSequence re-evaluates on every tick

Coverage improvements:
- loop_node.h: 65.9% -> 98.8% (+32.9%)
- reactive_fallback.cpp: 69.2% -> 74.4% (+5.2%)
- Overall: 76.6% -> 77.1%
- Tests: 420 -> 442 (+22)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The FileLogger2 constructor would start the writer thread after
registering status change callbacks in the base class. This created
a race condition where callbacks could fire before the writer thread
was ready, causing TSAN failures (double lock and data race errors).

Fix by adding an atomic 'ready' flag that is:
- Set to true when the writer thread starts running
- Checked in callback() before accessing the queue

Callbacks that arrive before the writer thread is ready are now
safely ignored.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The convertFromString<int64_t> and convertFromString<uint64_t>
functions were using 'long' and 'unsigned long' for parsing.
On Windows, these types are 32-bit, causing conversion failures
for values that exceed the 32-bit range.

Changed to use int64_t/uint64_t directly for proper cross-platform
64-bit integer handling.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@facontidavide facontidavide force-pushed the improve-test-suite-coverage branch from 0d99e5b to de0d2e9 Compare February 3, 2026 16:06
Use proper condition_variable synchronization to ensure the writer
thread is fully ready before the constructor returns. This prevents
race conditions where callbacks fire before the thread is initialized.

Previous atomic-based approach still had TSAN warnings because the
callback could start executing while the constructor was blocked
waiting for the condition variable, causing a double-lock scenario.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@facontidavide facontidavide force-pushed the improve-test-suite-coverage branch from 75cf848 to 3da513e Compare February 3, 2026 16:55
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: 1

🤖 Fix all issues with AI agents
In `@include/behaviortree_cpp/loggers/bt_file_logger_v2.h`:
- Around line 12-27: Add a direct `#include` <memory> to the header so
std::unique_ptr used in detail::FileLogger2PImplBase (member _p and
forward-declared FileLogger2Private) is available without relying on transitive
includes; place the include near the top of
include/behaviortree_cpp/loggers/bt_file_logger_v2.h before the
FileLogger2PImplBase declaration.

The root cause was that StatusChangeLogger subscribes to callbacks in
its constructor, but _p (the PImpl) wasn't initialized until after the
base class constructor completed. This meant callbacks could fire with
_p uninitialized.

Solution: Use the base-from-member idiom to ensure _p is initialized
BEFORE StatusChangeLogger. C++ initializes base classes in declaration
order, so by inheriting from FileLogger2PImplBase before StatusChangeLogger,
we guarantee _p exists when callbacks start firing.

Also wait for writer thread to be ready using condition_variable
synchronization before returning from the constructor.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@facontidavide facontidavide force-pushed the improve-test-suite-coverage branch from 3da513e to b41b8b6 Compare February 3, 2026 18:00
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/gtest_decorator.cpp (1)

266-271: ⚠️ Potential issue | 🟡 Minor

Comment inconsistency: delay is 100ms, not 200ms.

The comment on line 268 says "at least ~200ms" but the delay_msec in the XML is set to 100. Should read "~100ms" to match the actual configured delay.

📝 Proposed fix
   // The child (AlwaysSuccess) should have been executed after the delay
   ASSERT_EQ(status, NodeStatus::SUCCESS);
-  // Verify that at least ~200ms have passed (with small tolerance for timing jitter)
+  // Verify that at least ~100ms have passed (with small tolerance for timing jitter)
   ASSERT_GE(elapsed.count(), 80);
src/loggers/bt_file_logger_v2.cpp (1)

120-120: ⚠️ Potential issue | 🟡 Minor

Incorrect cast type: should be uint8_t, not uint64_t.

trans.status is declared as uint8_t in the Transition struct (see header line 74), but the cast here is to uint64_t. While this works due to implicit narrowing, it's misleading and inconsistent. Use the correct target type.

🔧 Proposed fix
-  trans.status = static_cast<uint64_t>(status);
+  trans.status = static_cast<uint8_t>(status);
🤖 Fix all issues with AI agents
In `@include/behaviortree_cpp/loggers/bt_file_logger_v2.h`:
- Around line 20-27: The struct FileLogger2PImplBase declares a destructor and
move operations but doesn't declare copy operations, triggering
cppcoreguidelines-special-member-functions; explicitly delete the copy
constructor and copy assignment operator to convey non-copyable intent. Update
FileLogger2PImplBase to add declarations "FileLogger2PImplBase(const
FileLogger2PImplBase&) = delete;" and "FileLogger2PImplBase& operator=(const
FileLogger2PImplBase&) = delete;", keeping the existing move ctor/operator= and
destructor, since it holds a std::unique_ptr<FileLogger2Private> _p; this will
silence clang-tidy and follow the Rule of Five.

facontidavide and others added 2 commits February 3, 2026 19:16
- Fix TSAN race condition in FileLogger2: set ready flag AFTER acquiring
  mutex for the first time to establish proper synchronization
- Add deleted copy operations to FileLogger2PImplBase to satisfy
  cppcoreguidelines-special-member-functions
- Fix nodiscard warnings in tests by casting convertFromString return
  values to void in ASSERT_THROW statements

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Establish a happens-before relationship by acquiring the mutex in the
main thread before starting the writer thread. This satisfies TSAN's
requirement that mutex creation and first cross-thread access are
properly synchronized.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/loggers/bt_file_logger_v2.cpp (1)

158-160: ⚠️ Potential issue | 🟡 Minor

Condition predicate uses && instead of || for shutdown check.

The condition !_p->transitions_queue.empty() && _p->loop won't immediately wake on shutdown (when loop becomes false). Compare with SqliteLogger::writerLoop which uses !transitions_queue_.empty() || !loop_ to wake immediately on either new data OR shutdown request.

Currently, shutdown relies on the 10ms timeout to wake the thread, causing a slight delay. This is functional but inconsistent with the pattern in SqliteLogger.

🐛 Proposed fix to match SqliteLogger pattern
       std::unique_lock lock(_p->queue_mutex);
       _p->queue_cv.wait_for(lock, std::chrono::milliseconds(10), [this]() {
-        return !_p->transitions_queue.empty() && _p->loop;
+        return !_p->transitions_queue.empty() || !_p->loop;
       });
🧹 Nitpick comments (1)
src/loggers/bt_file_logger_v2.cpp (1)

99-103: Spin-wait could be replaced with condition variable for better efficiency.

The spin-wait with yield() works correctly but burns CPU cycles until the writer thread signals readiness. Since the writer thread signals almost immediately after starting, this is acceptable for simplicity, but a condition variable would be more efficient if startup latency ever increases.

♻️ Optional: Use condition variable instead of spin-wait
-  // Wait for writer thread to signal it's ready
-  while(!_p->ready.load(std::memory_order_acquire))
-  {
-    std::this_thread::yield();
-  }
+  // Wait for writer thread to signal it's ready
+  {
+    std::unique_lock lock(_p->queue_mutex);
+    _p->queue_cv.wait(lock, [this]() { return _p->ready.load(std::memory_order_relaxed); });
+  }

This would require adjusting writerLoop to notify after setting ready.

facontidavide and others added 3 commits February 3, 2026 19:35
The mutex initialization must happen in FileLogger2PImplBase constructor,
BEFORE StatusChangeLogger's constructor runs. StatusChangeLogger subscribes
to callbacks during construction, which may fire and access the mutex.
Having the mutex "used" by the main thread before any other thread accesses
it establishes the happens-before relationship that TSAN requires.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
TSAN doesn't properly track happens-before relationship between mutex
creation in make_unique and first cross-thread access, even though
std::thread::thread provides the synchronization guarantee. The mutex
is properly protected: base class constructor acquires/releases it
before StatusChangeLogger subscribes to callbacks, and writer thread
acquires it before setting the ready flag.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The flush() method was accessing file_stream without synchronization
while the writer thread was also accessing it. This adds a file_mutex
to protect all file_stream operations in both flush() and writerLoop().

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@facontidavide facontidavide force-pushed the improve-test-suite-coverage branch from d85d80c to 5057dd7 Compare February 3, 2026 20:59
…fety

Add support for deferred subscription in StatusChangeLogger to fix race
conditions in loggers with worker threads (FileLogger2, SqliteLogger).

Changes to StatusChangeLogger:
- Add protected default constructor for deferred subscription mode
- Add protected subscribeToTreeChanges() method
- Derived classes can now complete initialization before receiving callbacks

Changes to FileLogger2:
- Remove FileLogger2PImplBase base-from-member idiom hack
- Use simple Pimpl pattern with deferred subscription
- Subscribe to tree changes AFTER writer thread is started
- Remove ready flag and related synchronization complexity

Changes to SqliteLogger:
- Use deferred subscription to fix same race condition

Remove all FileLogger2 TSAN suppressions - no longer needed.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@facontidavide facontidavide force-pushed the improve-test-suite-coverage branch from 5057dd7 to cadda8f Compare February 3, 2026 21:05
facontidavide and others added 2 commits February 3, 2026 22:20
Remove atomic flag approach to avoid ABI break. The deferred subscription
pattern (StatusChangeLogger() + subscribeToTreeChanges()) achieves the
same thread safety without adding new members to the base class.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Prevent recursive mutex locking when multiple nodes change status
during a single tree tick. The callback lambda now copies state
under the lock, releases it, then calls user code.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 3, 2026

Focus CI resources on debugging TSAN issues.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
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.

2 participants