From c6d61a3de0194e42a131828036dff0516fa9f397 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 9 Jun 2025 13:53:00 +0200 Subject: [PATCH] Implemented another hot reload locking mechanism --- .../PluginSystem/PluginFactory.Download.cs | 3 ++ .../PluginSystem/PluginFactory.HotReload.cs | 35 ++++++++++++-- .../Tools/PluginSystem/PluginFactory.cs | 47 +++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs index 7bad7ec4..e3923b65 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs @@ -42,6 +42,7 @@ public static partial class PluginFactory var tempDownloadFile = Path.GetTempFileName(); try { + await LockHotReloadAsync(); using var httpClient = new HttpClient(); var response = await httpClient.GetAsync(downloadUrl, cancellationToken); if (response.IsSuccessStatusCode) @@ -80,6 +81,8 @@ public static partial class PluginFactory LOG.LogError(e, "Failed to delete the temporary download file."); } } + + UnlockHotReload(); } return true; diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs index 7bed742e..b7cb0c18 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs @@ -46,12 +46,41 @@ public static partial class PluginFactory try { LOG.LogInformation($"File changed ({changeType}): {args.FullPath}. Reloading plugins..."); - - // Wait for parallel writes to finish: - await Task.Delay(TimeSpan.FromSeconds(3)); + if (File.Exists(HOT_RELOAD_LOCK_FILE)) + { + LOG.LogInformation("Hot reload lock file exists. Waiting for it to be released before proceeding with the reload."); + + var lockFileCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + var token = lockFileCancellationTokenSource.Token; + var waitTime = TimeSpan.FromSeconds(1); + while (File.Exists(HOT_RELOAD_LOCK_FILE) && !token.IsCancellationRequested) + { + try + { + LOG.LogDebug("Waiting for hot reload lock to be released..."); + await Task.Delay(waitTime, token); + waitTime = TimeSpan.FromSeconds(Math.Min(waitTime.TotalSeconds * 2, 120)); // Exponential backoff with a cap + } + catch (TaskCanceledException) + { + // Case: The cancellation token was triggered, meaning the lock file is still present. + // We expect that something goes wrong. So, we try to delete the lock file: + LOG.LogWarning("Hot reload lock file still exists after 30 seconds. Attempting to delete it..."); + UnlockHotReload(); + break; + } + } + + LOG.LogInformation("Hot reload lock file released. Proceeding with plugin reload."); + } + await LoadAll(); await MessageBus.INSTANCE.SendMessage(null, Event.PLUGINS_RELOADED); } + catch(Exception e) + { + LOG.LogError(e, $"Error while reloading plugins after change in file '{args.FullPath}' with change type '{changeType}'."); + } finally { HOT_RELOAD_SEMAPHORE.Release(); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 352c9bb3..2d7b38b0 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -13,6 +13,7 @@ public static partial class PluginFactory private static string PLUGINS_ROOT = string.Empty; private static string INTERNAL_PLUGINS_ROOT = string.Empty; private static string CONFIGURATION_PLUGINS_ROOT = string.Empty; + private static string HOT_RELOAD_LOCK_FILE = string.Empty; private static FileSystemWatcher HOT_RELOAD_WATCHER = null!; private static ILanguagePlugin BASE_LANGUAGE_PLUGIN = NoPluginLanguage.INSTANCE; @@ -30,6 +31,7 @@ public static partial class PluginFactory LOG.LogInformation("Initializing plugin factory..."); DATA_DIR = SettingsManager.DataDirectory!; PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); + HOT_RELOAD_LOCK_FILE = Path.Join(PLUGINS_ROOT, ".lock"); INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal"); CONFIGURATION_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".config"); @@ -41,6 +43,51 @@ public static partial class PluginFactory LOG.LogInformation("Plugin factory initialized successfully."); return true; } + + private static async Task LockHotReloadAsync() + { + if (!IS_INITIALIZED) + { + LOG.LogError("PluginFactory is not initialized."); + return; + } + + try + { + if (File.Exists(HOT_RELOAD_LOCK_FILE)) + { + LOG.LogWarning("Hot reload lock file already exists."); + return; + } + + await File.WriteAllTextAsync(HOT_RELOAD_LOCK_FILE, DateTime.UtcNow.ToString("o")); + } + catch (Exception e) + { + LOG.LogError(e, "An error occurred while trying to lock hot reloading."); + } + } + + private static void UnlockHotReload() + { + if (!IS_INITIALIZED) + { + LOG.LogError("PluginFactory is not initialized."); + return; + } + + try + { + if(File.Exists(HOT_RELOAD_LOCK_FILE)) + File.Delete(HOT_RELOAD_LOCK_FILE); + else + LOG.LogWarning("Hot reload lock file does not exist. Nothing to unlock."); + } + catch (Exception e) + { + LOG.LogError(e, "An error occurred while trying to unlock hot reloading."); + } + } public static void Dispose() {