Client runtime: load assemblies by path again so Assembly.Location is the file; tolerate locked files in test cleanup
Render plan and fractal scene derive directories from Assembly.Location; the in-memory load emptied it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d2f280d9f2
commit
739e2bd93e
2 changed files with 30 additions and 19 deletions
|
|
@ -83,7 +83,7 @@ public sealed class ClientRuntimeLoader : IDisposable
|
|||
|
||||
try
|
||||
{
|
||||
var assembly = loadContext.LoadFromFileWithoutLock(assemblyPath);
|
||||
var assembly = loadContext.LoadFromAssemblyPath(assemblyPath);
|
||||
var factoryTypes = assembly.GetTypes()
|
||||
.Where(type => typeof(IAquariumRuntimeFactory).IsAssignableFrom(type)
|
||||
&& type is { IsAbstract: false, IsInterface: false })
|
||||
|
|
@ -336,23 +336,7 @@ public sealed class ClientRuntimeLoader : IDisposable
|
|||
}
|
||||
|
||||
var path = resolver.ResolveAssemblyToPath(assemblyName);
|
||||
return path == null ? null : LoadFromFileWithoutLock(ResolveTrusted(trustedRoot, path));
|
||||
}
|
||||
|
||||
// LoadFromAssemblyPath maps the file and, on Windows, keeps it locked until the unloaded context is collected:
|
||||
// a rebuilt or deleted client dll (and its temp directory) would stay blocked. Managed assemblies are read into
|
||||
// memory instead, with their symbols when present, so no file handle outlives the load.
|
||||
public Assembly LoadFromFileWithoutLock(string path)
|
||||
{
|
||||
using var image = new MemoryStream(File.ReadAllBytes(path));
|
||||
var symbolsPath = Path.ChangeExtension(path, ".pdb");
|
||||
if (!File.Exists(symbolsPath))
|
||||
{
|
||||
return LoadFromStream(image);
|
||||
}
|
||||
|
||||
using var symbols = new MemoryStream(File.ReadAllBytes(symbolsPath));
|
||||
return LoadFromStream(image, symbols);
|
||||
return path == null ? null : LoadFromAssemblyPath(ResolveTrusted(trustedRoot, path));
|
||||
}
|
||||
|
||||
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,24 @@ public sealed class ClientRuntimeLoaderTrustTests : IDisposable
|
|||
outside = Directory.CreateDirectory(Path.Combine(work, "outside")).FullName;
|
||||
}
|
||||
|
||||
public void Dispose() => Directory.Delete(work, recursive: true);
|
||||
// A loaded assembly stays locked on Windows until its unloaded context is collected: tolerate that here.
|
||||
public void Dispose()
|
||||
{
|
||||
for (var attempt = 0; attempt < 5; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(work, recursive: true);
|
||||
return;
|
||||
}
|
||||
catch (Exception error) when (error is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A real client assembly: the minimal sample, copied so its location is ours to choose.
|
||||
private static string Copy(string directory, string name = "client.dll")
|
||||
|
|
@ -376,6 +393,16 @@ public sealed class ClientRuntimeLoaderTrustTests : IDisposable
|
|||
Assert.Contains("cannot be the client runtime trusted root", errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ALoadedClientSeesItsOwnFilePathAsAssemblyLocation()
|
||||
{
|
||||
var path = Copy(trusted, "a.dll");
|
||||
using var loader = new ClientRuntimeLoader(Options, path);
|
||||
loader.Load();
|
||||
|
||||
Assert.Equal(ClientRuntimeLoader.ResolveLinks(path), loader.Runtime.GetType().Assembly.Location);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoggedPathsEscapeAstralFormatCharactersAndLoneSurrogatesByCodePoint()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue