繁体   English   中英

隔离AppDomain中抛出的异常以免崩溃应用程序

[英]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英镑并且不要运行任何~FinialiserDispose()

从这一点看, AppDomain从根本上没有做它声称的(或者它看起来要声称的)要做的事情,因为它只是将所有异常直接传递到父域,使得它对于隔离无用而且对于任何类型来说都很弱安全性(如果我可以取出父进程,我可能会破坏机器)。 这在.Net中是一个非常根本的失败,所以我必须在我的代码中遗漏一些东西。

我错过了什么吗? 是否有一些方法可以让AppDomain实际上隔离它正在运行的代码并在发生错误时卸载? 我使用了错误的东西,是否有其他.Net功能提供异常隔离?

您无法对Environment.Exit()执行任何操作,就像您无法阻止用户在任务管理器中终止您的进程一样。 对此的静态分析也可以被规避。 我不会太担心这个。 你可以做的事情,你真的不能做的事情。

AppDomain确实做了它声称要做的事情。 然而,它实际上声称做,你相信它声称做是两回事什么。

任何地方未处理的异常都将取消您的应用程序。 AppDomains不能防范这些。 但是你可以通过以下方法防止未处理的异常越过AppDomain边界(抱歉,没有代码)

  1. 创建您的AppDomain
  2. 在此AppDomain中加载和解包您的插件控制器
  3. 通过这个控制器控制插件
  4. 通过将它们包装在try / catch块中来隔离对第三方插件的调用。

实际上,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能够处理大多数未处理的异常。 它们不属于的异常类别称为异步异常 现在我们有异步/等待, 但是它存在 ,找到有关此类异常的文档有点困难,它们有三种常见形式:

  • StackOverflowException
  • OutOfMemoryException异常
  • ThreadAbortException

这些异常被认为是异步的,因为它们可以被抛出到任何地方 ,甚至在CIL操作码之间。 前两个是关于整个环境的死亡。 CLR缺乏凤凰的权力,它无法处理这些例外,因为这样做的手段已经死了。 请注意,这些规则仅在CLR抛出它们时才存在。 如果你只是新手和实例并自己抛出它们,它们就像普通的异常一样。

旁注:如果你曾经看过托管CLR的进程的内存转储,你会发现堆上总是有 OutOfMemoryExceptionThreadAbortExceptionStackOverflowException ,但是它们没有你能看到的根,并且它们永远不会被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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM