【问题标题】:Unity and Random "Index was outside the bounds of the array" exception统一和随机“索引超出数组范围”异常
【发布时间】:2014-09-09 10:16:31
【问题描述】:

我们正在运行的网站有大约 15.000 个实时用户(谷歌分析)(大约 1000 个请求/秒(性能计数器))。

我们在负载均衡器后面有两个 Web 服务器。

有时每天有时每周 1 次我们的 Web 服务器之一停止执行请求并开始响应错误,并且每个请求都记录以下异常: “System.IndexOutOfRangeException - 索引超出了数组的范围。”

我们的环境:IIS 8.5、.Net 4.5.0、Mvc 5.1.0、Unity 3.5(与 3.0 状态相同)、WebActivatorEx 2.0 在 IIS 中,工作进程 1 和其他设置均采用默认设置。

我们无法捕捉到任何导致此错误的特定场景。应用程序池回收后一切都没有问题。并且在每个请求都以错误响应之前,没有任何与之相关的错误。

过去曾问过一个与相关旧 Unity 版本相关的问题: https://unity.codeplex.com/discussions/328841 http://unity.codeplex.com/workitem/11791 看不到我能做的任何事情。

这里的异常详情:

System.IndexOutOfRangeException
Index was outside the bounds of the array.

System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   at System.Linq.Enumerable.WhereListIterator`1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Microsoft.Practices.Unity.NamedTypesRegistry.RegisterType(Type t, String name)
   at Microsoft.Practices.Unity.UnityDefaultBehaviorExtension.OnRegisterInstance(Object sender, RegisterInstanceEventArgs e)
   at System.EventHandler`1.Invoke(Object sender, TEventArgs e)
   at Microsoft.Practices.Unity.UnityContainer.RegisterInstance(Type t, String name, Object instance, LifetimeManager lifetime)
   at Microsoft.Practices.Unity.UnityContainerExtensions.RegisterInstance[TInterface](IUnityContainer container, TInterface instance, LifetimeManager lifetimeManager)
   at DemoSite.News.Portal.UI.App_Start.UnityConfig.<>c__DisplayClass1.<RegisterTypes>b__0()
   at DemoSite.News.Portal.Core.Controller.BaseController.Initialize(RequestContext requestContext)
   at System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state)
   at System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__4(AsyncCallback asyncCallback, Object asyncState, ProcessRequestState innerState)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
   at System.Web.Mvc.Async.AsyncResultWrapper.Begin[TState](AsyncCallback callback, Object callbackState, BeginInvokeDelegate`1 beginDelegate, EndInvokeVoidDelegate`1 endDelegate, TState invokeState, Object tag, Int32 timeout, SynchronizationContext callbackSyncContext)
   at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

我的配置如下:

public static void RegisterTypes(IUnityContainer container) 
{ 
    var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
    container.LoadConfiguration(section); 
    ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(container));
}

初始化方法如下:

protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
    if (requestContext.RouteData.Values["ViewActionId"] != null)
    {
        int viewActionId;
        if (!int.TryParse(requestContext.RouteData.Values["ViewActionId"].ToString(), out viewActionId))
            return;

        var cacheProvider = ServiceLocator.Current.GetInstance<ICacheProvider>();
        List<ViewActionClass> viewActionClasses = null;
        string cacheKey = CacheKeyCompute.ComputeCacheKey("ViewActionClass", CacheKeyTypes.DataCache,
            new KeyValuePair<string, string>("viewActionId", viewActionId.ToString()));

        _configuration = ServiceLocator.Current.GetInstance<IConfiguration>();

        viewActionClasses = 
            cacheProvider.AddOrGetExistingWithLock<List<ViewActionClass>>(cacheKey, () =>
        {
            var viewActionClassBusiness = 
            ServiceLocator.Current.GetInstance<IViewActionClassBusiness>();

            return viewActionClassBusiness.ViewActionClassGetByViewActionId(viewActionId);
        });

        ViewBag.ActionClass = viewActionClasses;
        ViewBag.Configuration = _configuration;
    }
    base.Initialize(requestContext);
}

ICacheProvider、IConfiguration 和 IViewActionClassBusiness 的注册 xml

<type type="DemoSite.Runtime.Caching.ICacheProvider, DemoSite.Core"
        mapTo="DemoSite.Runtime.Caching.ObjectCacheProvider, DemoSite.Core">
    <lifetime type="containerControlledLifetimeManager" />
  </type>
<type type="DemoSite.Core.Configuration.IConfiguration, DemoSite.Core"
        mapTo="DemoSite.Core.Configuration.ConfigFileConfiguration, DemoSite.Core">
    <lifetime type="containerControlledLifetimeManager" />
  </type>
<type type="DemoSite.News.Business.IViewActionClassBusiness, DemoSite.News.Business"
        mapTo="DemoSite.News.Business.Default.ViewActionClassBusiness, DemoSite.News.Business.Default">
    <lifetime type="perRequestLifetimeManager" />
  </type>

可能与高流量有关。 有没有人遇到过这样的问题以及解决办法?

提前致谢

