问题
前些日子,在使用Roslyn时,发现unity屡屡崩溃。修复Roslyn多线程编译在Unity中产生崩溃的bug
报错日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 0x00007FF827E1AB6A (KERNELBASE) RaiseException 0x00007FFF69CB0C1C (mono-2.0-bdwgc) [X\mono\mono\utils\mono-log-common.c:143] mono_log_write_logfile 0x00007FFF69C9C822 (mono-2.0-bdwgc) [X\mono\mono\eglib\goutput.c:172] monoeg_g_logv_nofree 0x00007FFF69C9C888 (mono-2.0-bdwgc) [X\mono\mono\eglib\goutput.c:187] monoeg_g_log 0x00007FFF69D8C1E7 (mono-2.0-bdwgc) [X\mono\mono\metadata\image.c:1632] mono_image_storage_tryaddref 0x00007FFF69D8C4B8 (mono-2.0-bdwgc) [X\mono\mono\metadata\image.c:1697] mono_image_storage_open 0x00007FFF69D8C8BB (mono-2.0-bdwgc) [X\mono\mono\metadata\image.c:1777] do_mono_image_open 0x00007FFF69D4BBFB (mono-2.0-bdwgc) [X\mono\mono\metadata\icall.c:6209] ves_icall_System_Reflection_Assembly_InternalGetAssemblyName 0x00007FFF69D652E0 (mono-2.0-bdwgc) [X\mono\mono\metadata\icall-def.h:608] ves_icall_System_Reflection_Assembly_InternalGetAssemblyName_raw 0x0000021F5FB8E2E6 (Mono JIT Code) (wrapper managed-to-native) System.Reflection.Assembly:InternalGetAssemblyName (string,Mono.MonoAssemblyName&,string&) 0x0000021F5FB8E00B (Mono JIT Code) System.Reflection.AssemblyName:GetAssemblyName (string) 0x0000021F5FB8DD73 (Mono JIT Code) Microsoft.CodeAnalysis.MonoGlobalAssemblyCache:CreateAssemblyNameFromFile (string) 0x0000021F5FB84AE3 (Mono JIT Code) Microsoft.CodeAnalysis.MonoGlobalAssemblyCache:ResolvePartialName (string,string&,System.Collections.Immutable.ImmutableArray`1<System.Reflection.ProcessorArchitecture>,System.Globalization.CultureInfo) 0x0000021F5FB83AB2 (Mono JIT Code) Microsoft.CodeAnalysis.Scripting.Hosting.GacFileResolver:Resolve (string) 0x0000021F5FB7C03B (Mono JIT Code) Microsoft.CodeAnalysis.Scripting.Hosting.RuntimeMetadataReferenceResolver:ResolveMissingAssembly (Microsoft.CodeAnalysis.MetadataReference,Microsoft.CodeAnalysis.AssemblyIdentity) 0x0000021F5FB7BE1E (Mono JIT Code) Microsoft.CodeAnalysis.Scripting.ScriptMetadataResolver:ResolveMissingAssembly (Microsoft.CodeAnalysis.MetadataReference,Microsoft.CodeAnalysis.AssemblyIdentity) 0x0000021F5FB78E44 (Mono JIT Code) Microsoft.CodeAnalysis.CommonReferenceManager`2<TCompilation_REF, TAssemblySymbol_REF>:TryResolveMissingReference
|
经过阅读日志得知,问题出在:
0x0000021F5FB8E2E6 (Mono JIT Code) (wrapper managed-to-native) System.Reflection.Assembly:InternalGetAssemblyName (string,Mono.MonoAssemblyName&,string&)
之后就是进入了Mono内核的处理程序集环节,但就是这一步出现了问题。
Mono处理这块内容时的源码,对锁的处理出现了bug(这一点在新版mono中已修复,但在我测试环境Unity 2022.61f中仍未修复。)
解决方案
那么摆在眼前的路有两个
从Mono入手的话,可以找到Mono关于此修复的Commit,把image.c的代码更新一下重新构建dll即可。
本文的重头戏在于从Roslyn入手
流程
配合日志可以发现,这个问题在于Roslyn尝试寻找程序集。查询Roslyn官方文档可知,这主要通过MetadataResolver来实现。
查询Roslyn官方文档,可知,当我们调用CSharpScript.Create尝试编译时,我们可以为其指定一个ScriptOptions,其正好可以通过ScriptOptions.WithMetadataResolver来赋予属于自己的MetadataResolver!
至此的难点在于怎么修复> MetadataResolver。 关注其源码可知class ScriptMetadataResolver : MetadataReferenceResolver, IEquatable<LockedMetadataResolver>继承了MetadataReferenceResolver抽象类……但是ScriptMetadataResolver是sealed的,这个类没法继承,想实现里面的方法更是天方夜谭,他调用了internal的方法……
难道我们只能直接修改MetadataResolver,或者新建一个类实现方法,重新编译一份Roslyn了吗……?这当然可以,但是不够优雅!我们注意到,抽象类的方法都是public的,这个抽象类也是public的。我们没法直接实现这个抽象类的方法,那么答案呼之欲出:装饰模式。我们新建一个类作为wrapper,实现里面的方法,在关键的方法加上自己的锁即可!其他方法直接调用原本的官方默认实现。
源码
因为采用了装饰模式,代码不长,使用时只需在ScriptOptions.WithMetadataResolver添加上LockedMetadataResolver即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| using System; using System.Collections.Generic; using System.Collections.Immutable;
namespace Microsoft.CodeAnalysis.Scripting { class LockedMetadataResolver : MetadataReferenceResolver, IEquatable<LockedMetadataResolver> { private static readonly object _lockObject = new (); private readonly ScriptMetadataResolver _resolver;
public LockedMetadataResolver(ScriptMetadataResolver innerResolver) { _resolver = innerResolver; } # pragma warning disable CS8632 public ScriptMetadataResolver WithSearchPaths(params string[] searchPaths) => _resolver.WithSearchPaths(searchPaths);
public ScriptMetadataResolver WithSearchPaths(IEnumerable<string> searchPaths) => _resolver.WithSearchPaths(searchPaths);
public ScriptMetadataResolver WithSearchPaths(ImmutableArray<string> searchPaths) => _resolver.WithSearchPaths(searchPaths);
public ScriptMetadataResolver WithBaseDirectory(string? baseDirectory) => _resolver.WithBaseDirectory(baseDirectory);
public override PortableExecutableReference? ResolveMissingAssembly(MetadataReference definition, AssemblyIdentity referenceIdentity) { lock(_lockObject) { return _resolver.ResolveMissingAssembly(definition, referenceIdentity); } } #nullable enable public bool Equals(LockedMetadataResolver? other) => _resolver.Equals(other); public override bool Equals(object? other) => Equals(other as LockedMetadataResolver);
public override int GetHashCode() => _resolver.GetHashCode(); #pragma warning disable CS8765 public override ImmutableArray<PortableExecutableReference> ResolveReference(string reference, string baseFilePath, MetadataReferenceProperties properties) => _resolver.ResolveReference(reference, baseFilePath, properties);
public override bool ResolveMissingAssemblies => _resolver.ResolveMissingAssemblies; #pragma warning restore CS8765 } }
|
小结
本文算是对解决unity该bug的小结……这个bug困扰了我两天,期间尝试过了各种升降版本的办法……没想到是官方的bug, 希望早日修复。
日后我还会分享关于unity学习过程中的更多知识,欢迎关注。