Skip to content

Conversation

@facontidavide
Copy link
Collaborator

@facontidavide facontidavide commented Feb 8, 2026

Summary

  • Fix 6 Blackboard thread-safety data races (verified with ThreadSanitizer/clang-21):
    • set() new-entry path: missing entry_mutex lock when writing value/sequence_id/stamp
    • set() existing-entry path: use-after-free risk from raw reference after storage_mutex_ unlock
    • cloneInto(): missing per-entry locks when copying entry members + lock-order inversion (potential deadlock) between storage_mutex_ and entry_mutex — restructured into 3-phase approach that never holds both locks simultaneously
    • ImportBlackboardFromJSON(): unprotected write to entry->value
    • debugMessage(): iterates storage_ without storage_mutex_
    • getKeys(): iterates storage_ without storage_mutex_
  • Fix XML parser loadSubtreeModel null pointer dereference when <SubTree> in <TreeNodesModel> is missing the ID attribute
  • Fix variable name shadowing in XML parser loadSubtreeModel

Test plan

  • All 491 existing + new tests pass (normal build)
  • 0 TSan warnings on all 7 thread-safety tests (clang-21 TSan build)
  • BUG-7 XML test: throws descriptive RuntimeError instead of crashing
  • CI passes (all 6 workflows green)

🤖 Generated with Claude Code

facontidavide and others added 2 commits February 8, 2026 21:26
New control node that executes a try-child and, on failure, runs a
catch-child as recovery. Supports configurable catch_on_halt behavior,
async children, and re-entry after RUNNING states.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add null check for subtree_id when <SubTree> in <TreeNodesModel> is
  missing the ID attribute — now throws descriptive RuntimeError instead
  of crashing with UB (null pointer as map key)
- Fix variable name shadowing: rename inner-loop 'name' to 'port_name'
  to avoid shadowing the outer structured binding

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix 6 data races in Blackboard, verified with ThreadSanitizer (clang-21):

- set() new-entry path: wrote value/sequence_id/stamp without
  entry_mutex after createEntryImpl — add scoped_lock on entry_mutex
- set() existing-entry path: held raw reference after unlocking
  storage_mutex_, risking use-after-free if concurrent unset() erases
  the entry — copy shared_ptr before unlocking
- cloneInto(): read/wrote entry members without entry_mutex —
  add scoped_lock on src and dst entry mutexes
- ImportBlackboardFromJSON(): wrote entry->value without any lock —
  add scoped_lock on entry_mutex
- debugMessage(): iterated storage_ without storage_mutex_ —
  add unique_lock
- getKeys(): iterated storage_ without storage_mutex_ —
  add unique_lock

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch storage_mutex_ from std::mutex to std::shared_mutex so that
concurrent readers (getEntry, getKeys, debugMessage, set initial lookup,
cloneInto snapshot) no longer block each other.  Write operations
(createEntryImpl, unset, clear, cloneInto insert/erase) still take
exclusive locks.

Also remove the dead entry_mutex_ recursive_mutex and its deprecated
entryMutex() accessor — no callers exist in the codebase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 8, 2026

@facontidavide facontidavide merged commit 3633e27 into master Feb 9, 2026
15 checks passed
@facontidavide facontidavide deleted the bug_fixes_auto branch February 9, 2026 17:57
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