Hot Reloading Development Guide¶
StarStreamer's hot reloading system provides a seamless development experience by intelligently reloading modules without losing connections or state. This guide covers the architecture, usage patterns, and best practices for effective development with hot reloading.
Overview¶
Hot reloading in StarStreamer uses a hybrid approach that balances development speed with system safety:
- Module changes → Hot reload (fast, preserves state)
- Core changes → Full restart (safe, clean state)
This approach allows rapid iteration on module development while ensuring framework changes are handled safely.
Architecture¶
File Watcher System¶
The hot reloading system is built on Python's watchdog library with custom event handling and thread-safe event loop integration:
class HybridReloadHandler(FileSystemEventHandler):
"""Handler for hybrid reload - hot reload modules, full restart for core changes"""
def __init__(self, starstreamer_app: "StarStreamer", loop: asyncio.AbstractEventLoop):
self.app = starstreamer_app
self.loop = loop # Main event loop for thread-safe scheduling
def on_modified(self, event: FileSystemEvent) -> None:
if "src/modules/" in path or "/modules/" in path:
self._schedule_module_reload(path)
elif "src/starstreamer/" in path:
self._schedule_full_restart()
Reload Strategy Decision Tree¶
flowchart TD
A[File Changed] --> B{File Type?}
B -->|Python .py| C{Directory?}
B -->|Other| D[Ignore]
C -->|modules/| E[Hot Reload]
C -->|src/starstreamer/| F[Full Restart]
C -->|tests/| D
C -->|__pycache__| D
E --> G[Extract Module Name]
G --> H[Reload Specific Module]
F --> I[Graceful Shutdown]
I --> J[Complete Restart]
Module Discovery and Loading¶
The system includes automatic module discovery:
class ModuleRegistry:
async def discover_modules(self) -> list[str]:
"""Discover available modules in the modules directory"""
async def reload_module(self, module_name: str) -> bool:
"""Hot reload a specific module using importlib.reload()"""
async def unregister_module_handlers(self, module_name: str) -> None:
"""Clean up handlers for a module before reload"""
Usage¶
Starting Development Mode¶
Enable hot reloading with the --reload flag:
# Basic hot reload
uv run python src/main.py --reload
# With custom web port
uv run python src/main.py --reload --web-port 8080
# With debug logging
uv run python src/main.py --reload --log-level DEBUG
# Combined with other options
uv run python src/main.py --reload --web-port 8080 --log-level DEBUG
What Gets Monitored¶
Included Files:
- *.py files in the project directory
- Changes in modules/ subdirectories
- Changes in src/starstreamer/ subdirectories
Excluded Files:
- Test files (test_*.py, *_test.py)
- Python cache (__pycache__/, *.pyc)
- Hidden files (.git/, .venv/)
- Database files (*.db, *.db-wal, *.db-shm)
File Change Handling¶
The system includes intelligent debouncing and thread-safe event loop integration:
self.restart_delay = 0.3 # 300ms debounce
def _schedule_module_reload(self, path: str) -> None:
def reload_module() -> None:
# Check if we have an event loop
if not self.loop:
self.logger.error("Cannot hot reload: no event loop available")
return
# Schedule coroutine on the main event loop (thread-safe)
future = asyncio.run_coroutine_threadsafe(
self._hot_reload_module(module_name),
self.loop
)
future.add_done_callback(self._handle_reload_result)
# Cancel any pending reload
if self.restart_timer:
self.restart_timer.cancel()
# Schedule new reload with delay
self.restart_timer = threading.Timer(self.restart_delay, reload_module)
self.restart_timer.start()
Development Workflows¶
Module Development Workflow¶
-
Start development mode:
-
Create or modify a module:
-
Save the file → Module reloads automatically in ~200ms
-
Test immediately in Twitch chat without restarting
Core Development Workflow¶
-
Modify core framework:
-
Save the file → Full restart occurs automatically
-
All modules reload cleanly with new core functionality
New Module Creation Workflow¶
-
Create module structure:
-
Implement module class:
-
Add commands:
# modules/mymodule/actions/commands.py from starstreamer import on_event from starstreamer.triggers import trigger, CommandTrigger from starstreamer.plugins.twitch import TwitchClient from starstreamer.runtime.types import Event @on_event("twitch.chat.message") @trigger(CommandTrigger("!mycommand")) async def my_command(event: Event, twitch: TwitchClient): await twitch.send_message("My new module works!") -
Save files → Module is discovered and loaded automatically
Hot Reload Internals¶
Module Reloading Process¶
async def _hot_reload_module(self, module_name: str) -> None:
"""Perform hot reload of a specific module"""
try:
# 1. Get existing module instance
module = self.app.module_registry._modules.get(module_name)
if not module:
self.logger.warning(f"Module {module_name} not found for reload")
return
# 2. Unregister existing handlers
await self.app.event_bus.unregister_module_handlers(module_name)
# 3. Reload module using importlib
import importlib
module_path = f"modules.{module_name}.module"
if module_path in sys.modules:
importlib.reload(sys.modules[module_path])
# 4. Re-register actions
await module.register_actions()
self.logger.info(f"🔥 Hot reloaded module: {module_name}")
except Exception as e:
self.logger.error(f"Failed to hot reload module {module_name}: {e}")
Handler Tracking¶
The EventBus tracks handlers by module for clean unregistration:
class EventBus:
def __init__(self):
self.handler_registry = HandlerRegistry()
self._module_handlers: dict[str, list[str]] = {}
async def unregister_module_handlers(self, module_name: str) -> None:
"""Remove all handlers registered by a specific module"""
if module_name in self._module_handlers:
for handler_id in self._module_handlers[module_name]:
self.handler_registry.unregister(handler_id)
del self._module_handlers[module_name]
State Preservation¶
During hot reloads, critical state is preserved:
- Twitch WebSocket connections remain active
- Database connections stay open
- Service container maintains registered services
- Event bus configuration is preserved
- Web server continues running
Best Practices¶
Module Design for Hot Reloading¶
✅ Do:
# Stateless handlers
@on_event("twitch.chat.message")
@trigger(CommandTrigger("!counter"))
async def counter_command(event: Event, twitch: TwitchClient, db: Database):
# Get count from database each time
count = await db.get_variable("counter", 0)
await db.set_variable("counter", count + 1)
await twitch.send_message(f"Count: {count + 1}")
# Use dependency injection
async def my_handler(event: Event, economy: EconomyService, logger: logging.Logger):
# Services are injected fresh on each reload
pass
# Module-level configuration
class MyModule(BaseModule):
def __init__(self):
super().__init__()
self.config = self.load_config() # Reloaded with module
❌ Avoid:
# Module-level mutable state
user_cache = {} # Won't reset on hot reload
# Global service imports
from starstreamer.services.economy import economy_service
async def handler(event: Event):
# This reference won't update on core changes
pass
# Background tasks without cleanup
asyncio.create_task(background_worker()) # Orphaned on reload
Error Handling¶
Always handle reload failures gracefully:
try:
await registry.reload_module("problematic_module")
except ImportError as e:
logger.error(f"Syntax error in module: {e}")
# Previous version continues running
except Exception as e:
logger.error(f"Reload failed: {e}")
# Module remains in previous working state
Testing Hot Reloads¶
-
Make small, safe changes first:
-
Test error handling:
-
Verify state preservation:
Performance Considerations¶
Hot Reload Performance¶
- Reload time: ~200ms for typical modules
- Memory usage: Minimal increase during reload
- Connection stability: No interruption to external connections
- Event loop integration: Uses
asyncio.run_coroutine_threadsafe()for thread-safe scheduling
File Watcher Overhead¶
- CPU usage: <1% during file monitoring
- Memory usage: ~10MB for watchdog processes
- Debouncing: Prevents excessive reloads during rapid edits
Module Size Impact¶
| Module Size | Reload Time | Memory Impact |
|---|---|---|
| Small (1-5 files) | ~100ms | <1MB |
| Medium (5-15 files) | ~200ms | 1-5MB |
| Large (15+ files) | ~500ms | 5-10MB |
Troubleshooting¶
Common Issues¶
Module not reloading:
# Check if file is being watched
uv run python src/main.py --reload --log-level DEBUG
# Look for "📝 Detected change in..." messages
Syntax errors preventing reload:
# Error is logged, previous version continues
ERROR: Failed to hot reload module chat: invalid syntax (commands.py, line 15)
Handler not responding after reload:
Memory leaks during development:
# Restart periodically during heavy development
# Hot reloads preserve memory, but imports accumulate
Debug Information¶
Enable detailed logging for troubleshooting:
# Full debug output
uv run python src/main.py --reload --log-level DEBUG
# Watch specific directories
uv run python src/main.py --reload --watch-dirs modules
# Monitor file system events
uv run python src/main.py --reload --log-level DEBUG 2>&1 | grep "📝 Detected"
Recovery Procedures¶
If hot reload gets stuck:
1. Make a trivial change to force a reload
2. Check logs for error messages
3. Restart manually if needed: Ctrl+C → uv run python src/main.py --reload
If module state becomes inconsistent: 1. Force full restart by modifying a core file 2. Or restart manually to clean state
Advanced Usage¶
Web Interface Module Management¶
StarStreamer v0.6.1+ provides a web interface for interactive module management:
- Access the Modules page:
- Open http://localhost:8888/modules
-
View real-time module statistics and status
-
Available actions:
- Reload - Hot reload module code (same as automatic file watching)
- Enable/Disable - Toggle module functionality without reloading
-
Load/Unload - Add/remove modules from memory
-
Development benefits:
- Visual feedback - See which modules are loaded and enabled
- Manual control - Override automatic reloading when needed
- Status monitoring - Track reload success/failure in real-time
Selective Module Reloading¶
You can reload specific modules programmatically:
from modules.registry import ModuleRegistry
# In a web endpoint or CLI command
await registry.reload_module("chat")
await registry.reload_module("economy")
Custom File Watching¶
Extend the file watcher for custom needs:
class CustomReloadHandler(HybridReloadHandler):
def on_modified(self, event: FileSystemEvent) -> None:
if "config.yaml" in event.src_path:
self._reload_configuration()
else:
super().on_modified(event)
Integration with IDEs¶
VS Code settings for optimal experience:
{
"python.defaultInterpreterPath": ".venv/bin/python",
"python.terminal.activateEnvironment": false,
"files.watcherExclude": {
"**/__pycache__/**": true,
"**/data/*.db*": true
}
}
PyCharm configuration:
- Exclude __pycache__ directories from indexing
- Set up run configuration with --reload flag
- Enable auto-save for immediate feedback
See Also¶
- Module System - Understanding the module architecture
- Development Testing - Testing strategies for hot-reloaded modules
- Configuration Guide - Setting up development environment
- Quick Start - Getting started with development mode