簡體   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