на одном из Продакшен серверов возникло исключение.
2019-06-10 13:22:45.769 +10:00 [ERR] ОШИБКА ПОДГОТОВКИ ДАННЫХ К ОБМЕНУ. KeyExchange TcpIp=209 Plat=2 P=10/12 Stolb=_ Addr=9
NCalc.EvaluationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct. ---> System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at NCalc.Expression.Compile(String expression, Boolean nocache)
at NCalc.Expression.HasErrors()
--- End of inner exception stack trace ---
at NCalc.Expression.ToLambda[TResult]()
at Shared.Helpers.HelpersString.CalculateMathematicFormat(String str, Int32 row) in /src/Shared/Helpers/HelpersString.cs:line 145
at Shared.Helpers.HelpersString.<>c__DisplayClass0_0.<StringTemplateInsert>g__Evaluator|0(Match match) in /src/Shared/Helpers/HelpersString.cs:line 48
at System.Text.RegularExpressions.Regex.Replace(MatchEvaluator evaluator, Regex regex, String input, Int32 count, Int32 startat)
at System.Text.RegularExpressions.Regex.Replace(String input, MatchEvaluator evaluator, Int32 count, Int32 startat)
at System.Text.RegularExpressions.Regex.Replace(String input, MatchEvaluator evaluator)
at System.Text.RegularExpressions.Regex.Replace(String input, String pattern, MatchEvaluator evaluator)
at InputDataModel.Autodictor.DataProviders.ByRuleDataProviders.Rules.ViewRule.MakeBodySectionIndependentInserts(String body, AdInputType uit, Int32 currentRow) in /src/InputDataModel.Autodictor/DataProviders/ByRuleDataProviders/Rules/ViewRule.cs:line 268
at InputDataModel.Autodictor.DataProviders.ByRuleDataProviders.Rules.ViewRule.CreateStringRequest(IEnumerable`1 batch, Int32 startItemIndex) in /src/InputDataModel.Autodictor/DataProviders/ByRuleDataProviders/Rules/ViewRule.cs:line 209
at InputDataModel.Autodictor.DataProviders.ByRuleDataProviders.Rules.ViewRule.GetDataRequestString(List`1 items)+MoveNext() in /src/InputDataModel.Autodictor/DataProviders/ByRuleDataProviders/Rules/ViewRule.cs:line 122
at InputDataModel.Autodictor.DataProviders.ByRuleDataProviders.ByRulesDataProvider.ViewRuleSendData(Rule rule, List`1 takesItems) in /src/InputDataModel.Autodictor/DataProviders/ByRuleDataProviders/ByRulesDataProvider.cs:line 248
at InputDataModel.Autodictor.DataProviders.ByRuleDataProviders.ByRulesDataProvider.StartExchangePipeline(InDataWrapper`1 inData) in /src/InputDataModel.Autodictor/DataProviders/ByRuleDataProviders/ByRulesDataProvider.cs:line 193
at Exchange.Base.ExchangeUniversal`1.SendingPieceOfData(InDataWrapper`1 inData, CancellationToken ct) in /src/Exchange.Base/ExchangeUniversal.cs:line 453
Довольно большой стек вызовов, т.к. работа сервиса была оттестирована и перехват исключения произошел на самом "верху".
Как только возникло исключение, оно стало появляться постоянно, как будто "поломался" какой-то совместно используемый разными потоками ресурс.
Пакет NCalc вычисляет строковое представления математического выражения https://github.com/sklose/NCalc2
Я его использую для вычисления переменной номера строки и вставки его по формату. Например заданна формула для вычисления rowNumber "rowNumber+10-25" и результат преобразовать обратно в строку по формату "X2"
Много потоков, одновременно используют эти вычисления, через вызов HelperString.StringTemplateInsert(...); В методе CalculateMathematicFormat(...) Expression - это и есть экземпляр класса в библиотеке NCalc.
public static class HelperString
{
/// Вставка переменных (по формату) в строку по шаблону.
/// </summary>
/// <param name="template">базовая строка с местозаполнителем</param>
/// <param name="dict">словарь переменных key= название переменной val= значение</param>
/// <param name="pattern">Как выдедить переменную и ее формат, по умолчанию {val:format}</param>
/// <returns></returns>
public static string StringTemplateInsert(string template, Dictionary<string, object> dict, string pattern = @"\{(.*?)(:.+?)?\}")
{
string Evaluator(Match match)
{
string res;
var key = match.Groups[1].Value;
if (key.Contains("rowNumber"))
{
var replacement = dict["rowNumber"];
var calcVal = CalculateMathematicFormat(key, (int)replacement);
var formatValue = match.Groups[2].Value;
var format = "{0" + formatValue + "}";
res = string.Format(format, calcVal);
}
else
{
res = match.Value;
}
return res;
}
var result = Regex.Replace(template, pattern, Evaluator);
return result;
}
private static int CalculateMathematicFormat(string str, int row)
{
var expr = new Expression(str)
{
Parameters = { ["rowNumber"] = row }
};
var func = expr.ToLambda<int>();
var arithmeticResult = func();
return arithmeticResult;
}
}
Трассировка лога указывает что произошло внутренне исключение из-за одновременного доступа к Dictionary.
Внутри NCalc есть static метод Compile, который внутри себя, возможно, использует Dictionary Из разных потоков он будет вызываться и возможно работать с Dictionary что и приводит к исключению.
public static LogicalExpression Compile(string expression, bool nocache);
// Type: NCalc.Expression
// Assembly: NCalc, Version=3.5.0.2, Culture=neutral, PublicKeyToken=null
// MVID: 20EB71AB-FAFA-4DD3-BB8B-E5838F6706A1
// Assembly location: C:\Users\Admin\.nuget\packages\coreclr-ncalc\2.2.51\lib\netstandard2.0\NCalc.dll
using NCalc.Domain;
using System;
using System.Collections;
using System.Collections.Generic;
namespace NCalc
{
public class Expression
{
protected string OriginalExpression;
protected Dictionary<string, IEnumerator> ParameterEnumerators;
protected Dictionary<string, object> ParametersBackup;
public EvaluateOptions Options { get; set; }
public Expression(string expression);
public Expression(string expression, EvaluateOptions options);
public Expression(LogicalExpression expression);
public Expression(LogicalExpression expression, EvaluateOptions options);
public static bool CacheEnabled { get; set; }
public static LogicalExpression Compile(string expression, bool nocache);
public bool HasErrors();
public string Error { get; private set; }
public Exception ErrorException { get; private set; }
public LogicalExpression ParsedExpression { get; private set; }
public System.Func<TResult> ToLambda<TResult>();
public Func<TContext, TResult> ToLambda<TContext, TResult>() where TContext : class;
public object Evaluate();
public event EvaluateFunctionHandler EvaluateFunction;
public event EvaluateParameterHandler EvaluateParameter;
public Dictionary<string, object> Parameters { get; set; }
}
}
Я попытался воспроизвести ошибку у себя, создав простое консольное приложение, но ошибку не воспроизвел, все отработало корректно. Так ли я понял причину Exception?
class Program
{
static async Task Main(string[] args)
{
List<int> results = new List<int>();
for (int i = 0; i < 10000; i++)
{
var t1 = Task<int>.Factory.StartNew(() =>
{
var res = CalculateMathematicFormat("rowNumber + 10", 15);
return res;
});
var t2 = Task<int>.Factory.StartNew(() =>
{
var res = CalculateMathematicFormat("rowNumber + 10 * rowNumber % 2", 1285);
return res;
});
var t3 = Task<int>.Factory.StartNew(() =>
{
var res = CalculateMathematicFormat("rowNumber + 10 * rowNumber % 2", 1285);
return res;
});
var t4 = Task<int>.Factory.StartNew(() =>
{
var res = CalculateMathematicFormat("rowNumber + 10 * rowNumber % 2", 1263285);
return res;
});
var t5 = Task<int>.Factory.StartNew(() =>
{
var res = CalculateMathematicFormat("rowNumber + 10 * rowNumber % 2", 453);
return res;
});
var t6 = Task<int>.Factory.StartNew(() =>
{
var res = CalculateMathematicFormat("rowNumber + 10 * rowNumber % 2", 896);
return res;
});
try
{
var array = await Task.WhenAll(t1, t2, t3, t4, t5, t6);
results.AddRange(array);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
private static int CalculateMathematicFormat(string str, int row)
{
var expr = new Expression(str)
{
Parameters = { ["rowNumber"] = row }
};
var func = expr.ToLambda<int>();
var arithmeticResult = func();
return arithmeticResult;
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Всем привет, выполняю свои ui-тесты (C# + Selenium + NUnit)Необходимо получить значение - Время выполнения теста
Есть код, который скачивает список изображений по полученным ссылкам и конвертирует их для уменьшения размера: