[英]Isolate exceptions thrown in an AppDomain to not Crash the Application
TL; DR:如何通过杀死主进程来隔离加载项异常?
我想要一个非常稳定的.Net应用程序,它在AppDomain
中运行不太稳定的代码。 这似乎是AppDomain
的主要目的之一(嗯,那和安全沙箱)但它似乎不起作用。
例如在AddIn.exe
:
public static class Program
{
public static void Main(string[] args)
{
throw new Exception("test")
}
}
在我的“稳定”代码中调用:
var domain = AppDomain.CreateDomain("sandbox");
domain.UnhandledException += (sender, e) => {
Console.WriteLine("\r\n ## Unhandled: " + ((Exception) e.ExceptionObject).Message);
};
domain.ExecuteAssemblyByName("AddIn.exe", "arg A", "arg B")
AppDomain
抛出的异常会直接传递给创建域的应用程序。 我可以使用domain.UnhandledException
记录这些并在包装器应用程序中捕获它们。
但是,抛出更多有问题的异常,例如:
public static class Program
{
public static void Main(string[] args)
{
Stackoverflow(1);
}
static int Stackoverflow(int x)
{
return Stackoverflow(++x);
}
}
这将抛出stackoverflow异常,每次都会终止整个应用程序。 它甚至没有触发domain.UnhandledException
- 它只是直接杀死整个应用程序。
此外,从AppDomain
内部调用Environment.Exit()
类的东西也会杀死父应用程序,不要传递GO,不要收取200英镑并且不要运行任何~Finialiser
或Dispose()
。
从这一点看, AppDomain
从根本上没有做它声称的(或者它看起来要声称的)要做的事情,因为它只是将所有异常直接传递到父域,使得它对于隔离无用而且对于任何类型来说都很弱安全性(如果我可以取出父进程,我可能会破坏机器)。 这在.Net中是一个非常根本的失败,所以我必须在我的代码中遗漏一些东西。
我错过了什么吗? 是否有一些方法可以让AppDomain
实际上隔离它正在运行的代码并在发生错误时卸载? 我使用了错误的东西,是否有其他.Net功能提供异常隔离?
您无法对Environment.Exit()
执行任何操作,就像您无法阻止用户在任务管理器中终止您的进程一样。 对此的静态分析也可以被规避。 我不会太担心这个。 你可以做的事情,你真的不能做的事情。
AppDomain
确实做了它声称要做的事情。 然而,它实际上声称做,你相信它声称做是两回事什么。
任何地方未处理的异常都将取消您的应用程序。 AppDomains不能防范这些。 但是你可以通过以下方法防止未处理的异常越过AppDomain边界(抱歉,没有代码)
实际上,AppDomain为您提供的唯一功能是能够加载,隔离和卸载在运行时期间您不完全信任的程序集 。 您无法在执行的AppDomain中执行此操作。 所有加载的程序集一直保持到执行暂停,并且它们享有与AppDomain中所有其他代码相同的权限集。
为了更清楚,这里有一些看起来像c#的伪代码,可以防止第三方代码在AppDomain边界上抛出异常。
public class PluginHost : IPluginHost, IPlugin
{
private IPlugin _wrapped;
void IPluginHost.Load(string filename, string typename)
{
// load the assembly (filename) into the AppDomain.
// Activator.CreateInstance the typename to create 3rd party plugin
// _wrapped = the plugin instance
}
void IPlugin.DoWork()
{
try
{
_wrapped.DoWork();
}catch(Exception ex)
// log
// unload plugin whatevs
}
}
此类型将在您的插件AppDomain中创建,其代理将在应用程序AppDomain中解包。 你使用它来插入插件AppDomain中的插件。 它可以防止异常越过AppDomain边界,执行加载任务等。将插件类型的代理拉入应用程序AppDomain是非常危险的,因为代理可能以某种方式进入您手中的任何非MarshalByRefObject的对象类型(例如, Throw new MyCustomException()
)将导致插件程序集在应用程序AppDomain中加载,从而使您的隔离工作无效。
(这有点过于简化了)
我会抛出一些随意的想法,但@Will所说的在权限,CAS,安全透明度和沙盒方面是正确的。 AppDomains不是超人。 但是,关于异常,AppDomain能够处理大多数未处理的异常。 它们不属于的异常类别称为异步异常 。 现在我们有异步/等待, 但是它存在 ,找到有关此类异常的文档有点困难,它们有三种常见形式:
这些异常被认为是异步的,因为它们可以被抛出到任何地方 ,甚至在CIL操作码之间。 前两个是关于整个环境的死亡。 CLR缺乏凤凰的权力,它无法处理这些例外,因为这样做的手段已经死了。 请注意,这些规则仅在CLR抛出它们时才存在。 如果你只是新手和实例并自己抛出它们,它们就像普通的异常一样。
旁注:如果你曾经看过托管CLR的进程的内存转储,你会发现堆上总是有
OutOfMemoryException
,ThreadAbortException
和StackOverflowException
,但是它们没有你能看到的根,并且它们永远不会被GCed。 是什么赋予了? 它们存在的原因是因为CLR预分配它们 - 它们无法在需要时分配它们。 当我们内存不足时,它将无法分配OutOfMemoryException
。
有一个软件能够处理所有这些异常。 从2005年开始,SQL就能够使用名为SQLCLR的功能运行.NET程序集。 SQL服务器是一个相当重要的过程,并且.NET程序集抛出OutOfMemoryException
并且它使整个SQL进程失效似乎非常不可取,因此SQL团队不会让这种情况发生。
他们使用称为约束执行和关键区域的.NET 2.0功能来实现此目的。 这就是ExecuteCodeWithGuaranteedCleanup
东西。 如果您能够自己托管CLR,请从本机代码开始并自行启动CLR,然后您就可以更改升级策略:从本机代码中您可以处理这些托管异常。 这就是SQL CLR处理这些情况的方式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.