c# - .NET Core 2.0 正则表达式超时死锁
问题描述
我有一个 .NET Core 2.0 应用程序,我在其中迭代了许多不同大小的文件(600,000 个)(总共 220GB)。
我列举他们使用
new DirectoryInfo(TargetPath)
.EnumerateFiles("*.*", SearchOption.AllDirectories)
.GetEnumerator()
并使用
Parallel.ForEach(contentList.GetConsumingEnumerable(),
new ParallelOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount * 2
},
file => ...
在其中,我有一个正则表达式列表,然后我使用它扫描文件
Parallel.ForEach(_Rules,
new ParallelOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount * 2
},
rule => ...
最后,我使用Regex 类的实例获取匹配项
RegEx = new Regex(
Pattern.ToLowerInvariant(),
RegexOptions.Multiline | RegexOptions.Compiled,
TimeSpan.FromSeconds(_MaxSearchTime))
这个实例在所有文件之间共享,所以我编译一次。有 175 种模式应用于文件。
在随机(ish)点,应用程序死锁并且完全没有响应。再多的尝试/捕捉也无法阻止这种情况的发生。如果我采用完全相同的代码并为 .NET Framework 4.6 编译它,它可以毫无问题地工作。
我已经尝试了很多东西,我目前的测试似乎有效(但我非常小心!)是不使用实例,而是Regex.Matches
每次都调用 STATIC 方法。我不知道我对性能的影响有多大,但至少我没有陷入僵局。
我可以使用一些见解,或者至少可以作为一个警示故事。
更新: 我得到这样的文件列表:
private void GetFiles(string TargetPath, BlockingCollection<FileInfo> ContentCollector)
{
List<FileInfo> results = new List<FileInfo>();
IEnumerator<FileInfo> fileEnum = null;
FileInfo file = null;
fileEnum = new DirectoryInfo(TargetPath).EnumerateFiles("*.*", SearchOption.AllDirectories).GetEnumerator();
while (fileEnum.MoveNext())
{
try
{
file = fileEnum.Current;
//Skip long file names to mimic .Net Framework deficiencies
if (file.FullName.Length > 256) continue;
ContentCollector.Add(file);
}
catch { }
}
ContentCollector.CompleteAdding();
}
在我的 Rule 类中,以下是相关方法:
_RegEx = new Regex(Pattern.ToLowerInvariant(), RegexOptions.Multiline | RegexOptions.Compiled, TimeSpan.FromSeconds(_MaxSearchTime));
...
public MatchCollection Matches(string Input) { try { return _RegEx.Matches(Input); } catch { return null; } }
public MatchCollection Matches2(string Input) { try { return Regex.Matches(Input, Pattern.ToLowerInvariant(), RegexOptions.Multiline, TimeSpan.FromSeconds(_MaxSearchTime)); } catch { return null; } }
然后,这是匹配的代码:
public List<SearchResult> GetMatches(string TargetPath)
{
//Set up the concurrent containers
ConcurrentBag<SearchResult> results = new ConcurrentBag<SearchResult>();
BlockingCollection<FileInfo> contentList = new BlockingCollection<FileInfo>();
//Start getting the file list
Task collector = Task.Run(() => { GetFiles(TargetPath, contentList); });
int cnt = 0;
//Start processing the files.
Task matcher = Task.Run(() =>
{
//Process each file making it as parallel as possible
Parallel.ForEach(contentList.GetConsumingEnumerable(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 }, file =>
{
//Read in the whole file and make it lowercase
//This makes it so the Regex engine does not have
//to do it for each 175 patterns!
StreamReader stream = new StreamReader(File.OpenRead(file.FullName));
string inputString = stream.ReadToEnd();
stream.Close();
string inputStringLC = inputString.ToLowerInvariant();
//Run through all the patterns as parallel as possible
Parallel.ForEach(_Rules, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 }, rule =>
{
MatchCollection matches = null;
int matchCount = 0;
Stopwatch ruleTimer = Stopwatch.StartNew();
//Run the match for the rule and then get our count (does the actual match iteration)
try
{
//This does not work - Causes Deadlocks:
//matches = rule.Matches(inputStringLC);
//This works - No Deadlocks;
matches = rule.Matches2(inputStringLC);
//Process the regex by calling .Count()
if (matches == null) matchCount = 0;
else matchCount = matches.Count;
}
//Catch timeouts
catch (Exception ex)
{
//Log the error
string timeoutMessage = String.Format("****** Regex Timeout: {0} ===> {1} ===> {2}", ruleTimer.Elapsed, rule.Pattern, file.FullName);
Console.WriteLine(timeoutMessage);
matchCount = 0;
}
ruleTimer.Stop();
if (matchCount > 0)
{
cnt++;
//Iterate the matches and generate our match records
foreach (Match match in matches)
{
//Fill my result object
//...
//Add the result to the collection
results.Add(result);
}
}
});
});
});
//Wait until all are done.
Task.WaitAll(collector, matcher);
Console.WriteLine("Found {0:n0} files with {1:n0} matches", cnt, results.Count);
return results.ToList();
}
更新2
我正在运行的测试没有死锁,但是当它接近尾声时,它似乎停止了,但我仍然可以用VS闯入该进程。然后我意识到我没有在我的测试中设置超时,而我在我发布的代码中设置了(rule.Matches
和rule.Matches2
)。 随着超时,它会死锁。 没有超时,它不会。两者仍然在 .Net Framework 4.6 中工作。我需要正则表达式的超时,因为有一些大文件会导致某些模式停止运行。
更新 3: 我一直在使用超时值,它似乎是线程运行、超时异常以及导致正则表达式引擎死锁的超时值的某种组合。我无法准确确定它,但超时 >= 5 分钟似乎有帮助。作为临时修复,我可以将值设置为 10 分钟,但这不是永久修复!
解决方案
推荐阅读
- laravel - 调用未定义的方法 Illuminate\Database\Query\Builder::tags()
- c# - 重定向到部署在 MVC 应用程序中的 IIS 中的虚拟 Web 窗体应用程序中的页面时出现问题?
- javascript - 数组 indexOf 检查对象是否相等
- python - 写锁定的文件有时找不到内容(打开腌制的熊猫数据帧时)-EOFError: Ran out of input
- django - 将子文件夹请求重定向到乘客
- java - Selenium - 从 angularjs 组件中选择一个输入
- c++11 - 字符串匹配某些情况,而不是其他使用 rabin karp 算法?
- php - Codeigniter - 使用 Select2 更新多行
- java - 基本 Firebase / Java 读/写
- javascript - 尝试在javascript中访问表单输入