Fixed two critical race conditions that prevented multi-threaded module execution: ## Bug #1: ThreadedModuleSystem::registerModule() race condition **Symptom:** Deadlock on first processModules() call **Root Cause:** Worker thread started before being added to workers vector **Fix:** Add worker to vector BEFORE spawning thread (src/ThreadedModuleSystem.cpp:102-108) Before: - Create worker → Start thread → Add to vector (RACE!) - Thread accesses workers[index] before push_back completes After: - Create worker → Add to vector → Start thread (SAFE) - Thread guaranteed to find worker in vector ## Bug #2: stillhammer::createLogger() race condition **Symptom:** Deadlock when multiple threads create loggers simultaneously **Root Cause:** Check-then-register pattern without mutex protection **Fix:** Added static mutex around spdlog::get() + register_logger() (external/StillHammer/logger/src/Logger.cpp:94-96) Before: - Thread 1: check → create → register - Thread 2: check → create → register (RACE on spdlog registry!) After: - Mutex protects entire check-then-register critical section ## Validation & Testing Added comprehensive test suite: - test_threaded_module_system.cpp (6 unit tests) - test_threaded_stress.cpp (5 stress tests: 50 modules × 1000 frames) - test_logger_threadsafe.cpp (concurrent logger creation) - benchmark_threaded_vs_sequential.cpp (performance comparison) - docs/THREADED_MODULE_SYSTEM_VALIDATION.md (full validation report) All tests passing (100%): - ThreadedModuleSystem: ✅ 0.15s - ThreadedStress: ✅ 7.64s - LoggerThreadSafe: ✅ 0.13s ## Impact ThreadedModuleSystem now PRODUCTION READY: - Thread-safe module registration - Stable parallel execution (validated with 50,000+ operations) - Hot-reload working (100 cycles tested) - Logger thread-safe for concurrent module initialization Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
393 lines
18 KiB
HTML
393 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>GroveEngine - Module Lifecycle</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
padding: 20px;
|
|
background: #1a1a2e;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 100vh;
|
|
font-family: 'Consolas', 'Monaco', monospace;
|
|
}
|
|
|
|
.diagram-container {
|
|
width: 1400px;
|
|
height: 900px;
|
|
background: white;
|
|
border-radius: 10px;
|
|
padding: 40px;
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
|
|
}
|
|
|
|
svg {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.state {
|
|
filter: drop-shadow(3px 3px 5px rgba(0,0,0,0.2));
|
|
}
|
|
|
|
.state-unloaded {
|
|
fill: #e0e0e0;
|
|
stroke: #757575;
|
|
stroke-width: 3;
|
|
}
|
|
|
|
.state-loaded {
|
|
fill: #fff9c4;
|
|
stroke: #f57f17;
|
|
stroke-width: 3;
|
|
}
|
|
|
|
.state-configured {
|
|
fill: #c8e6c9;
|
|
stroke: #388e3c;
|
|
stroke-width: 3;
|
|
}
|
|
|
|
.state-running {
|
|
fill: #81c784;
|
|
stroke: #2e7d32;
|
|
stroke-width: 4;
|
|
}
|
|
|
|
.state-hotreload {
|
|
fill: #ffccbc;
|
|
stroke: #d84315;
|
|
stroke-width: 3;
|
|
}
|
|
|
|
.state-error {
|
|
fill: #ffcdd2;
|
|
stroke: #c62828;
|
|
stroke-width: 3;
|
|
}
|
|
|
|
.state-shutdown {
|
|
fill: #b0bec5;
|
|
stroke: #455a64;
|
|
stroke-width: 3;
|
|
}
|
|
|
|
.transition-arrow {
|
|
stroke: #424242;
|
|
stroke-width: 2.5;
|
|
fill: none;
|
|
marker-end: url(#arrowhead);
|
|
}
|
|
|
|
.transition-hot {
|
|
stroke: #d32f2f;
|
|
stroke-width: 3;
|
|
fill: none;
|
|
marker-end: url(#arrowhead-hot);
|
|
}
|
|
|
|
.transition-label {
|
|
font-size: 11px;
|
|
font-weight: bold;
|
|
fill: #424242;
|
|
}
|
|
|
|
.state-name {
|
|
font-size: 15px;
|
|
font-weight: bold;
|
|
fill: #1a1a1a;
|
|
}
|
|
|
|
.state-desc {
|
|
font-size: 9px;
|
|
fill: #555;
|
|
}
|
|
|
|
.main-title {
|
|
font-size: 32px;
|
|
font-weight: bold;
|
|
fill: #1a1a1a;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 14px;
|
|
fill: #666;
|
|
}
|
|
|
|
.timing {
|
|
font-size: 9px;
|
|
fill: #d32f2f;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.detail-box {
|
|
fill: #f5f5f5;
|
|
stroke: #ddd;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.detail-title {
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
fill: #1a1a1a;
|
|
}
|
|
|
|
.detail-text {
|
|
font-size: 9px;
|
|
fill: #555;
|
|
}
|
|
|
|
.legend-box {
|
|
fill: #e3f2fd;
|
|
stroke: #2196f3;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.code-text {
|
|
font-family: 'Consolas', monospace;
|
|
font-size: 9px;
|
|
fill: #1a1a1a;
|
|
}
|
|
|
|
.circle-guide {
|
|
fill: none;
|
|
stroke: #e0e0e0;
|
|
stroke-width: 1;
|
|
stroke-dasharray: 5,5;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="diagram-container">
|
|
<svg viewBox="0 0 1320 820" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<marker id="arrowhead" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
<polygon points="0 0, 10 3, 0 6" fill="#424242" />
|
|
</marker>
|
|
<marker id="arrowhead-hot" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
<polygon points="0 0, 10 3, 0 6" fill="#d32f2f" />
|
|
</marker>
|
|
</defs>
|
|
|
|
<!-- Title -->
|
|
<text x="660" y="30" class="main-title" text-anchor="middle">Module Lifecycle State Machine</text>
|
|
<text x="660" y="50" class="subtitle" text-anchor="middle">GroveEngine • Circular Flow • Hot-Reload Cycle</text>
|
|
|
|
<!-- Circle guide (optional, for visual reference) -->
|
|
<circle cx="280" cy="330" r="200" class="circle-guide"/>
|
|
|
|
<!-- State 1: UNLOADED (top, 12 o'clock) -->
|
|
<rect x="200" y="100" width="160" height="70" rx="8" class="state state-unloaded"/>
|
|
<text x="280" y="125" class="state-name" text-anchor="middle">UNLOADED</text>
|
|
<text x="280" y="142" class="state-desc" text-anchor="middle">Initial state</text>
|
|
<text x="280" y="156" class="state-desc" text-anchor="middle">No .so/.dll loaded</text>
|
|
|
|
<!-- State 2: LOADED (2 o'clock position) -->
|
|
<rect x="380" y="160" width="160" height="70" rx="8" class="state state-loaded"/>
|
|
<text x="460" y="185" class="state-name" text-anchor="middle">LOADED</text>
|
|
<text x="460" y="202" class="state-desc" text-anchor="middle">Library loaded</text>
|
|
<text x="460" y="216" class="state-desc" text-anchor="middle">createModule()</text>
|
|
|
|
<!-- State 3: CONFIGURED (4 o'clock position) -->
|
|
<rect x="410" y="310" width="160" height="70" rx="8" class="state state-configured"/>
|
|
<text x="490" y="335" class="state-name" text-anchor="middle">CONFIGURED</text>
|
|
<text x="490" y="352" class="state-desc" text-anchor="middle">Config set</text>
|
|
<text x="490" y="366" class="state-desc" text-anchor="middle">IIO connected</text>
|
|
|
|
<!-- State 4: RUNNING (5 o'clock position, emphasized) -->
|
|
<rect x="350" y="450" width="160" height="80" rx="8" class="state state-running"/>
|
|
<text x="430" y="478" class="state-name" text-anchor="middle">RUNNING</text>
|
|
<text x="430" y="497" class="state-desc" text-anchor="middle">Active execution</text>
|
|
<text x="430" y="511" class="state-desc" text-anchor="middle">process() loop</text>
|
|
<text x="430" y="525" class="state-desc" text-anchor="middle">60 FPS</text>
|
|
|
|
<!-- State 5: ERROR (7 o'clock position, lower) -->
|
|
<rect x="150" y="570" width="160" height="70" rx="8" class="state state-error"/>
|
|
<text x="230" y="595" class="state-name" text-anchor="middle">ERROR</text>
|
|
<text x="230" y="612" class="state-desc" text-anchor="middle">Exception caught</text>
|
|
<text x="230" y="626" class="state-desc" text-anchor="middle">Recovery possible</text>
|
|
|
|
<!-- State 6: SHUTDOWN (9 o'clock position) -->
|
|
<rect x="30" y="360" width="160" height="70" rx="8" class="state state-shutdown"/>
|
|
<text x="110" y="385" class="state-name" text-anchor="middle">SHUTDOWN</text>
|
|
<text x="110" y="402" class="state-desc" text-anchor="middle">Cleanup complete</text>
|
|
<text x="110" y="416" class="state-desc" text-anchor="middle">Module destroyed</text>
|
|
|
|
<!-- State 7: HOT-RELOAD (right side, much lower) -->
|
|
<rect x="450" y="600" width="160" height="80" rx="8" class="state state-hotreload"/>
|
|
<text x="530" y="628" class="state-name" text-anchor="middle">HOT-RELOAD</text>
|
|
<text x="530" y="647" class="state-desc" text-anchor="middle">Extract state (0.1ms)</text>
|
|
<text x="530" y="661" class="state-desc" text-anchor="middle">Unload/Load (0.2ms)</text>
|
|
<text x="530" y="675" class="state-desc" text-anchor="middle">Restore state (0.1ms)</text>
|
|
|
|
<!-- Transition: UNLOADED → LOADED -->
|
|
<path d="M 340 135 C 360 135, 370 160, 380 180" class="transition-arrow"/>
|
|
<text x="360" y="155" class="transition-label">load()</text>
|
|
|
|
<!-- Transition: LOADED → CONFIGURED -->
|
|
<path d="M 500 230 L 520 310" class="transition-arrow"/>
|
|
<text x="525" y="275" class="transition-label">setConfig()</text>
|
|
|
|
<!-- Transition: CONFIGURED → RUNNING -->
|
|
<path d="M 500 380 L 470 450" class="transition-arrow"/>
|
|
<text x="500" y="420" class="transition-label">process()</text>
|
|
|
|
<!-- Self-loop: RUNNING → RUNNING (process loop) -->
|
|
<path d="M 510 475 C 560 475, 560 510, 510 510" class="transition-arrow"/>
|
|
<text x="565" y="495" class="transition-label">process()</text>
|
|
|
|
<!-- Transition: RUNNING → ERROR -->
|
|
<path d="M 350 520 C 310 540, 280 560, 270 570" class="transition-arrow"/>
|
|
<text x="300" y="550" class="transition-label">Exception</text>
|
|
|
|
<!-- Transition: ERROR → SHUTDOWN -->
|
|
<path d="M 190 570 C 160 530, 140 480, 140 430" class="transition-arrow"/>
|
|
<text x="145" y="500" class="transition-label">Fatal</text>
|
|
|
|
<!-- Transition: SHUTDOWN → UNLOADED (restart) -->
|
|
<path d="M 150 360 C 170 270, 210 180, 240 170" class="transition-arrow"/>
|
|
<text x="160" y="260" class="transition-label">unload()</text>
|
|
|
|
<!-- Transition: ERROR → RUNNING (recovery) -->
|
|
<path d="M 310 595 C 350 560, 380 540, 400 530" class="transition-arrow"/>
|
|
<text x="350" y="580" class="transition-label">recover()</text>
|
|
|
|
<!-- Transition: RUNNING → SHUTDOWN (direct) -->
|
|
<path d="M 350 490 C 270 480, 210 440, 190 410" class="transition-arrow"/>
|
|
<text x="250" y="460" class="transition-label">shutdown()</text>
|
|
|
|
<!-- HOT-RELOAD cycle: RUNNING → HOT-RELOAD (right curve) -->
|
|
<path d="M 490 525 C 510 550, 525 575, 530 600" class="transition-hot"/>
|
|
<text x="520" y="560" class="transition-label" fill="#d32f2f">reload()</text>
|
|
<text x="520" y="575" class="timing">0.4ms</text>
|
|
|
|
<!-- HOT-RELOAD cycle: HOT-RELOAD → RUNNING (left curve) -->
|
|
<path d="M 470 610 C 460 585, 450 560, 440 530" class="transition-hot"/>
|
|
<text x="445" y="570" class="transition-label" fill="#d32f2f">restored</text>
|
|
|
|
<!-- Right Panel: Hot-Reload Details -->
|
|
<rect x="720" y="80" width="280" height="165" rx="8" class="detail-box"/>
|
|
<text x="860" y="105" class="detail-title" text-anchor="middle">Hot-Reload Cycle (0.4ms)</text>
|
|
|
|
<text x="735" y="128" class="detail-text" font-weight="bold">1. Extract State:</text>
|
|
<text x="745" y="143" class="code-text">auto state = module->getState();</text>
|
|
<text x="950" y="143" class="timing">0.1ms</text>
|
|
|
|
<text x="735" y="163" class="detail-text" font-weight="bold">2. Unload Library:</text>
|
|
<text x="745" y="178" class="code-text">dlclose(handle);</text>
|
|
<text x="950" y="178" class="timing">0.05ms</text>
|
|
|
|
<text x="735" y="198" class="detail-text" font-weight="bold">3. Load New Library:</text>
|
|
<text x="745" y="213" class="code-text">handle = dlopen(path, RTLD_NOW);</text>
|
|
<text x="950" y="213" class="timing">0.15ms</text>
|
|
|
|
<text x="735" y="233" class="detail-text" font-weight="bold">4. Restore State:</text>
|
|
<text x="745" y="248" class="code-text">module->setState(state);</text>
|
|
<text x="950" y="248" class="timing">0.1ms</text>
|
|
|
|
<!-- Right Panel: State Preservation -->
|
|
<rect x="720" y="260" width="280" height="135" rx="8" fill="#e8f5e9" stroke="#4caf50" stroke-width="2"/>
|
|
<text x="860" y="285" class="detail-title" text-anchor="middle" fill="#2e7d32">State Preservation (100%)</text>
|
|
|
|
<text x="735" y="308" class="detail-text">What gets preserved:</text>
|
|
<text x="745" y="323" class="detail-text">• Player position, velocity, health</text>
|
|
<text x="745" y="338" class="detail-text">• Enemy AI states, pathfinding data</text>
|
|
<text x="745" y="353" class="detail-text">• UI widget states, text inputs</text>
|
|
<text x="745" y="368" class="detail-text">• Timers, counters, game state</text>
|
|
<text x="745" y="383" class="detail-text">• Any serializable module data</text>
|
|
|
|
<!-- Right Panel: Configuration -->
|
|
<rect x="720" y="410" width="280" height="110" rx="8" class="detail-box"/>
|
|
<text x="860" y="435" class="detail-title" text-anchor="middle">Configuration Phase</text>
|
|
|
|
<text x="735" y="458" class="code-text">setConfiguration(config, io, scheduler):</text>
|
|
<text x="745" y="473" class="detail-text">• config: IDataNode (JSON/XML)</text>
|
|
<text x="745" y="488" class="detail-text">• io: IIO for pub/sub messaging</text>
|
|
<text x="745" y="503" class="detail-text">• scheduler: ITaskScheduler</text>
|
|
|
|
<!-- Right Panel: IModule Interface -->
|
|
<rect x="720" y="535" width="280" height="135" rx="8" class="legend-box"/>
|
|
<text x="860" y="560" class="detail-title" text-anchor="middle">IModule Interface</text>
|
|
|
|
<text x="735" y="583" class="code-text">Required methods:</text>
|
|
<text x="745" y="598" class="detail-text">• process(deltaTime) - Main loop (60 FPS)</text>
|
|
<text x="745" y="613" class="detail-text">• getState() - Serialize to IDataNode</text>
|
|
<text x="745" y="628" class="detail-text">• setState(state) - Deserialize</text>
|
|
<text x="745" y="643" class="detail-text">• setConfiguration() - Init config</text>
|
|
<text x="745" y="658" class="detail-text">• shutdown() - Clean cleanup</text>
|
|
|
|
<!-- Right Panel: Performance -->
|
|
<rect x="720" y="685" width="280" height="80" rx="8" fill="#fff3e0" stroke="#ff9800" stroke-width="2"/>
|
|
<text x="860" y="710" class="detail-title" text-anchor="middle" fill="#e65100">Performance Metrics</text>
|
|
|
|
<text x="735" y="733" class="detail-text">Cold start (UNLOADED→RUNNING): <tspan class="timing" font-size="12px">~51ms</tspan></text>
|
|
<text x="735" y="753" class="detail-text">Hot-reload cycle: <tspan class="timing" font-size="12px">0.4ms avg</tspan> (0.055ms best)</text>
|
|
|
|
<!-- Bottom Left: State Legend -->
|
|
<rect x="1020" y="80" width="280" height="200" rx="8" class="legend-box"/>
|
|
<text x="1160" y="105" class="detail-title" text-anchor="middle">State Legend</text>
|
|
|
|
<rect x="1035" y="120" width="30" height="20" rx="4" class="state-unloaded"/>
|
|
<text x="1075" y="134" class="detail-text">Unloaded - Initial/Final</text>
|
|
|
|
<rect x="1035" y="150" width="30" height="20" rx="4" class="state-loaded"/>
|
|
<text x="1075" y="164" class="detail-text">Loaded - Library in memory</text>
|
|
|
|
<rect x="1035" y="180" width="30" height="20" rx="4" class="state-configured"/>
|
|
<text x="1075" y="194" class="detail-text">Configured - Ready to start</text>
|
|
|
|
<rect x="1035" y="210" width="30" height="20" rx="4" class="state-running"/>
|
|
<text x="1075" y="224" class="detail-text">Running - Active execution</text>
|
|
|
|
<rect x="1035" y="240" width="30" height="20" rx="4" class="state-hotreload"/>
|
|
<text x="1075" y="254" class="detail-text">Hot-Reload - 0.4ms transition</text>
|
|
|
|
<!-- Bottom Left: Typical Flow -->
|
|
<rect x="1020" y="295" width="280" height="110" rx="8" fill="#e8f5e9" stroke="#4caf50" stroke-width="2"/>
|
|
<text x="1160" y="320" class="detail-title" text-anchor="middle" fill="#2e7d32">Typical Development Flow</text>
|
|
|
|
<text x="1035" y="343" class="detail-text">1. Load module once (UNLOADED→RUNNING)</text>
|
|
<text x="1035" y="358" class="detail-text">2. Edit code in VSCode/IDE</text>
|
|
<text x="1035" y="373" class="detail-text">3. Build with cmake (300ms)</text>
|
|
<text x="1035" y="388" class="detail-text">4. Hot-reload (0.4ms, 60+ times/hour)</text>
|
|
|
|
<!-- Bottom Left: Error Handling -->
|
|
<rect x="1020" y="420" width="280" height="110" rx="8" fill="#ffebee" stroke="#d32f2f" stroke-width="2"/>
|
|
<text x="1160" y="445" class="detail-title" text-anchor="middle" fill="#c62828">Error Handling</text>
|
|
|
|
<text x="1035" y="468" class="detail-text">Exception in process() → ERROR state</text>
|
|
<text x="1035" y="483" class="detail-text">Recovery possible → back to RUNNING</text>
|
|
<text x="1035" y="498" class="detail-text">Fatal error → SHUTDOWN</text>
|
|
<text x="1035" y="513" class="detail-text">Module logs errors via spdlog</text>
|
|
|
|
<!-- Bottom Left: Benefits -->
|
|
<rect x="1020" y="545" width="280" height="135" rx="8" fill="#e3f2fd" stroke="#2196f3" stroke-width="2"/>
|
|
<text x="1160" y="570" class="detail-title" text-anchor="middle" fill="#0d47a1">Key Benefits</text>
|
|
|
|
<text x="1035" y="593" class="detail-text">✓ Sub-millisecond reload (0.4ms avg)</text>
|
|
<text x="1035" y="608" class="detail-text">✓ 100% state preservation</text>
|
|
<text x="1035" y="623" class="detail-text">✓ Game keeps running (no restart)</text>
|
|
<text x="1035" y="638" class="detail-text">✓ Zero context switching</text>
|
|
<text x="1035" y="653" class="detail-text">✓ Instant feedback loop</text>
|
|
<text x="1035" y="668" class="detail-text">✓ Perfect for rapid prototyping</text>
|
|
|
|
<!-- Bottom Left: Notes -->
|
|
<rect x="1020" y="695" width="280" height="70" rx="8" class="detail-box"/>
|
|
<text x="1160" y="720" class="detail-title" text-anchor="middle">Implementation Notes</text>
|
|
|
|
<text x="1035" y="740" class="detail-text">• Each ModuleLoader manages ONE module</text>
|
|
<text x="1035" y="755" class="detail-text">• Don't reuse loaders (causes SEGFAULT)</text>
|
|
|
|
<!-- Footer -->
|
|
<text x="660" y="810" class="subtitle" text-anchor="middle">
|
|
GroveEngine © 2025 • Hot-Reload System • Zero-downtime Development
|
|
</text>
|
|
</svg>
|
|
</div>
|
|
</body>
|
|
</html>
|