- Fixed recursion deadlock due to the EventService.Reset() being called during event publishing.

This commit is contained in:
MapleWheels
2026-02-03 18:53:31 -05:00
committed by Maplewheels
parent 02a7338ab8
commit fd037153ee
2 changed files with 105 additions and 53 deletions
@@ -80,38 +80,45 @@ namespace Barotrauma
/// <param name="screen">The new game screen.</param> /// <param name="screen">The new game screen.</param>
public partial void OnScreenSelected(Screen screen) public partial void OnScreenSelected(Screen screen)
{ {
switch (screen) /*Note: This logic needs to be run after the triggering event so that recursion scenarios (ie. resetting the EventService)
do not occur, so we delay it by one game tick.*/
CoroutineManager.Invoke(() =>
{ {
// menus and navigation states switch (screen)
case MainMenuScreen: {
case ModDownloadScreen: // menus and navigation states
case ServerListScreen: case MainMenuScreen:
SetRunState(RunState.Unloaded); case ModDownloadScreen:
SetRunState(RunState.LoadedNoExec); case ServerListScreen:
break; SetRunState(RunState.Unloaded);
// running lobby or editor states
case CampaignEndScreen:
case CharacterEditorScreen:
case EventEditorScreen:
case GameScreen:
case LevelEditorScreen:
case NetLobbyScreen:
case ParticleEditorScreen:
case RoundSummaryScreen:
case SpriteEditorScreen:
case SubEditorScreen:
case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated.
if (CheckCsEnabled() && this.CurrentRunState >= RunState.Running)
{
SetRunState(RunState.LoadedNoExec); SetRunState(RunState.LoadedNoExec);
} break;
SetRunState(RunState.Running); // running lobby or editor states
break; case CampaignEndScreen:
default: case CharacterEditorScreen:
Logger.LogError($"{nameof(LuaCsSetup)}: Received an unknown screen {screen?.GetType().Name ?? "'null screen'"}. Retarding load state to 'unloaded'."); case EventEditorScreen:
SetRunState(RunState.Unloaded); case GameScreen:
break; case LevelEditorScreen:
} case NetLobbyScreen:
case ParticleEditorScreen:
case RoundSummaryScreen:
case SpriteEditorScreen:
case SubEditorScreen:
case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated.
if (CheckCsEnabled() && this.CurrentRunState >= RunState.Running)
{
SetRunState(RunState.LoadedNoExec);
}
SetRunState(RunState.Running);
break;
default:
Logger.LogError(
$"{nameof(LuaCsSetup)}: Received an unknown screen {screen?.GetType().Name ?? "'null screen'"}. Retarding load state to 'unloaded'.");
SetRunState(RunState.Unloaded);
break;
}
}, delay: 0f); // min is one tick delay.
} }
} }
} }
@@ -9,6 +9,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Xml.Serialization;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.IO; using Barotrauma.IO;
using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Data;
@@ -92,13 +93,75 @@ public class PluginManagementService : IAssemblyManagementService
public void Dispose() public void Dispose()
{ {
throw new NotImplementedException(); using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
{
return;
}
UnsafeDisposeResourcesInternal();
_assemblyLoaderFactory = null;
_storageService = null;
_eventService = null;
_logger = null;
_configService = null;
_luaScriptManagementService = null;
GC.SuppressFinalize(this);
} }
public bool IsDisposed { get; } private void UnsafeDisposeResourcesInternal()
{
foreach (var packPlugin in _pluginInstances.SelectMany(kvp => kvp.Value.Select(pluginInst => (kvp.Key, pluginInst))))
{
try
{
packPlugin.pluginInst.Dispose();
}
catch (Exception e)
{
_logger.LogError($"Error while disposing plugin for ContentPackage {packPlugin.Key.Name}: \n{e.Message}");
}
}
_pluginInstances.Clear();
_pluginInjectorContainer.Dispose();
_pluginInjectorContainer = null;
foreach (var loader in _assemblyLoaders)
{
try
{
loader.Value.Dispose();
_unloadingAssemblyLoaders.Add(loader.Value, loader.Key);
}
catch (Exception e)
{
_logger.LogError($"Failed to dispose of {nameof(IAssemblyLoaderService)} for ContentPackage {loader.Key.Name}: \n{e.Message}");
if (loader.Value.Assemblies.Any())
{
foreach (var ass in loader.Value.Assemblies)
{
_logger.LogWarning($"{nameof(PluginManagementService)}: Fallback manual unsubscription of assemblies: {ass.GetName()}");
ReflectionUtils.RemoveAssemblyFromCache(ass);
}
}
}
}
_assemblyLoaders.Clear();
}
private int _isDisposed = 0;
public bool IsDisposed
{
get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
public FluentResults.Result Reset() public FluentResults.Result Reset()
{ {
return FluentResults.Result.Fail("Not implemented"); using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
UnsafeDisposeResourcesInternal();
return FluentResults.Result.Ok();
} }
#endregion #endregion
@@ -536,8 +599,6 @@ public class PluginManagementService : IAssemblyManagementService
private void OnAssemblyLoaderUnloading(IAssemblyLoaderService loader) private void OnAssemblyLoaderUnloading(IAssemblyLoaderService loader)
{ {
using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
if (!loader.Assemblies.Any()) if (!loader.Assemblies.Any())
{ {
return; return;
@@ -545,7 +606,7 @@ public class PluginManagementService : IAssemblyManagementService
foreach (var assembly in loader.Assemblies) foreach (var assembly in loader.Assemblies)
{ {
_eventService.Value.PublishEvent<IEventAssemblyUnloading>(sub => sub.OnAssemblyUnloading(assembly)); _eventService?.Value?.PublishEvent<IEventAssemblyUnloading>(sub => sub.OnAssemblyUnloading(assembly));
} }
} }
@@ -564,20 +625,11 @@ public class PluginManagementService : IAssemblyManagementService
results.WithReasons(UnsafeDisposeManagedTypeInstances().Reasons); results.WithReasons(UnsafeDisposeManagedTypeInstances().Reasons);
ReflectionUtils.ResetCache(); ReflectionUtils.ResetCache();
bool[] targetGcGeneration = new bool[GC.MaxGeneration];
for (int i = 0; i < targetGcGeneration.Length; i++)
{
targetGcGeneration[i] = false;
}
foreach (var loaderService in _assemblyLoaders) foreach (var loaderService in _assemblyLoaders)
{ {
try try
{ {
loaderService.Value.Dispose(); loaderService.Value.Dispose();
targetGcGeneration[GC.GetGeneration(loaderService.Value)] = true;
_unloadingAssemblyLoaders.Add(loaderService.Value, loaderService.Key); _unloadingAssemblyLoaders.Add(loaderService.Value, loaderService.Key);
} }
catch (Exception e) catch (Exception e)
@@ -587,14 +639,7 @@ public class PluginManagementService : IAssemblyManagementService
} }
_assemblyLoaders.Clear(); _assemblyLoaders.Clear();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
for (int i = 0; i < targetGcGeneration.Length; i++)
{
if (!targetGcGeneration[i])
{
GC.Collect(i, GCCollectionMode.Aggressive, true);
}
}
return results; return results;
} }