【问题标题】:RuntimeMiddleware Destination array was not long enough ErrorRuntimeMiddleware 目标数组不够长 错误
【发布时间】:2020-09-21 00:16:56
【问题描述】:

我在 asp.net core 2.1 中有一个编写 RuntimeMiddleware 服务,它工作正常,但有时会出错。

错误:

目标数组不够长。检查目标索引、长度和数组的下限。

代码:

public class RuntimeMiddlewareService
{
    private Func<RequestDelegate, RequestDelegate> middleware;

    private IApplicationBuilder appBuilder;

    internal void Use(IApplicationBuilder app)
    => appBuilder = app.Use(next => context => middleware == null ? next(context) : middleware(next)(context));

    public void Configure(Action<IApplicationBuilder> action)
    {
        var app = appBuilder.New();
        action(app);
        middleware = next => app.Use(_ => next).Build(); //the line getting error
    }
}

【问题讨论】:

  • 也许您可以在发生此错误时与我们分享堆栈跟踪。

标签: c# asp.net-core


【解决方案1】:

我也使用Installing a new middleware at runtime in ASP.Net Core的方法遇到了这个间歇性问题

堆栈跟踪

System.ArgumentException

Destination array was not long enough. Check the destination index, length, and the array's lower bounds. (Parameter 'destinationArray')

MyAssembly.Services.RuntimeMiddlewareService+<>c__DisplayClass2_0.<Use>b__1 


System.ArgumentException:
   at System.Array.Copy (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Collections.Generic.List`1.CopyTo (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Collections.Generic.EnumerableHelpers.ToArray (System.Linq, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)
   at System.Linq.Buffer`1..ctor (System.Linq, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)
   at System.Linq.Enumerable+ReverseIterator`1.MoveNext (System.Linq, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)
   at Microsoft.AspNetCore.Builder.ApplicationBuilder.Build (Microsoft.AspNetCore.Http, Version=3.1.6.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at MyAssembly.Services.RuntimeMiddlewareService+<>c__DisplayClass2_0.<Use>b__1 (MyAssembly, Version=1.0.12.0, Culture=neutral, PublicKeyToken=nullMyAssembly, Version=1.0.12.0, Culture=neutral, PublicKeyToken=null: /home/vsts/work/1/s/src/MyAssembly/Services/RuntimeMiddlewareService.csDeploymentManagementProxy, Version=1.0.12.0, Culture=neutral, PublicKeyToken=null: 14)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke (Microsoft.AspNetCore.StaticFiles, Version=3.1.6.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware+<Invoke>d__5.MoveNext (Swashbuckle.AspNetCore.SwaggerUI, Version=5.4.1.0, Culture=neutral, PublicKeyToken=4232c99127b3c254)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware+<Invoke>d__4.MoveNext (Swashbuckle.AspNetCore.Swagger, Version=5.4.1.0, Culture=neutral, PublicKeyToken=62657d7474907593)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware+<Invoke>d__5.MoveNext (Microsoft.AspNetCore.Authorization.Policy, Version=3.1.6.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware+<Invoke>d__6.MoveNext (Microsoft.AspNetCore.Authentication, Version=3.1.6.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT`1+<ProcessRequestAsync>d__2.MoveNext (Microsoft.AspNetCore.Server.IIS, Version=3.1.6.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)

我不得不进行大量挖掘才能找到根本原因:

从 ApplicationBuilder 的 Build 方法开始 https://github.com/dotnet/aspnetcore/blob/4ef204e13b88c0734e0e94a1cc4c0ef05f40849e/src/Http/Http/src/Builder/ApplicationBuilder.cs#L82

private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
    _components.Add(middleware);
    return this;
}

public RequestDelegate Build()
{
    RequestDelegate app = context =>
    {
        // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
        // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
        var endpoint = context.GetEndpoint();
        var endpointRequestDelegate = endpoint?.RequestDelegate;
        if (endpointRequestDelegate != null)
        {
            var message =
                $"The request reached the end of the pipeline without executing the endpoint: '{endpoint.DisplayName}'. " +
                $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
                $"routing.";
            throw new InvalidOperationException(message);
        }

        context.Response.StatusCode = 404;
        return Task.CompletedTask;
    };

    foreach (var component in _components.Reverse())
    {
        app = component(app);
    }

    return app;
}

