IA hotreloads
This commit is contained in:
parent
f7e08a337b
commit
986ce76fb7
|
|
@ -7,9 +7,19 @@ return [
|
||||||
| Hot Reload (Development Mode)
|
| Hot Reload (Development Mode)
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| When enabled, controller files are reloaded on every request instead of
|
| When enabled, ALL code is reloaded on every request in child processes.
|
||||||
| being cached. This allows you to make code changes without restarting
|
| This includes Models, Resources, Services, Controllers, Config, and
|
||||||
| the WebSocket server. Disable in production for better performance.
|
| everything else - allowing code changes without restarting the server.
|
||||||
|
|
|
||||||
|
| How it works:
|
||||||
|
| - OPcache is cleared in child processes (forces PHP to recompile files)
|
||||||
|
| - Laravel container singletons are reset (forces fresh instantiation)
|
||||||
|
| - Config files are re-read from disk
|
||||||
|
| - View, route, translation, and validation caches are cleared
|
||||||
|
| - WebSocket ControllerResolver cache is cleared
|
||||||
|
|
|
||||||
|
| WARNING: Disable in production for better performance. Hot reload adds
|
||||||
|
| ~5-15ms overhead per request due to cache clearing and file re-reads.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'hot_reload' => env('WEBSOCKET_HOT_RELOAD', env('APP_DEBUG', false)),
|
'hot_reload' => env('WEBSOCKET_HOT_RELOAD', env('APP_DEBUG', false)),
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,11 @@ class Handler implements MessageComponentInterface
|
||||||
private int $gcCounter = 0;
|
private int $gcCounter = 0;
|
||||||
private const GC_INTERVAL = 100;
|
private const GC_INTERVAL = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether hot reload is enabled (cached for performance)
|
||||||
|
*/
|
||||||
|
private static ?bool $hotReload = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new handler.
|
* Initialize a new handler.
|
||||||
*/
|
*/
|
||||||
|
|
@ -361,6 +366,120 @@ class Handler implements MessageComponentInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if hot reload mode is enabled
|
||||||
|
*/
|
||||||
|
protected static function isHotReload(): bool
|
||||||
|
{
|
||||||
|
if (self::$hotReload === null) {
|
||||||
|
self::$hotReload = (bool) config('websockets.hot_reload', false);
|
||||||
|
}
|
||||||
|
return self::$hotReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hot reload: Clear all caches in child process for fresh code loading
|
||||||
|
* This allows Models, Resources, Services, and everything else to be reloaded
|
||||||
|
* without restarting the WebSocket server.
|
||||||
|
*
|
||||||
|
* Only called when websockets.hot_reload is enabled.
|
||||||
|
*/
|
||||||
|
protected function hotReloadChild(): void
|
||||||
|
{
|
||||||
|
if (!self::isHotReload()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Clear OPcache - forces PHP to recompile files from disk
|
||||||
|
if (function_exists('opcache_reset')) {
|
||||||
|
opcache_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Clear Laravel's compiled services and config cache in container
|
||||||
|
$container = \Illuminate\Container\Container::getInstance();
|
||||||
|
|
||||||
|
// 3. Flush resolved instances - forces fresh instantiation
|
||||||
|
// This clears all singleton instances so they get rebuilt
|
||||||
|
$container->forgetScopedInstances();
|
||||||
|
|
||||||
|
// 4. Clear config repository cache (forces fresh config reads)
|
||||||
|
// Re-read all config files from disk
|
||||||
|
try {
|
||||||
|
/** @var \Illuminate\Config\Repository $config */
|
||||||
|
$config = $container->make('config');
|
||||||
|
|
||||||
|
// Get the path to config files
|
||||||
|
$configPath = base_path('config');
|
||||||
|
|
||||||
|
if (is_dir($configPath)) {
|
||||||
|
$files = glob($configPath . '/*.php');
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$key = basename($file, '.php');
|
||||||
|
// Invalidate opcache for this config file
|
||||||
|
if (function_exists('opcache_invalidate')) {
|
||||||
|
opcache_invalidate($file, true);
|
||||||
|
}
|
||||||
|
// Force re-require the config file
|
||||||
|
$freshConfig = require $file;
|
||||||
|
$config->set($key, $freshConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Config refresh failed, continue anyway
|
||||||
|
Log::channel('websocket')->debug('Hot reload config refresh failed: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Clear view cache (if views are being used in responses)
|
||||||
|
try {
|
||||||
|
if ($container->bound('view')) {
|
||||||
|
$container->forgetInstance('view');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// View refresh failed, continue anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Clear route cache (if routes are dynamically resolved)
|
||||||
|
try {
|
||||||
|
if ($container->bound('router')) {
|
||||||
|
$container->forgetInstance('router');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Router refresh failed, continue anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Clear translation cache
|
||||||
|
try {
|
||||||
|
if ($container->bound('translator')) {
|
||||||
|
$container->forgetInstance('translator');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Translator refresh failed, continue anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Clear validation factory (for custom rules)
|
||||||
|
try {
|
||||||
|
if ($container->bound('validator')) {
|
||||||
|
$container->forgetInstance('validator');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Validator refresh failed, continue anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. Clear event dispatcher cache (for fresh event/listener bindings)
|
||||||
|
try {
|
||||||
|
if ($container->bound('events')) {
|
||||||
|
$container->forgetInstance('events');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Events refresh failed, continue anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. Clear WebSocket ControllerResolver cache for fresh controller loading
|
||||||
|
ControllerResolver::clearCache();
|
||||||
|
|
||||||
|
Log::channel('websocket')->debug('Hot reload: caches cleared in child process');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fork with event-driven socket pair IPC (no polling!)
|
* Fork with event-driven socket pair IPC (no polling!)
|
||||||
* Parent is notified INSTANTLY when child sends data
|
* Parent is notified INSTANTLY when child sends data
|
||||||
|
|
@ -384,6 +503,9 @@ class Handler implements MessageComponentInterface
|
||||||
// === CHILD PROCESS ===
|
// === CHILD PROCESS ===
|
||||||
$ipc->setupChild();
|
$ipc->setupChild();
|
||||||
|
|
||||||
|
// Hot reload: clear all caches for fresh code loading (only in dev mode)
|
||||||
|
$this->hotReloadChild();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Lazy DB reconnect: disconnect now, reconnect only when first query runs
|
// Lazy DB reconnect: disconnect now, reconnect only when first query runs
|
||||||
// This saves ~5-15ms for methods that don't use the database
|
// This saves ~5-15ms for methods that don't use the database
|
||||||
|
|
@ -516,6 +638,9 @@ class Handler implements MessageComponentInterface
|
||||||
array $message,
|
array $message,
|
||||||
string $requestId
|
string $requestId
|
||||||
): void {
|
): void {
|
||||||
|
// Hot reload: clear all caches for fresh code loading (only in dev mode)
|
||||||
|
$this->hotReloadChild();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Lazy DB reconnect: disconnect now, reconnect only when first query runs
|
// Lazy DB reconnect: disconnect now, reconnect only when first query runs
|
||||||
// This saves ~5-15ms for methods that don't use the database
|
// This saves ~5-15ms for methods that don't use the database
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue