У меня есть служба. NET Core 2.1, которая работает на виртуальной машине Ubuntu 18.04 и вызывает Tesseract OCR 4.00 через экземпляр Process. Я хотел бы использовать API-оболочку, но я мог найти только одну доступную, и она только в бета-версии для последней версии Tesseract - стабильная оболочка использует версию 3 вместо 4. В прошлом этот сервис работал достаточно хорошо, но я изменил его так, чтобы данные документа / изображения записывались и читались с диска реже в попытке повысить скорость. Служба вызывала гораздо больше внешних процессов (таких как ImageMagick), которые были излишними из-за наличия API, поэтому я заменял их на вызовы API.
Недавно я тестировал это с помощью Пример файла взят из реальных данных. Это факсимильный документ PDF, который имеет 133 страницы, но только 5,8 МБ, несмотря на это из-за оттенков серого и разрешения. Служба берет документ, разбивает его на отдельные страницы, затем назначает несколько потоков (по одному потоку на страницу) для вызова Tesseract и обрабатывает их, используя Parallel.For
. Ограничения потока настраиваются. Мне известно, что у Tesseract есть собственная переменная среды многопоточности (OMP_THREAD_LIMIT). В предыдущем тестировании я обнаружил, что установка его на «1» идеально подходит для нашей установки в данный момент, но в моем недавнем тестировании по этой проблеме я попытался оставить его неустановленным (значение Dynami c) без каких-либо улучшений.
Проблема заключается в том, что непредсказуемо, когда вызывается Tesseract, служба будет зависать около минуты, а затем cra sh, при этом единственная ошибка, отображаемая в journalctl:
dotnet[32328]: Error while reaping child. errno = 10
dotnet[32328]: at System.Environment.FailFast(System.String, System.Exception)
dotnet[32328]: at System.Environment.FailFast(System.String)
dotnet[32328]: at System.Diagnostics.ProcessWaitState.TryReapChild()
dotnet[32328]: at System.Diagnostics.ProcessWaitState.CheckChildren(Boolean)
dotnet[32328]: at System.Diagnostics.Process.OnSigChild(Boolean)
Я не могу ничего найти в сети для этой конкретной ошибки. Мне кажется, основываясь на связанном исследовании, которое я провел с классом Process
, это происходит, когда процесс завершается, а do tnet пытается очистить ресурсы, которые он использовал. Я действительно в растерянности относительно того, как даже подойти к этой проблеме, хотя я пробовал ряд «догадок», таких как изменение предельных значений потока. Там нет пересечения между потоками. Каждый поток имеет свой собственный раздел страниц (в зависимости от того, как Parallel.For
разделяет коллекцию), и он начинает работать с этими страницами, по одному за раз.
Вот вызов процесса, вызываемый из нескольких потоков (8 - это предел, который мы обычно устанавливаем):
private bool ProcessOcrPage(IMagickImage page, int pageNumber, object instanceId)
{
var inputPageImagePath = Path.Combine(_fileOps.GetThreadWorkingDirectory(instanceId), $"ocrIn_{pageNumber}.{page.Format.ToString().ToLower()}");
string outputPageFilePathWithoutExt = Path.Combine(_fileOps.GetThreadOutputDirectory(instanceId),
$"pg_{pageNumber.ToString().PadLeft(3, '0')}");
page.Write(inputPageImagePath);
var cmdArgs = $"-l eng \"{inputPageImagePath}\" \"{outputPageFilePathWithoutExt}\" pdf";
bool success;
_logger.LogStatement($"[Thread {instanceId}] Executing the following command:{Environment.NewLine}tesseract {cmdArgs}", LogLevel.Debug);
var psi = new ProcessStartInfo("tesseract", cmdArgs)
{
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
// 0 is not the default value for this environment variable. It should remain unset if there
// is no config value, as it is determined dynamically by default within OpenMP.
if (_processorConfig.TesseractThreadLimit > 0)
psi.EnvironmentVariables.Add("OMP_THREAD_LIMIT", _processorConfig.TesseractThreadLimit.ToString());
using (var p = new Process() { StartInfo = psi })
{
string standardErr, standardOut;
int exitCode;
p.Start();
standardOut = p.StandardOutput.ReadToEnd();
standardErr = p.StandardError.ReadToEnd();
p.WaitForExit();
exitCode = p.ExitCode;
if (!string.IsNullOrEmpty(standardOut))
_logger.LogStatement($"Tesseract stdOut:\n{standardOut}", LogLevel.Debug, nameof(ProcessOcrPage));
if (!string.IsNullOrEmpty(standardErr))
_logger.LogStatement($"Tesseract stdErr:\n{standardErr}", LogLevel.Debug, nameof(ProcessOcrPage));
success = p.ExitCode == 0;
}
return success;
}
РЕДАКТИРОВАТЬ 4: После большого тестирования и обсуждения с Клинтом в чате, вот что мы узнали. Ошибка возникает из события Process «OnSigChild», что очевидно из трассировки стека, но нет способа подключиться к тому же событию, которое вызывает эту ошибку. Процесс никогда не прерывается при заданном тайм-ауте в 10 секунд (Tesseract обычно занимает всего несколько секунд для обработки данной страницы). Любопытно, что если тайм-аут процесса удален, и я жду закрытия стандартных потоков вывода и ошибок, он будет зависать в течение хороших 20-30 секунд, но процесс не появляется в ps auxf
в течение этого времени зависания. Насколько я могу судить, Linux может определить, что процесс завершен, а NET - нет. В противном случае ошибка, по-видимому, возникает в тот самый момент, когда процесс завершается выполнением.
Самым удивительным для меня остается то, что часть кода, обрабатывающая процессы, действительно не сильно изменилась по сравнению с рабочей версией этого кода, которую мы имеем в производстве. Это говорит о том, что я где-то допустил ошибку, но просто не могу ее найти. Думаю, мне придется открыть проблему на трекере do tnet GitHub.