General
Related to #9
I accidentally came across a problem with undefined static behavior in plugins, which led to a critical bug in Rocket.Core.Plugins.RocketPluginManager.
The core of the issue lies in plugins being loaded into the game process multiple times due to an incorrect DLL loading order.
This behavior completely breaks the Singleton pattern, effectively turning it into a kind of "Dualton".
The problem is easier to illustrate with an example.
Example/Reproduce
The Rocket/Plugins folder contains the following files:
ADependantPlugin.dll
CorePlugin.dll
Here, ADependantPlugin.dll references code from CorePlugin.dll (the files order matters in this case).
Referencing the code from:
|
private void loadPlugins() |
|
private Assembly OnAssemblyResolve(object sender, ResolveEventArgs args) |
Rocket’s behavior is as follows:
- It caches the paths to all unique DLLs from
Rocket/Libraries and Rocket/Plugins.
- It starts loading DLLs from
Rocket/Plugins sequentially into the game process.
- It loads
ADependantPlugin.dll, which requires CorePlugin.dll.
- Through
OnAssemblyResolve, CorePlugin.dll is loaded using Assembly.Load().
- Later,
CorePlugin.dll is loaded again via Assembly.LoadFile() inside the loop of LoadAssembliesFromDirectory().
As a result, we end up with two identical DLLs loaded into the game. This causes static constructors to be called twice.
So, even if public static CorePlugin Instance { get; private set; } was set during Load() of the CorePlugin, it will be reset to null upon repeated access, throwing an exception.
Note
At the moment, I haven’t had time to create a repository to reproduce the issue, as I discovered it while working on a client project.
However, if a repro repository is needed, feel free to ask and I’ll be happy to provide one.
The issue can be temporarily resolved by renaming ADependantPlugin.dll to DependantPlugin.dll, so it loads after CorePlugin.dll, as the folder content becomes:
CorePlugin.dll
DependantPlugin.dll
Conclusion
Despite the possibility of working around this issue, I believe this kind of hidden bug is extremely problematic.
General
Related to #9
I accidentally came across a problem with undefined static behavior in plugins, which led to a critical bug in
Rocket.Core.Plugins.RocketPluginManager.The core of the issue lies in plugins being loaded into the game process multiple times due to an incorrect DLL loading order.
This behavior completely breaks the Singleton pattern, effectively turning it into a kind of "Dualton".
The problem is easier to illustrate with an example.
Example/Reproduce
The
Rocket/Pluginsfolder contains the following files:ADependantPlugin.dllCorePlugin.dllHere,
ADependantPlugin.dllreferences code fromCorePlugin.dll(the files order matters in this case).Referencing the code from:
RocketModFix/Rocket/Rocket.Core/Plugins/RocketPluginManager.cs
Line 78 in 19e5b74
RocketModFix/Rocket/Rocket.Core/Plugins/RocketPluginManager.cs
Line 43 in 19e5b74
Rocket’s behavior is as follows:
Rocket/LibrariesandRocket/Plugins.Rocket/Pluginssequentially into the game process.ADependantPlugin.dll, which requiresCorePlugin.dll.OnAssemblyResolve,CorePlugin.dllis loaded usingAssembly.Load().CorePlugin.dllis loaded again viaAssembly.LoadFile()inside the loop ofLoadAssembliesFromDirectory().As a result, we end up with two identical DLLs loaded into the game. This causes static constructors to be called twice.
So, even if
public static CorePlugin Instance { get; private set; }was set duringLoad()of theCorePlugin, it will be reset tonullupon repeated access, throwing an exception.Note
At the moment, I haven’t had time to create a repository to reproduce the issue, as I discovered it while working on a client project.
However, if a repro repository is needed, feel free to ask and I’ll be happy to provide one.
The issue can be temporarily resolved by renaming
ADependantPlugin.dlltoDependantPlugin.dll, so it loads afterCorePlugin.dll, as the folder content becomes:CorePlugin.dllDependantPlugin.dllConclusion
Despite the possibility of working around this issue, I believe this kind of hidden bug is extremely problematic.