natemcmaster / DotNetCorePlugins

.NET Core library for dynamically loading code

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is it possible to hot reload unmanaged DLLs?

KatoStoelen opened this issue · comments

Posting this issue as a question. It's related to #93 but for unmanaged DLLs.

Scenario - Plugin Accessing a SQL Server Database

You add the Microsoft.Data.SqlClient NuGet package to the plugin project, which (on Windows atleast) has a dependency on sni.dll. The host runs fine when you start it. However, if you want to make a change to the plugin and deploy a new version without restarting the host, the deployment might failt due to sni.dll being locked by the host process.

You might be able to just ignore the fact that you cannot change or delete the unmanaged DLLs, but would it be possible to achieve hot reloading of plugins dependent on unmanaged DLLs?

Is loading multiple versions of an unmanaged .dll even supported by AssemblyLoadContext? Unlike managed assemblies, unmanaged .dll's don't have a version (from the perspective of coreclr), so I'm not sure what the DllImport resolver does when asked for an unmanaged dll that has already been loaded.

If ALC does allow multiple versions of an unmanaged library, then we can consider supporting this. AssemblyLoadContext has an API which allows loading managed .dll's from a stream, which is how hot reload avoids file-locking.

public Assembly LoadAssemblyFromFilePath(string path)
{
if (!_loadInMemory)
{
return LoadFromAssemblyPath(path);
}
using var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
return LoadFromStream(file);
}
I don't see an equivalent for unmanaged .dll's on https://docs.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext. The only alternative I can think of is to implement "shadow copy". Copy the unmanaged .dll to a path in /tmp (or Windows equivalent) and load that instead, and make sure to delete the tmp file once the context is unloaded/disposed. That change would need to be attempting in
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
.

I thought about shadow copying, but as observed in #93 the DLL is still locked by the host process even after the context is disposed. Which would mean that the shadow copy of the DLLs would be locked until the host process is stopped. One could select a different, unique target directory for the shadow copy on each reload. ALC might be smart enough to know when an unmanaged DLL is already loaded? This approach does seem a little hackish, though. When the plugin no longer requires a specific unmanaged DLL, one would have to restart the host process to free up memory (if my observation is correct).

It would have been nice to load the unmanaged DLLs into memory, but it might be that the limitations of this approach is the reason for why ALC does not provide such an option?

I think a unique target directory for each reload is necessary. In the absence of a better solution, shadow copy is worth prototyping to validate this guess.

By the way, I've marked this issue as 'help wanted' as I don't have time to take this on right now, but I'm willing to accept a pull request to resolve this.