# Synchronization Guidelines - GroveEngine ## Overview This document describes the thread-safety patterns used in GroveEngine to prevent deadlocks and optimize concurrent access. ## Mutex Types ### std::mutex Use for simple, exclusive access to resources with balanced read/write patterns. ### std::shared_mutex (C++17) Use for **read-heavy workloads** where multiple readers can access data concurrently. - `std::shared_lock` - Allows concurrent reads - `std::unique_lock` - Exclusive write access ## DO: Use std::scoped_lock for multiple mutexes When you need to lock multiple mutexes, **always** use `std::scoped_lock` to prevent deadlock via lock-order-inversion: ```cpp void function() { std::scoped_lock lock(mutex1, mutex2, mutex3); // Safe - lock order guaranteed by implementation (deadlock-free algorithm) } ``` ## DON'T: Use std::lock_guard for multiple mutexes ```cpp void function() { std::lock_guard lock1(mutex1); // BAD std::lock_guard lock2(mutex2); // DEADLOCK RISK // If another thread locks in reverse order -> deadlock } ``` ## DO: Use std::unique_lock with std::lock if you need early unlock ```cpp void function() { std::unique_lock lock1(mutex1, std::defer_lock); std::unique_lock lock2(mutex2, std::defer_lock); std::lock(lock1, lock2); // Safe deadlock-free acquisition // ... do work ... // Can unlock early if needed lock1.unlock(); } ``` ## DO: Use shared_lock for read operations ```cpp class DataStore { mutable std::shared_mutex mutex; std::map data; public: Data get(const std::string& key) const { std::shared_lock lock(mutex); // Multiple readers allowed return data.at(key); } void set(const std::string& key, Data value) { std::unique_lock lock(mutex); // Exclusive write access data[key] = std::move(value); } }; ``` ## Read/Write Ratio Guidelines | Ratio | Recommendation | |-------|---------------| | >10:1 (read-heavy) | Use `std::shared_mutex` | | 1:1 to 10:1 | Consider `std::shared_mutex` | | <1:1 (write-heavy) | Use `std::mutex` (shared_mutex overhead not worth it) | ## GroveEngine Specific Patterns ### TopicTree - Uses `std::shared_mutex` - `findSubscribers()` - `shared_lock` (READ) - `registerSubscriber()` - `unique_lock` (WRITE) - `unregisterSubscriber()` - `unique_lock` (WRITE) ### IntraIOManager - Uses `std::shared_mutex` for instances map - Uses `std::scoped_lock` when both `managerMutex` and `batchMutex` needed - `getInstance()` - `shared_lock` (READ) - `createInstance()` - `unique_lock` (WRITE) - `routeMessage()` - `scoped_lock` (both mutexes) ## Validation Tools ### ThreadSanitizer (TSan) Build with TSan to detect lock-order-inversions and data races: ```bash cmake -DGROVE_ENABLE_TSAN=ON -B build-tsan cmake --build build-tsan TSAN_OPTIONS="detect_deadlocks=1" ctest ``` ### Helgrind Alternative detector via Valgrind: ```bash cmake -DGROVE_ENABLE_HELGRIND=ON -B build cmake --build build make helgrind ``` ## Common Pitfalls 1. **Nested lock calls** - Don't call a function that takes a lock while holding the same lock 2. **Lock-order-inversion** - Always use `scoped_lock` for multiple mutexes 3. **shared_lock in write context** - Never use shared_lock when modifying data 4. **Recursive locking** - Avoid; redesign if needed --- **Author**: Claude Code **Date**: 2025-01-21