From 9da491c18e22f53b9429242bf391fd920ee54dcc Mon Sep 17 00:00:00 2001 From: Jason Ginchereau Date: Wed, 25 Jun 2025 14:04:43 -1000 Subject: [PATCH 1/3] Fix worker assembly loading bug --- src/NodeApi.DotNetHost/ManagedHost.cs | 60 ++++++++++++++------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/NodeApi.DotNetHost/ManagedHost.cs b/src/NodeApi.DotNetHost/ManagedHost.cs index 65860e50..be60777d 100644 --- a/src/NodeApi.DotNetHost/ManagedHost.cs +++ b/src/NodeApi.DotNetHost/ManagedHost.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -49,13 +50,13 @@ public sealed class ManagedHost : JSEventEmitter, IDisposable /// /// Mapping from assembly file paths to loaded assemblies. /// - private readonly Dictionary _loadedAssembliesByPath = new(); + private readonly ConcurrentDictionary _loadedAssembliesByPath = new(); /// /// Mapping from assembly names (not including version or other parts) to /// loaded assemblies. /// - private readonly Dictionary _loadedAssembliesByName = new(); + private readonly ConcurrentDictionary _loadedAssembliesByName = new(); /// /// Tracks names of assemblies that have been exported to JS. @@ -467,7 +468,7 @@ public JSValue LoadAssembly(JSCallbackArgs args) assembly = LoadAssembly(assemblyNameOrFilePath, allowNativeLibrary: true); } - if (!_exportedAssembliesByName.Contains(assembly.GetName().Name!)) + if (assembly != null && !_exportedAssembliesByName.Contains(assembly.GetName().Name!)) { _typeExporter.ExportAssemblyTypes(assembly); _exportedAssembliesByName.Add(assembly.GetName().Name!); @@ -525,41 +526,44 @@ private Assembly LoadAssembly(string assemblyNameOrFilePath, bool allowNativeLib "or the name of a system assembly (without path or DLL extension)."); } - Assembly assembly; - try + Assembly? assembly = _loadedAssembliesByPath.GetOrAdd(assemblyFilePath, _ => { + try + { #if NETFRAMEWORK || NETSTANDARD - // TODO: Load assemblies in a separate appdomain. - assembly = Assembly.LoadFrom(assemblyFilePath); + // TODO: Load assemblies in a separate appdomain. + return Assembly.LoadFrom(assemblyFilePath); #else - assembly = _loadContext.LoadFromAssemblyPath(assemblyFilePath); + return _loadContext.LoadFromAssemblyPath(assemblyFilePath); #endif - } - catch (BadImageFormatException) - { - if (!allowNativeLibrary) - { - throw; } + catch (BadImageFormatException) + { + if (!allowNativeLibrary) + { + throw; + } - // This might be a native DLL, not a managed assembly. - // Load the native library, which enables it to be auto-resolved by - // any later DllImport operations for the same library name. - NativeLibrary.Load(assemblyFilePath); + // This might be a native DLL, not a managed assembly. + // Load the native library, which enables it to be auto-resolved by + // any later DllImport operations for the same library name. + NativeLibrary.Load(assemblyFilePath); + return null; + } + catch (FileNotFoundException fnfex) + { + throw new FileNotFoundException( + $"Assembly file not found: {assemblyNameOrFilePath}", fnfex); + } + }); - Trace($"< ManagedHost.LoadAssembly() => {assemblyFilePath} (native library)"); - return null!; - } - catch (FileNotFoundException fnfex) + if (assembly != null) { - throw new FileNotFoundException( - $"Assembly file not found: {assemblyNameOrFilePath}", fnfex); + _loadedAssembliesByName.GetOrAdd(assembly.GetName().Name!, assembly); } - _loadedAssembliesByPath.Add(assemblyFilePath, assembly); - _loadedAssembliesByName.Add(assembly.GetName().Name!, assembly); - - Trace($"< ManagedHost.LoadAssembly() => {assemblyFilePath}, {assembly.GetName().Version}"); + var version = assembly?.GetName().Version.ToString() ?? "(native library)"; + Trace($"< ManagedHost.LoadAssembly() => {assemblyFilePath} {version}"); return assembly; } From 9957125a2b03393f4dec0b76026d786b2505651b Mon Sep 17 00:00:00 2001 From: Jason Ginchereau Date: Wed, 25 Jun 2025 14:36:35 -1000 Subject: [PATCH 2/3] Fix nullability --- src/NodeApi.DotNetHost/ManagedHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NodeApi.DotNetHost/ManagedHost.cs b/src/NodeApi.DotNetHost/ManagedHost.cs index be60777d..39ceb7ae 100644 --- a/src/NodeApi.DotNetHost/ManagedHost.cs +++ b/src/NodeApi.DotNetHost/ManagedHost.cs @@ -488,7 +488,7 @@ private JSValue ResolveAssembly(JSCallbackArgs args) return default; } - private Assembly LoadAssembly(string assemblyNameOrFilePath, bool allowNativeLibrary) + private Assembly? LoadAssembly(string assemblyNameOrFilePath, bool allowNativeLibrary) { Trace($"> ManagedHost.LoadAssembly({assemblyNameOrFilePath})"); @@ -562,7 +562,7 @@ private Assembly LoadAssembly(string assemblyNameOrFilePath, bool allowNativeLib _loadedAssembliesByName.GetOrAdd(assembly.GetName().Name!, assembly); } - var version = assembly?.GetName().Version.ToString() ?? "(native library)"; + var version = assembly?.GetName().Version?.ToString() ?? "(native library)"; Trace($"< ManagedHost.LoadAssembly() => {assemblyFilePath} {version}"); return assembly; } From e285fc719c888403901fa0d9da5414b5b3acbcde Mon Sep 17 00:00:00 2001 From: Jason Ginchereau Date: Wed, 25 Jun 2025 15:07:42 -1000 Subject: [PATCH 3/3] Disable IDE0350 lambda expression format rule --- .editorconfig | 1 + src/NodeApi.DotNetHost/ManagedHost.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index e7ef5f29..8705144d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -161,6 +161,7 @@ dotnet_diagnostic.CA1835.severity = none dotnet_diagnostic.IDE0290.severity = none # Use primary constructor dotnet_diagnostic.IDE0065.severity = none # Using directives must be placed outside of namespace +dotnet_diagnostic.IDE0350.severity = none # Lambda expression can be simplified # Collection initialization can be simplified. # This relies on implicit conversions in ways that are sometimes undesirable. diff --git a/src/NodeApi.DotNetHost/ManagedHost.cs b/src/NodeApi.DotNetHost/ManagedHost.cs index 39ceb7ae..402befe6 100644 --- a/src/NodeApi.DotNetHost/ManagedHost.cs +++ b/src/NodeApi.DotNetHost/ManagedHost.cs @@ -562,7 +562,7 @@ private JSValue ResolveAssembly(JSCallbackArgs args) _loadedAssembliesByName.GetOrAdd(assembly.GetName().Name!, assembly); } - var version = assembly?.GetName().Version?.ToString() ?? "(native library)"; + string version = assembly?.GetName().Version?.ToString() ?? "(native library)"; Trace($"< ManagedHost.LoadAssembly() => {assemblyFilePath} {version}"); return assembly; }