- Create new docs/plans/ directory with organized structure - Add comprehensive PLAN_deadlock_detection_prevention.md (15h plan) - ThreadSanitizer integration (2h) - Helgrind validation (3h) - std::scoped_lock refactoring (4h) - std::shared_mutex optimization (6h) - Migrate 16 plans from planTI/ to docs/plans/ - Rename all files to PLAN_*.md convention - Update README.md with index and statuses - Remove old planTI/ directory - Add run_all_tests.sh script for test automation Plans now include: - 1 active development plan (deadlock prevention) - 3 test architecture plans - 13 integration test scenario plans 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
29 KiB
Plan : Détection & Prévention Deadlock - GroveEngine
Date : 2025-01-21 Durée totale : 15h sur 2 semaines Objectif : Implémenter 4 aspects de protection anti-deadlock
📋 Vue d'Ensemble
| Phase | Aspect | Durée | Difficulté | Priorité | Livrable |
|---|---|---|---|---|---|
| 1.1 | ThreadSanitizer | 2h | ⭐ Easy | 🔥 Critique | TSan activé + tests clean |
| 1.2 | Helgrind | 3h | ⭐⭐ Medium | 🔶 Important | Validation croisée |
| 2 | std::scoped_lock | 4h | ⭐⭐ Medium | 🔥 Critique | Prévention deadlock |
| 3 | std::shared_mutex | 6h | ⭐⭐⭐ Hard | 🔶 Optim | Perf concurrent +50-400% |
Phase 1 : Détection Runtime (Semaine 1 - 5h)
Phase 1.1 : ThreadSanitizer (TSan) - Jour 1-2 (2h)
Objectif : Détection automatique des deadlocks potentiels et réels
Modifications CMakeLists.txt
Fichier : CMakeLists.txt (après project())
# ============================================================================
# Sanitizers for Testing
# ============================================================================
option(GROVE_ENABLE_TSAN "Enable ThreadSanitizer" OFF)
if(GROVE_ENABLE_TSAN)
message(STATUS "🔍 ThreadSanitizer enabled (5-15x slowdown expected)")
add_compile_options(-fsanitize=thread -g -O1 -fno-omit-frame-pointer)
add_link_options(-fsanitize=thread)
# Disable optimizations that confuse TSan
add_compile_options(-fno-optimize-sibling-calls)
message(WARNING "⚠️ TSan cannot be combined with ASan - build separately")
endif()
Tests
# Build avec TSan
cmake -DGROVE_ENABLE_TSAN=ON -B build-tsan
cmake --build build-tsan
# Run tous les tests
cd build-tsan
TSAN_OPTIONS="detect_deadlocks=1 history_size=7 exitcode=1" ctest -V
# Run un test spécifique
TSAN_OPTIONS="detect_deadlocks=1 second_deadlock_stack=1" ./tests/test_13_cross_system
# Avec logging détaillé
TSAN_OPTIONS="detect_deadlocks=1 log_path=tsan.log verbosity=2" ctest
Options TSan utiles
# Dans TSAN_OPTIONS (séparés par espaces)
detect_deadlocks=1 # Activer détection deadlock
history_size=7 # Taille historique (défaut=2, max=7)
second_deadlock_stack=1 # Afficher 2e stack trace
exitcode=1 # Exit code si erreur détectée
halt_on_error=0 # Continuer après 1ère erreur
log_path=tsan.log # Fichier de log (au lieu de stderr)
verbosity=2 # Niveau de détail (0-2)
Exemple de sortie TSan
==================
WARNING: ThreadSanitizer: lock-order-inversion (potential deadlock)
Cycle in lock order graph: M1 (0x7b0c00001000) => M2 (0x7b0c00002000) => M1
Mutex M1 acquired here while holding mutex M2:
#0 pthread_mutex_lock
#1 std::mutex::lock()
#2 IntraIOManager::routeMessage() src/IntraIOManager.cpp:176
Mutex M2 previously acquired here while holding mutex M1:
#0 pthread_mutex_lock
#1 std::mutex::lock()
#2 IntraIOManager::flushBatch() src/IntraIOManager.cpp:221
==================
Livrables Phase 1.1
- CMakeLists.txt modifié avec option GROVE_ENABLE_TSAN
- Documentation des options TSan
- Tous les tests passent sans warnings TSan
- CI/CD optionnellement intègre build TSan (peut être lent)
Effort estimé : 2h (1h setup + 1h fix issues)
Phase 1.2 : Helgrind - Jour 3-4 (3h)
Objectif : Double-vérification avec un détecteur alternatif
Modifications CMakeLists.txt
Fichier : CMakeLists.txt
# ============================================================================
# Helgrind (Valgrind) Integration
# ============================================================================
option(GROVE_ENABLE_HELGRIND "Add Helgrind test target" OFF)
if(GROVE_ENABLE_HELGRIND)
find_program(VALGRIND_EXECUTABLE valgrind)
if(VALGRIND_EXECUTABLE)
message(STATUS "✅ Valgrind found: ${VALGRIND_EXECUTABLE}")
# Add custom target for all tests
add_custom_target(helgrind
COMMAND ${CMAKE_COMMAND} -E echo "🔍 Running Helgrind (10-50x slowdown, be patient)..."
COMMAND ${VALGRIND_EXECUTABLE}
--tool=helgrind
--log-file=${CMAKE_BINARY_DIR}/helgrind-full.log
--suppressions=${CMAKE_SOURCE_DIR}/helgrind.supp
--error-exitcode=1
--read-var-info=yes
${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 600
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Running all tests with Helgrind deadlock detector"
)
# Add convenience target for single test
add_custom_target(helgrind-single
COMMAND ${CMAKE_COMMAND} -E echo "🔍 Running single test with Helgrind..."
COMMAND ${VALGRIND_EXECUTABLE}
--tool=helgrind
-v
--log-file=${CMAKE_BINARY_DIR}/helgrind-single.log
--suppressions=${CMAKE_SOURCE_DIR}/helgrind.supp
--error-exitcode=1
--read-var-info=yes
./tests/test_13_cross_system
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Running test_13_cross_system with Helgrind"
)
message(STATUS "✅ Helgrind targets added:")
message(STATUS " - make helgrind (all tests)")
message(STATUS " - make helgrind-single (test_13 only)")
else()
message(WARNING "⚠️ Valgrind not found - Helgrind targets disabled")
message(STATUS " Install: sudo apt-get install valgrind")
endif()
endif()
Fichier de suppressions
Fichier : helgrind.supp
# helgrind.supp - Suppress known false positives
# Format: https://valgrind.org/docs/manual/manual-core.html#manual-core.suppress
# spdlog false positives (lazy initialization)
{
spdlog_registry_instance
Helgrind:Race
fun:*spdlog*registry*instance*
}
{
spdlog_logger_creation
Helgrind:Race
...
fun:*spdlog*
}
# std::thread false positives
{
std_thread_detach
Helgrind:Race
fun:*std*thread*
}
# C++ static initialization race (benign)
{
static_initialization_guard
Helgrind:Race
fun:__cxa_guard_acquire
}
# Helgrind doesn't understand std::atomic properly
{
atomic_load
Helgrind:Race
fun:*atomic*load*
}
{
atomic_store
Helgrind:Race
fun:*atomic*store*
}
Tests
# Build avec Helgrind target
cmake -DGROVE_ENABLE_HELGRIND=ON -B build
cmake --build build
# Run all tests avec Helgrind (TRÈS LENT - 10-50x slowdown)
cd build
make helgrind
# Run un seul test (plus rapide pour debug)
make helgrind-single
# Voir les résultats
cat helgrind-full.log | grep -E "(Possible|Thread|ERROR|definitely)" | less
# Compter les problèmes
cat helgrind-full.log | grep "Possible data race" | wc -l
cat helgrind-full.log | grep "lock order" | wc -l
Exemple de sortie Helgrind
==12345== Helgrind, a thread error detector
==12345== Using Valgrind-3.18.1
Thread #1: lock order "0x123456 before 0x789abc" violated
Thread #2: lock order "0x789abc before 0x123456" violated
Expected order: 0x123456 before 0x789abc
at 0x4E4B123: pthread_mutex_lock (in /lib/libpthread.so)
by 0x401234: std::mutex::lock() (mutex:123)
by 0x402345: IntraIOManager::routeMessage() (IntraIOManager.cpp:176)
Comparaison TSan vs Helgrind
| Aspect | ThreadSanitizer | Helgrind |
|---|---|---|
| Overhead | 5-15x | 10-50x |
| Détection | Lock order + races | Lock order + races |
| Précision | Très bonne | Bonne (plus de FP) |
| Intégration | Compile-time | Runtime (externe) |
| Plateformes | Linux, macOS | Linux, macOS |
| Facilité | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| Recommandation | PRIMARY | Validation croisée |
Livrables Phase 1.2
- CMakeLists.txt avec targets Helgrind
- Fichier helgrind.supp avec suppressions
- Documentation d'usage (ce fichier)
- Tableau comparatif TSan vs Helgrind rempli
- Tests passent avec Helgrind
Effort estimé : 3h (1h setup + 2h suppressions + comparaison)
Phase 2 : Prévention Compile-time (Semaine 2 - 4h)
std::scoped_lock - Jour 5-7
Objectif : Prévenir deadlocks lors de l'acquisition de plusieurs mutexes
Analyse préalable
Rechercher tous les endroits avec multiple locks :
# Recherche pattern : lock_guard successifs
cd src
grep -n "lock_guard" *.cpp | sort -t: -k1,1
# Afficher contexte (2 lignes avant/après)
grep -B2 -A2 "lock_guard" IntraIOManager.cpp
Modifications identifiées
1. IntraIOManager.cpp - Ligne 176
Avant (risque de deadlock) :
void IntraIOManager::routeMessage(const std::string& sourceId,
const std::string& topic,
const json& messageData) {
std::lock_guard<std::mutex> lock(managerMutex);
// ... code ...
// Plus loin dans le même scope
std::lock_guard<std::mutex> batchLock(batchMutex); // ❌ Ordre variable
// Access batch buffer
}
Après (deadlock-proof) :
void IntraIOManager::routeMessage(const std::string& sourceId,
const std::string& topic,
const json& messageData) {
std::scoped_lock lock(managerMutex, batchMutex); // ✅ Ordre garanti
// ... code ...
// Accès safe aux deux ressources
}
2. IntraIOManager.cpp - Ligne 221
Avant :
void IntraIOManager::someOtherFunction() {
std::lock_guard<std::mutex> lock(managerMutex);
// ...
std::lock_guard<std::mutex> batchLock(batchMutex);
}
Après :
void IntraIOManager::someOtherFunction() {
std::scoped_lock lock(managerMutex, batchMutex);
// ...
}
3. IntraIOManager.cpp - Lignes 256, 272, 329
Même pattern : remplacer par scoped_lock.
Test unitaire de validation
Fichier : tests/unit/test_scoped_lock.cpp
#define CATCH_CONFIG_MAIN
#include <catch2/catch_test_macros.hpp>
#include <mutex>
#include <thread>
#include <atomic>
#include <vector>
TEST_CASE("scoped_lock prevents deadlock") {
std::mutex m1, m2;
std::atomic<int> counter{0};
std::atomic<bool> deadlocked{false};
// Thread 1: lock m1 then m2
std::thread t1([&]() {
for (int i = 0; i < 10000; i++) {
std::scoped_lock lock(m1, m2); // Order: m1, m2
counter++;
std::this_thread::yield();
}
});
// Thread 2: lock m2 then m1 (INVERSE ORDER)
std::thread t2([&]() {
for (int i = 0; i < 10000; i++) {
std::scoped_lock lock(m2, m1); // Order: m2, m1 - Still safe!
counter++;
std::this_thread::yield();
}
});
// Watchdog thread
std::thread watchdog([&]() {
for (int i = 0; i < 50; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter == 20000) {
return; // Success
}
}
deadlocked = true;
});
t1.join();
t2.join();
watchdog.join();
REQUIRE_FALSE(deadlocked);
REQUIRE(counter == 20000);
}
TEST_CASE("scoped_lock vs lock_guard - demonstrate issue") {
std::mutex m1, m2;
SECTION("scoped_lock - safe") {
std::thread t1([&]() {
std::scoped_lock lock(m1, m2);
});
std::thread t2([&]() {
std::scoped_lock lock(m2, m1); // Inverse order - SAFE
});
t1.join();
t2.join();
// No deadlock
REQUIRE(true);
}
// NOTE: Cannot easily test lock_guard deadlock without actual deadlock
// This is why we use TSan/Helgrind instead
}
Documentation - Patterns à éviter
Fichier : docs/coding_guidelines.md
## Synchronization Guidelines
### ✅ DO: Use std::scoped_lock for multiple mutexes
```cpp
void function() {
std::scoped_lock lock(mutex1, mutex2, mutex3);
// Safe - lock order guaranteed by implementation
}
❌ DON'T: Use std::lock_guard for multiple mutexes
void function() {
std::lock_guard<std::mutex> lock1(mutex1); // BAD
std::lock_guard<std::mutex> lock2(mutex2); // DEADLOCK RISK
// If another thread locks in reverse order -> deadlock
}
✅ DO: Use std::unique_lock with std::lock if you need unlock
void function() {
std::unique_lock<std::mutex> lock1(mutex1, std::defer_lock);
std::unique_lock<std::mutex> lock2(mutex2, std::defer_lock);
std::lock(lock1, lock2); // Safe deadlock-free acquisition
// ... do work ...
// Can unlock early if needed
lock1.unlock();
}
#### Checklist de refactoring
**Fichiers à modifier** :
- [x] `src/IntraIOManager.cpp` lignes 176, 221, 256, 272, 329
- [ ] `src/IntraIO.cpp` (vérifier si applicable)
- [ ] `src/JsonDataTree.cpp` (vérifier si applicable)
- [ ] `src/ModuleLoader.cpp` (vérifier si applicable)
**Tests** :
- [x] `tests/unit/test_scoped_lock.cpp` créé
- [x] Tous les tests d'intégration passent
- [x] TSan validation (pas de lock-order-inversion)
- [x] Helgrind validation
#### Livrables Phase 2
- [x] Toutes les acquisitions multi-mutex utilisent `std::scoped_lock`
- [x] Test unitaire démontrant la prévention
- [x] Documentation patterns à éviter (coding_guidelines.md)
- [x] Checklist de refactoring complétée
**Effort estimé** : 4h (2h recherche + 1h refactor + 1h tests)
---
## Phase 3 : Optimisation Concurrence (Semaine 2-3 - 6h)
### std::shared_mutex - Jour 8-10
**Objectif** : Permettre lectures concurrentes sans blocage
#### Analyse Read/Write Ratio
Avant de modifier, vérifier que c'est pertinent :
```bash
# Analyser les logs pour voir ratio read/write
cd build
./tests/test_11_io_system 2>&1 | grep "findSubscribers" | wc -l # READS
./tests/test_11_io_system 2>&1 | grep "registerSubscriber" | wc -l # WRITES
# Expected ratio: >100:1 (read-heavy)
Modification 1 : TopicTree.h
Fichier : external/StillHammer/topictree/include/topictree/TopicTree.h
Ligne 56 - Avant :
mutable std::mutex treeMutex; // Read-write would be better but keep simple
Ligne 56 - Après :
mutable std::shared_mutex treeMutex; // ✅ Reader-writer lock for concurrent reads
Ligne 222 - registerSubscriber() - WRITE :
void registerSubscriber(const std::string& pattern, const SubscriberType& subscriber) {
auto segments = splitTopic(pattern);
std::unique_lock lock(treeMutex); // ✅ Exclusive lock for write
insertPattern(&root, segments, 0, subscriber);
}
Ligne 234 - findSubscribers() - READ :
std::vector<SubscriberType> findSubscribers(const std::string& topic) const {
auto segments = splitTopic(topic);
std::unordered_set<SubscriberType> matches;
std::shared_lock lock(treeMutex); // ✅ Shared lock - concurrent reads!
findMatches(&root, segments, 0, matches);
return std::vector<SubscriberType>(matches.begin(), matches.end());
}
Ligne 253 - unregisterSubscriber() - WRITE :
void unregisterSubscriber(const std::string& pattern, const SubscriberType& subscriber) {
auto segments = splitTopic(pattern);
std::unique_lock lock(treeMutex); // ✅ Exclusive lock for write
removeSubscriberFromNode(&root, segments, 0, subscriber);
}
Ligne 266 - unregisterSubscriberAll() - WRITE :
void unregisterSubscriberAll(const SubscriberType& subscriber) {
std::unique_lock lock(treeMutex); // ✅ Exclusive lock for write
unregisterSubscriberAllRecursive(&root, subscriber);
}
Ligne 274 - clear() - WRITE :
void clear() {
std::unique_lock lock(treeMutex); // ✅ Exclusive lock for write
root = Node();
}
Ligne 282 - subscriberCount() - READ :
size_t subscriberCount() const {
std::shared_lock lock(treeMutex); // ✅ Shared lock - concurrent reads
return countSubscribersRecursive(&root);
}
Modification 2 : IntraIOManager
Fichier : include/grove/IntraIOManager.h
Avant :
class IntraIOManager {
private:
mutable std::mutex managerMutex;
std::mutex batchMutex;
};
Après :
class IntraIOManager {
private:
// Split into two mutexes with clear roles
mutable std::shared_mutex instancesMutex; // For instances map (read-heavy)
mutable std::mutex statsMutex; // For stats counters (simple)
std::mutex batchMutex; // For batch operations (keep as-is)
};
Fichier : src/IntraIOManager.cpp
getInstance() - READ :
std::shared_ptr<IntraIO> IntraIOManager::getInstance(const std::string& instanceId) const {
std::shared_lock lock(instancesMutex); // ✅ Concurrent reads
auto it = instances.find(instanceId);
if (it != instances.end()) {
return std::static_pointer_cast<IntraIO>(it->second);
}
return nullptr;
}
routeMessage() - READ (CRITICAL!) :
void IntraIOManager::routeMessage(const std::string& sourceId,
const std::string& topic,
const json& messageData) {
// Update stats - separate mutex
{
std::lock_guard<std::mutex> statsLock(statsMutex);
totalRoutedMessages++;
messagesSinceLastLog++;
}
// Instance lookup + routing - shared read lock
std::vector<std::string> subscribers;
{
std::shared_lock lock(instancesMutex); // ✅ Multiple threads route concurrently!
subscribers = topicTree.findSubscribers(topic);
}
// Deliver to subscribers
size_t deliveredCount = 0;
for (const auto& subscriberId : subscribers) {
std::shared_ptr<IIntraIODelivery> subscriber;
{
std::shared_lock lock(instancesMutex); // ✅ Concurrent lookup
auto it = instances.find(subscriberId);
if (it != instances.end()) {
subscriber = it->second;
}
}
if (subscriber && subscriberId != sourceId) {
// Deliver message (outside lock)
// ...
deliveredCount++;
}
}
// Update stats
{
std::lock_guard<std::mutex> statsLock(statsMutex);
totalDeliveredMessages += deliveredCount;
}
}
createInstance() - WRITE :
std::shared_ptr<IntraIO> IntraIOManager::createInstance(const std::string& instanceId) {
std::unique_lock lock(instancesMutex); // ✅ Exclusive write lock
auto it = instances.find(instanceId);
if (it != instances.end()) {
return std::static_pointer_cast<IntraIO>(it->second);
}
auto instance = createIntraIOInstance(instanceId);
instances[instanceId] = instance;
return instance;
}
removeInstance() - WRITE :
void IntraIOManager::removeInstance(const std::string& instanceId) {
std::unique_lock lock(instancesMutex); // ✅ Exclusive write lock
auto it = instances.find(instanceId);
if (it == instances.end()) {
return;
}
topicTree.unregisterSubscriberAll(instanceId);
instancePatterns.erase(instanceId);
instances.erase(it);
}
Modification 3 : JsonDataTree (Optionnel)
Fichier : include/grove/JsonDataTree.h
class JsonDataTree : public IDataTree {
private:
mutable std::shared_mutex treeMutex; // ✅ Instead of std::mutex
// ...
};
Fichier : src/JsonDataTree.cpp
READS - Concurrent :
std::unique_ptr<IDataNode> JsonDataTree::getConfigRoot() {
std::shared_lock lock(treeMutex); // ✅ Concurrent reads
auto configNode = m_root->getFirstChildByName("config");
if (!configNode) {
return nullptr;
}
auto* jsonNode = static_cast<JsonDataNode*>(configNode);
return std::make_unique<JsonDataNode>(jsonNode->getName(),
jsonNode->getJsonData(),
nullptr,
true);
}
std::unique_ptr<IDataNode> JsonDataTree::getDataRoot() {
std::shared_lock lock(treeMutex); // ✅ Concurrent reads
// ...
}
IDataNode* JsonDataTree::getDataRootReadOnly() {
std::shared_lock lock(treeMutex); // ✅ Concurrent reads
return m_root->getFirstChildByName("data");
}
WRITES - Exclusive :
bool JsonDataTree::loadConfigFile(const std::string& filename) {
std::unique_lock lock(treeMutex); // ✅ Exclusive write
// ...
}
bool JsonDataTree::reloadIfChanged() {
std::unique_lock lock(treeMutex); // ✅ Exclusive write
// ...
}
Benchmark Performance
Fichier : tests/benchmarks/benchmark_shared_mutex.cpp
#include <benchmark/benchmark.h>
#include <mutex>
#include <shared_mutex>
#include <thread>
// Mock TopicTree avec std::mutex
class TopicTreeMutex {
std::mutex mtx;
std::vector<std::string> data;
public:
void findSubscribers() {
std::lock_guard<std::mutex> lock(mtx);
volatile auto size = data.size(); // Simulate work
}
};
// Mock TopicTree avec std::shared_mutex
class TopicTreeSharedMutex {
std::shared_mutex mtx;
std::vector<std::string> data;
public:
void findSubscribers() const {
std::shared_lock lock(mtx);
volatile auto size = data.size(); // Simulate work
}
};
static void BM_Mutex_SingleThread(benchmark::State& state) {
TopicTreeMutex tree;
for (auto _ : state) {
tree.findSubscribers();
}
}
static void BM_SharedMutex_SingleThread(benchmark::State& state) {
TopicTreeSharedMutex tree;
for (auto _ : state) {
tree.findSubscribers();
}
}
static void BM_Mutex_MultiThread(benchmark::State& state) {
static TopicTreeMutex tree;
for (auto _ : state) {
tree.findSubscribers();
}
}
static void BM_SharedMutex_MultiThread(benchmark::State& state) {
static TopicTreeSharedMutex tree;
for (auto _ : state) {
tree.findSubscribers();
}
}
BENCHMARK(BM_Mutex_SingleThread);
BENCHMARK(BM_SharedMutex_SingleThread);
BENCHMARK(BM_Mutex_MultiThread)->Threads(1)->Threads(2)->Threads(4)->Threads(8);
BENCHMARK(BM_SharedMutex_MultiThread)->Threads(1)->Threads(2)->Threads(4)->Threads(8);
BENCHMARK_MAIN();
Ajouter au CMakeLists.txt :
# Benchmark shared_mutex
add_executable(benchmark_shared_mutex
benchmarks/benchmark_shared_mutex.cpp
)
target_link_libraries(benchmark_shared_mutex PRIVATE
GroveEngine::core
benchmark::benchmark
)
Run benchmark :
cmake --build build
./build/tests/benchmark_shared_mutex --benchmark_min_time=3s
Résultats attendus :
Benchmark Time CPU Iterations
-----------------------------------------------------------------------
BM_Mutex_SingleThread 15 ns 15 ns 46000000
BM_SharedMutex_SingleThread 15 ns 15 ns 46000000 (same overhead)
BM_Mutex_MultiThread/threads:1 15 ns 15 ns 46000000
BM_Mutex_MultiThread/threads:2 60 ns 120 ns 5800000 (2x slower - serialized)
BM_Mutex_MultiThread/threads:4 240 ns 960 ns 1450000 (4x slower)
BM_Mutex_MultiThread/threads:8 960 ns 7680 ns 180000 (8x slower)
BM_SharedMutex_MultiThread/threads:1 15 ns 15 ns 46000000
BM_SharedMutex_MultiThread/threads:2 18 ns 36 ns 19000000 (CONCURRENT!)
BM_SharedMutex_MultiThread/threads:4 22 ns 88 ns 7900000 (4x faster than mutex)
BM_SharedMutex_MultiThread/threads:8 30 ns 240 ns 2900000 (32x faster than mutex!)
Tests de validation
# Test fonctionnel
cmake --build build
ctest --output-on-failure
# Test avec TSan (vérifier pas de data race)
cmake -DGROVE_ENABLE_TSAN=ON -B build-tsan
cmake --build build-tsan
cd build-tsan
TSAN_OPTIONS="detect_deadlocks=1" ctest -V
# Helgrind validation
cd build
make helgrind
# Benchmark performance
./tests/benchmark_shared_mutex --benchmark_min_time=3s
Rapport de performance
Template : docs/performance_reports/shared_mutex_results.md
# Shared Mutex Performance Report
**Date** : YYYY-MM-DD
**Branch** : feature/shared-mutex
**Commit** : abc123
## Modifications
- TopicTree: std::mutex → std::shared_mutex
- IntraIOManager: split managerMutex into instancesMutex (shared) + statsMutex (exclusive)
- JsonDataTree: std::mutex → std::shared_mutex (optionnel)
## Benchmark Results
### Single-threaded Performance
| Component | Before (ns) | After (ns) | Overhead |
|-----------|-------------|------------|----------|
| TopicTree::findSubscribers | 15.2 | 15.3 | +0.7% ✅ |
| IntraIOManager::getInstance | 8.5 | 8.6 | +1.2% ✅ |
**Conclusion** : Overhead négligeable en single-thread.
### Multi-threaded Performance (4 threads)
| Component | Before (ns) | After (ns) | Speedup |
|-----------|-------------|------------|---------|
| TopicTree::findSubscribers | 960 | 22 | **43.6x** 🚀 |
| IntraIOManager::getInstance | 480 | 18 | **26.7x** 🚀 |
### Multi-threaded Performance (8 threads)
| Component | Before (ns) | After (ns) | Speedup |
|-----------|-------------|------------|---------|
| TopicTree::findSubscribers | 7680 | 30 | **256x** 🚀 |
| IntraIOManager::getInstance | 3840 | 25 | **153x** 🚀 |
## Integration Test Results
| Test | Before (ms) | After (ms) | Improvement |
|------|-------------|------------|-------------|
| test_11_io_system | 1250 | 380 | **3.3x faster** |
| test_13_cross_system | 2100 | 620 | **3.4x faster** |
## Validation
- [x] Tous les tests fonctionnels passent
- [x] TSan : Aucun data race détecté
- [x] Helgrind : Aucun lock order violation
- [x] Benchmark : Gain significatif en multi-thread
## Conclusion
✅ **shared_mutex apporte un gain massif** (50-250x) pour les workloads read-heavy.
✅ Overhead négligeable en single-thread.
✅ Aucune régression fonctionnelle.
**Recommandation** : Merge dans main.
Livrables Phase 3
- TopicTree.h utilise
std::shared_mutex - IntraIOManager split mutex (instancesMutex shared + statsMutex exclusive)
- JsonDataTree utilise
std::shared_mutex(optionnel) - Benchmark créé et exécuté
- Rapport de performance rempli (>50% speedup démontré)
- Tests passent avec TSan (pas de data race)
- Tests d'intégration passent
Effort estimé : 6h (2h TopicTree + 2h IntraIOManager + 1h JsonDataTree + 1h benchmarks)
Checklist Finale de Validation
Phase 1 : Détection Runtime
TSan :
- CMakeLists.txt modifié avec
GROVE_ENABLE_TSAN - Build TSan compile sans erreurs
- Tous les tests passent avec
TSAN_OPTIONS="detect_deadlocks=1" - Documentation TSan options
Helgrind :
- CMakeLists.txt avec targets
helgrindethelgrind-single - Fichier
helgrind.suppcréé avec suppressions - Tests passent avec Helgrind
- Tableau comparatif TSan vs Helgrind documenté
Phase 2 : Prévention
scoped_lock :
- Tous les lock_guard multi-mutex remplacés par scoped_lock
- Test unitaire
test_scoped_lock.cppcréé - Documentation
coding_guidelines.mdmise à jour - Tests d'intégration passent
Phase 3 : Optimisation
shared_mutex :
- TopicTree.h modifié (shared_lock/unique_lock)
- IntraIOManager.h/cpp modifiés (instancesMutex shared)
- JsonDataTree.h/cpp modifiés (optionnel)
- Benchmark
benchmark_shared_mutex.cppcréé - Rapport de performance rempli (gain >50%)
- TSan valide pas de data race
- Tests d'intégration passent
Calendrier
| Semaine | Jour | Phase | Durée | Cumul |
|---|---|---|---|---|
| 1 | Lun-Mar | TSan | 2h | 2h |
| 1 | Mer-Jeu | Helgrind | 3h | 5h |
| 2 | Lun-Mar | scoped_lock | 4h | 9h |
| 2 | Mer-Ven | shared_mutex | 6h | 15h |
Prochaines Étapes (Hors Plan)
Une fois ce plan complété, considérer :
-
Clang Thread Safety Annotations (long-terme)
- Ajouter
GUARDED_BY,REQUIRES,EXCLUDES - Compile-time verification
- Ajouter
-
Hierarchical Mutexes (si architecture complexe)
- Définir hiérarchie : Engine > ModuleSystem > Module > IO
- Runtime enforcement
-
Lock-free Structures (ultra-hot paths)
- TopicTree subscribers avec
std::atomic - Message queues lock-free
- TopicTree subscribers avec
Auteur : Claude Code Version : 1.0 Dernière mise à jour : 2025-01-21