Skip to content

Гонка в ПрочитатьСтроку() потока вывода процесса: вставляется лишняя пустая строка #1726

Description

@nixel2007

Опишите ошибку

ПотокВывода.ПрочитатьСтроку() у процесса иногда возвращает лишнюю пустую строку в середине вывода. Это гонка между потоком, наполняющим буфер, и читателем.

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);
        }
    }
}

Буфер растёт так: L1L1␤L2L1␤L2␤L3. То есть последняя строка в буфере всегда лежит без терминатора.

Теперь 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/ там не затрагивается.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions