6

今天我們在生產中發生了重大中斷,其中內存從我們的Web服務器快速消失。這可以追溯到Ninject中的緩存機制(我認爲它是Activation Cache或其他 - 不完全確定)。在調查這個問題後,我們得出結論,我們在範圍回調中有一個循環引用。由於循環範圍回調引用導致的注入內存泄漏

class View 
{ 
    Presenter presenter; 

    View() 
    { 
     //service locators are smelly, but webforms forces this uglyness 
     this.presenter = ServiceLocator.Get<Func<View, Presenter>>()(this); 

     this.presenter.InitUI(); 
    } 
} 

class Presenter 
{ 
    CookieContainer cookieContainer; 
    View view; 

    Presenter(View view, CookieContainer cookieContainer) 
    { 
     this.view = view; 
     this.cookieContainer = cookieContainer; 
    } 
} 

class CookieContainer 
{ 
    HttpRequest request; 
    HttpResponse response; 

    CookieContainer() 
    { 
     this.request = HttpRequest.Current.Request; 
     this.response = HttpRequest.Current.Response; 
    } 
} 

Bind<Func<View, Presenter>>().ToMethod(ctx => view => 
     ctx.Kernel.Get<Presenter>(new ConstructorArgument("view", view))); 

Bind<Presenter>().ToSelf().InTransientScope(); 
Bind<CookieContainer>().ToSelf().InRequestScope(); 

這是導致問題的代碼表示。貌似發生了什麼事情是CookieContainer的範圍回調是HttpContext.Current,並且CookieContainer也引用了HttpContext.Current。因此,Ninject永遠不能修剪它的緩存中的CookieContainer實例,因爲CookieContainer實例將範圍回調對象保持在活動狀態。當我們將CookieContainer的範圍更改爲瞬態時,所有工作都正常,正如我們所預料的那樣。但我仍然不完全確定爲什麼會出現這種情況,因爲看起來這是相當傳統的做法。也許沒有......

我也很困惑,因爲我認爲如果回調對象一直保持活着,那麼不應該Ninject只是從緩存回傳相同的實例,看起來好像回調仍然是活着,所以實例應該在範圍內?爲什麼會不停地獲取CookieContainer的新實例並緩存它們?我想還會有其他問題與不正確的對象有關,但這至少只是一個錯誤,而不是內存泄漏。

我的問題是a)我們是否正確診斷出這個錯誤? b)有沒有建議採取這種做法不要再發生? c)我可以修復代碼來檢查這種類型的循環依賴(假設我們已經正確診斷)嗎?

回答

7

簡單地說,緩存是實例的弱引用範圍對象的字典。只要範圍是活的,引用的對象也保持活動狀態。所以是的,如果你的CookieContainer引用HttpContext.Current並且處於請求範圍內,這個標準機制將永遠不會適用於釋放它們。

但是在InRequestScope的特殊情況下,OnePerRequestModule實現了另一個釋放機制,在請求完成後立即釋放所有InRequestScoped對象。如果您使用的是Ninject.Web或Ninject.Web.MVC3的最新版本,那麼它是預配置的。否則,您必須通過在web.config中配置此HTTPModule來顯式添加它。

你不明白的另一點是Ninject只要它活着就不會返回相同的對象。例如。在請求範圍內,它將爲請求返回相同的對象。如果多個請求同時運行,它們都會得到不同的實例。

+0

你今天救了我屁股的人。我們有完全相同的問題! – pkolodziej 2014-01-03 21:48:27