Опишите ошибку
ПотокВывода.ПрочитатьСтроку() у процесса иногда возвращает лишнюю пустую строку в середине вывода. Это гонка между потоком, наполняющим буфер, и читателем.
ProcessOutputWrapper.StreamDataReceived получает от .NET уже разбитые строки (e.Data приходит без терминатора) и склеивает их, добавляя разделитель перед каждой следующей:
private void StreamDataReceived(object sender, sys.DataReceivedEventArgs e)
{
if (e.Data != null)
{
lock(_buffer)
{
if (_buffer.Length != 0)
_buffer.Append(System.Environment.NewLine);
_buffer.Append(e.Data);
}
}
}
Буфер растёт так: L1 → L1L2 → L1L2L3. То есть последняя строка в буфере всегда лежит без терминатора.
Теперь ProcessOutputWrapper.ReadLine(). Если читатель вычерпал буфер ровно на конце L2:
int ch = ReadInternal();
if (ch == -1) break; // буфер кончился, терминатора после L2 ещё нет
...
if (sb.Length > 0)
return sb.ToString(); // вернули "L2" - пока верно
Дальше продюсер дописывает L3, и _bufferIndex оказывается на разделителе. Следующий вызов ReadLine() первым же символом видит \r, подглядывает \n, съедает оба и возвращает пустую строку.
Воспроизведение ошибки
Плавающее, зависит от того, как лягут порции вывода. Проявляется на существующем тесте tests/process.os → ТестДолжен_ПрочитатьВыводOscriptПострочно, который читает штатный вывод oscript построчно:
Различия в позиции 195.
Ожидали: `ents...]\n\nModes:\n -measure `
Получено:`ents...]\n\nModes:\n\n -measure `
Позиция значения не имеет — это просто место, где в том прогоне совпало вычерпывание буфера.
Ожидаемое поведение
Построчное чтение возвращает ровно те строки, которые вывел процесс, без вставки пустых.
Соседний тест ТестДолжен_ПрочитатьВыводOscriptСразу, читающий тот же вывод целиком через Прочитать(), стабильно зелёный — проблема именно в построчной нарезке.
Окружение
- ОС: воспроизводилось на Windows (Jenkins-агент). Гонка платформонезависимая, но на Windows
Environment.NewLine это \r\n, вывод приходит бо́льшим числом порций, и шанс попасть в стык выше. На Linux те же тесты стабильно зелёные.
- Версия:
develop
Дополнительная информация
Возможное решение — дописывать разделитель после строки, а не перед следующей:
_buffer.Append(e.Data);
_buffer.Append(System.Environment.NewLine);
Тогда каждая полная строка в буфере всегда терминирована: читатель не вернёт незавершённый хвост как готовую строку и не наткнётся на осиротевший разделитель.
Побочный эффект — Прочитать() начнёт отдавать текст с завершающим переводом строки, а ТестДолжен_ПрочитатьВыводOscriptСразу сравнивает результат с эталоном без него. То есть правка тянет за собой либо СокрП в тесте, либо подрезание хвоста в ReadToEnd. Что из этого правильнее — вопрос к тому, каким контракт Прочитать() задумывался.
Найдено при разборе падения в PR #1725; к изменениям того PR отношения не имеет — каталог src/OneScript.StandardLibrary/Processes/ там не затрагивается.
Опишите ошибку
ПотокВывода.ПрочитатьСтроку()у процесса иногда возвращает лишнюю пустую строку в середине вывода. Это гонка между потоком, наполняющим буфер, и читателем.ProcessOutputWrapper.StreamDataReceivedполучает от .NET уже разбитые строки (e.Dataприходит без терминатора) и склеивает их, добавляя разделитель перед каждой следующей:Буфер растёт так:
L1→L1L2→L1L2L3. То есть последняя строка в буфере всегда лежит без терминатора.Теперь
ProcessOutputWrapper.ReadLine(). Если читатель вычерпал буфер ровно на концеL2:Дальше продюсер дописывает
L3, и_bufferIndexоказывается на разделителе. Следующий вызовReadLine()первым же символом видит\r, подглядывает\n, съедает оба и возвращает пустую строку.Воспроизведение ошибки
Плавающее, зависит от того, как лягут порции вывода. Проявляется на существующем тесте
tests/process.os→ТестДолжен_ПрочитатьВыводOscriptПострочно, который читает штатный выводoscriptпострочно:Позиция значения не имеет — это просто место, где в том прогоне совпало вычерпывание буфера.
Ожидаемое поведение
Построчное чтение возвращает ровно те строки, которые вывел процесс, без вставки пустых.
Соседний тест
ТестДолжен_ПрочитатьВыводOscriptСразу, читающий тот же вывод целиком черезПрочитать(), стабильно зелёный — проблема именно в построчной нарезке.Окружение
Environment.NewLineэто\r\n, вывод приходит бо́льшим числом порций, и шанс попасть в стык выше. На Linux те же тесты стабильно зелёные.developДополнительная информация
Возможное решение — дописывать разделитель после строки, а не перед следующей:
Тогда каждая полная строка в буфере всегда терминирована: читатель не вернёт незавершённый хвост как готовую строку и не наткнётся на осиротевший разделитель.
Побочный эффект —
Прочитать()начнёт отдавать текст с завершающим переводом строки, аТестДолжен_ПрочитатьВыводOscriptСразусравнивает результат с эталоном без него. То есть правка тянет за собой либоСокрПв тесте, либо подрезание хвоста вReadToEnd. Что из этого правильнее — вопрос к тому, каким контрактПрочитать()задумывался.Найдено при разборе падения в PR #1725; к изменениям того PR отношения не имеет — каталог
src/OneScript.StandardLibrary/Processes/там не затрагивается.