【问题讨论】:

  • Csn你贴出匿名方法App_Start.UnityConfig.&lt;&gt;c__DisplayClass1.&lt;RegisterTypes&gt;b__0()的代码? UnityConfig 类中可能有一个 RegisterTypes 方法。该方法包含调用 RegisterInstance 的 lambda。
  • 当您从 nuget 安装 Unity 引导程序时,我们使用相同的代码: public static void RegisterTypes(IUnityContainer container) { // 注意:要从 web.config 加载,请取消注释下面的行。确保将 Microsoft.Practices.Unity.Configuration 添加到 using 语句。容器.LoadConfiguration(); // TODO:在此处注册您的类型 // container.RegisterType(); } --> nuget.org/packages/Unity.Mvc
  • 您查看的源代码是否与生产环境中运行的源代码不同? App_Start.UnityConfig 在某处包含一个被调用的匿名方法,但您显示的 RegisterTypes 方法不包含 lambda。
  • 我会检查并通知您。另一件事是 ServiceLocator 有使用警告。它很旧,但有关于传递容器实例的警告:blogs.msdn.com/b/miah/archive/2009/05/12/… 并且在 UnityWebActivator 容器中传递了湖:DependencyResolver.SetResolver(new UnityDependencyResolver(container));
  • @Steven,感谢您的帮助。我们停止使用 ServiceLocator for Mvc。

标签: asp.net-mvc dependency-injection inversion-of-control unity-container ioc-container


【解决方案1】:

据我从堆栈跟踪中可以看出,您正在 Web 请求期间在容器中注册实例。 RegisterTypeRegisterInstance 方法在 Unity 中是不是线程安全的(这可能适用于 .NET 中的大多数 DI 库)。这就解释了为什么在随机点和高负载下会发生这种情况。

最好只在启动时注册您的容器,以后不要更改它。特别是使用依赖倒置原则和依赖注入模式,您尝试集中了解对象图如何连接的知识,但稍后通过进行新的注册再次分散它。即使注册在 Unity 中是线程安全的,您仍然很可能通过在运行时更改注册来引入竞争条件。

更新

您的代码有以下导致问题的代码:

ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(container));

这看起来很无辜,但实际上它会导致并发错误内存泄漏

因为new 语句在lambda 中,所以每次调用ServiceLocator.Current 时都会创建一个新的UnityServiceLocator。这本身还不错,但是UnityServiceLocator 的构造函数调用container.RegisterInstance 以在容器中注册自己。但正如我已经说过的:调用 RegisterInstance` 不是线程安全的。

但即使它是线程安全的,它仍然会导致应用程序中的内存泄漏,因为对 RegisterInstance 的调用不会替换现有注册,而是将其附加到注册列表中。这意味着容器中的UnityServiceLocator 实例列表将不断增长,并最终导致系统崩溃并出现 OutOfMemoryException。实际上你很幸运,你首先遇到了这个并发错误,因为 OOM 错误更难追溯。

解决方法实际上非常简单:将 UnityServiceLocator 的构造移出 lambda 并每次返回单个实例:

var locator = new UnityServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => locator);

UnityServiceLocator 的行为在我看来是一个设计缺陷,因为RegisterInstance 不是线程安全的,而且UnityServiceLocator 不知道它被创建了多少次,所以它永远不应该调用RegisterInstance从它的构造函数中 - 或者至少 - 不检查注册该实例是否安全。

然而,问题是删除对 RegisterInstance 的调用是一个重大更改,但对于 Unity 团队来说仍然可能是最佳解决方案。大多数用户可能不会注意到缺少的IServiceLocator 注册,如果他们这样做了,Unity 会在这种情况下传达明确的异常消息。另一种选择是让UnityServiceLocator 检查是否已经从容器中解析了任何实例,在这种情况下,从UnityServiceLocator 的构造函数中抛出 InvalidOperationException。

【讨论】:

  • 实际上每个请求都没有任何相关的注册。我们以后不会更改它。我们正在使用 WebActivatorEx,所有相关的注册都在这里完成:[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(DemoSite.News.Portal.UI.App_Start.UnityWebActivator), "Start")] - 你能告诉我代码的哪一部分看到我们在每个请求上都注册了类型吗?
  • 好的,当我再次查看堆栈跟踪时,我看到有“RegisterInstance”,但我们从未在任何地方调用它。我想知道为什么堆栈跟踪是这样的。
  • @jandark:堆栈跟踪不会说谎。查看BaseController 类的Initialize 方法。它调用 Unity。
  • BaseController Initilaze 方法中唯一与 Unity 相关的是“var cacheProvider = ServiceLocator.Current.GetInstance();”你觉得它会造成问题吗?
  • 我在我的问题中添加了 Initialize 方法。您将看到以相同方式调用 IConfiguration 和 IViewActionClassBusiness。但是我之前没有指定,因为没有区别。我们唯一在 AddOrGetExistingWithLock 方法中使用带有 Lazy 类的锁。但同样的方法也用于其他地方。而且没有问题。 -对于注册码,我们使用的是xml配置,非常基础。像,对于Cache和Configuration类我们使用ContainerControlledLifetimeManager,其他的PerRequest
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-05
  • 2012-11-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-17
相关资源
最近更新 更多