要注意的行是:

foreach (var component in _components.Reverse())

Reverse 致电ReverseIterator https://github.com/microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs#L911

public static IEnumerable<TSource> Reverse<TSource>(this IEnumerable<TSource> source) {
    if (source == null) throw Error.ArgumentNull("source");
    return ReverseIterator<TSource>(source);
}

static IEnumerable<TSource> ReverseIterator<TSource>(IEnumerable<TSource> source) {
    Buffer<TSource> buffer = new Buffer<TSource>(source);
    for (int i = buffer.count - 1; i >= 0; i--) yield return buffer.items[i];
}

创建一个Buffer&lt;TElement&gt; https://github.com/microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs#L2664

struct Buffer<TElement>
{
    internal TElement[] items;
    internal int count;

    internal Buffer(IEnumerable<TElement> source) {
        TElement[] items = null;
        int count = 0;
        ICollection<TElement> collection = source as ICollection<TElement>;
        if (collection != null) {
            count = collection.Count;
            if (count > 0) {
                items = new TElement[count];
                collection.CopyTo(items, 0);
            }
        }
        else {
            foreach (TElement item in source) {
                if (items == null) {
                    items = new TElement[4];
                }
                else if (items.Length == count) {
                    TElement[] newItems = new TElement[checked(count * 2)];
                    Array.Copy(items, 0, newItems, 0, count);
                    items = newItems;
                }
                items[count] = item;
                count++;
            }
        }
        this.items = items;
        this.count = count;
    }

    internal TElement[] ToArray() {
        if (count == 0) return new TElement[0];
        if (items.Length == count) return items;
        TElement[] result = new TElement[count];
        Array.Copy(items, 0, result, 0, count);
        return result;
    }
}

考虑以下几行:

if (collection != null) {
    count = collection.Count;
    if (count > 0) {
        items = new TElement[count];
        collection.CopyTo(items, 0);
    }
}

集合是通过引用传递的。似乎在获取集合计数和创建相同大小的新数组之间的某个地方,集合的大小增加了超出TElement 数组的大小,导致错误“目标数组不够长。检查目标索引,长度和数组的下限。(参数'destinationArray')。这将表明存在线程安全问题并解释间歇性错误。

下一个问题是,线程安全问题的根源在哪里?

这对我个人来说还是个谜。我的应用程序中的RuntimeMiddlewareService 是在启动时创建的单例。并且它的Configure 函数肯定会被调用,但不够频繁(据我所知)来解释错误。所以 _middleware Func 可能会在其他地方被调用。也就是说,在对 Use 和 Build 的调用周围添加一个锁定对象解决了这个问题,可能是因为线程 1 正在调用 Build 而线程 2 刚刚调用 Use(这向 collection 添加了一个新项目) .

public class RuntimeMiddlewareService : IRuntimeMiddlewareService
{
    private static readonly object _middlewareLock = new object();
    private Func<RequestDelegate, RequestDelegate> _middleware;

    private IApplicationBuilder _appBuilder;

    public void Use(IApplicationBuilder app)
    => _appBuilder = app.Use(next => context => _middleware == null ? next(context) : _middleware(next)(context));

    public void Configure(Action<IApplicationBuilder> action)
    {
        var app = _appBuilder.New();
        action(app);
        _middleware = next =>
        {
            lock (_middlewareLock)
            {
                return app.Use(_ => next).Build();
            }
        };
    }
}

【讨论】:

    猜你喜欢
    • 2012-05-08
    • 2012-08-02
    • 1970-01-01
    • 2017-06-20
    • 2014-05-11
    • 2016-04-08
    • 2022-01-23
    • 1970-01-01
    • 2020-03-16
    相关资源
    最近更新 更多