Машинно-зависимые ошибки _write с кодом ошибки EINVAL - PullRequest
3 голосов
/ 25 февраля 2009

Это довольно длинная предыстория перед фактическим вопросом, однако, оно имеет некоторые объяснения, как мы надеемся, отсеять немного красной сельди.

Наше приложение, разработанное в Microsoft Visual C ++ (2005), использует стороннюю библиотеку (исходный код которой мы, к счастью, имеем) для экспорта сжатого файла, используемого в другом стороннем приложении. Библиотека отвечает за создание экспортированного файла, управление данными и сжатием, а также за обработку всех ошибок. Недавно мы начали получать отзывы о том, что на некоторых машинах наше приложение зависало во время записи в файл. На основании первоначального исследования мы смогли определить следующее:

  • Сбои произошли на различных аппаратных установках и операционных системах (хотя наши клиенты ограничены XP / 2000)
  • Сбои всегда будут происходить с одним и тем же набором данных; однако они не будут встречаться на всех наборах данных
  • Для набора данных, вызвавших сбой, сбой не воспроизводится на всех машинах, даже с одинаковыми характеристиками, т. Е. Операционной системой, объемом ОЗУ и т. Д.
  • Ошибка будет проявляться только при запуске приложения в каталоге установки - не при сборке из Visual Studio, запуске в режиме отладки или даже в других каталогах, к которым у пользователя был доступ
  • Проблема возникает, создается ли файл на локальном или подключенном диске

После изучения проблемы мы обнаружили, что проблема заключается в следующем блоке кода (слегка измененном для удаления некоторых макросов):

 while (size>0) {
    do {
        nbytes = _write(file->fd, buf, size);
    } while (-1==nbytes && EINTR==errno);
    if (-1==nbytes) /* error */
        throw("file write failed")
    assert(nbytes>0);
    assert((size_t)nbytes<=size);
    size -= (size_t)nbytes;
    addr += (haddr_t)nbytes;
    buf = (const char*)buf + nbytes;
}

В частности, _write возвращает код ошибки 22 или EINVAL. Согласно MSDN , _write, возвращающий EINVAL, подразумевает, что буфер (в данном случае buf) является нулевым указателем. Однако некоторые простые проверки вокруг этой функции подтвердили, что это не имело место при любых вызовах, сделанных ей.

Однако мы вызываем этот метод с некоторыми очень большими наборами данных - до 250 МБ за один вызов, в зависимости от входных данных. Когда мы наложили искусственное ограничение на объем данных, передаваемых этим методом, мы, похоже, решили проблему. Это, однако, попахивает исправлением кода для проблемы, которая зависит от машины / разрешения зависят / зависят от фазы луны. Итак, теперь вопросы:

  1. Кто-нибудь знает об ограничении количества данных, которые _write может обработать за один вызов? Или - за исключением _write - какой-либо поддержки команд ввода / вывода из файла Visual C ++?
  2. Поскольку это происходит не на всех компьютерах - или даже на каждом вызове достаточного размера (один вызов с 250 МБ будет работать, другой вызов - нет), - знает кто-нибудь о пользователе, компьютере, настройках групповой политики или разрешения для папки, которые могут повлиять на это?

UPDATE: Несколько других моментов из постов на данный момент:

  • Мы обрабатываем случаи, когда большое выделение буфера не удается. По соображениям производительности в стороннем приложении, которое читает создаваемый файл, мы хотим записать все данные в один большой блок (хотя, учитывая эту ошибку, это может оказаться невозможным)
  • Мы проверили начальное значение размера в приведенной выше подпрограмме, и оно совпадает с размером выделенного буфера. Кроме того, когда код ошибки EINVAL повышен, размер равен 0, а buf не является нулевым указателем - что заставляет меня думать, что это не является причиной проблемы.

Другое обновление:

Ниже приведен пример сбоя с некоторыми удобными printfs в приведенном выше примере кода.

     while (size>0) {
    if (NULL == buf)
    {
        printf("Buffer is null\n");
    }
    do {
        nbytes = _write(file->fd, buf, size);
    } while (-1==nbytes && EINTR==errno);
    if (-1==nbytes) /* error */
    {
        if (NULL == buf)
        {
            printf("Buffer is null post write\n");
        }
        printf("Error number: %d\n", errno);
        printf("Buffer address: %d\n", &buf);
        printf("Size: %d\n", size);
        throw("file write failed")
    }
    assert(nbytes>0);
    assert((size_t)nbytes<=size);
    size -= (size_t)nbytes;
    addr += (haddr_t)nbytes;
    buf = (const char*)buf + nbytes;
}

При сбое будет напечатано:

Error number: 22
Buffer address: 1194824
Size: 89702400

Обратите внимание, что ни один байт не был успешно записан и что буфер имеет действительный адрес (и не было выполнено ни одной проверки указателя NULL, до или после _write)

ПОСЛЕДНИЕ ОБНОВЛЕНИЯ

К сожалению, мы были преодолены событиями и не смогли окончательно решить эту проблему. Мы смогли найти некоторые интересные (и, возможно, даже тревожные) факты. 1. Ошибки возникали только на машинах с более медленным временем записи на их жесткие диски. Два компьютера с одинаковыми характеристиками оборудования, но с разными конфигурациями RAID (RAID 0 и RAID 1) будут иметь разные результаты. RAID 0 будет обрабатывать данные правильно; RAID 1 выйдет из строя. Точно так же старые ПК с более медленными жесткими дисками также выйдут из строя; более новые ПК с более быстрыми жесткими дисками - но схожие процессоры / память - будут работать. 2. Размер записи имел значение. Когда мы ограничили объем передаваемых данных _write до 64 МБ, все, кроме одного файла, успешно. Когда мы ограничили его 32 МБ, все файлы были успешными. Мы испытали снижение производительности в используемой нами библиотеке - которая была ограничением этой библиотеки и не зависела от _write или проблемы, с которой мы столкнулись, - но это было наше единственное «программное» исправление.

К сожалению, я так и не получил хорошего ответа (и мы собирались позвонить в Microsoft по этому поводу, но мы должны были заставить бизнес подписаться за счет звонка в службу технической поддержки) о том, почему EINVAL возвращался в первое место. Это не - из того, что мы смогли найти - документировано где-либо в API библиотеки C.

Если кто-то найдет хороший ответ для этого, пожалуйста, опубликуйте его здесь, и я отмечу его как ответ. Я бы хотел получить заключение для этой саги, даже если оно больше не относится ко мне напрямую.

Ответы [ 5 ]

4 голосов
/ 29 мая 2009

У нас была очень похожая проблема, которую нам удалось воспроизвести довольно легко. Сначала мы скомпилировали следующую программу:

#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{ int len = 70000000;
  int handle= creat(argv[1], S_IWRITE | S_IREAD);
  setmode (handle, _O_BINARY);
  void *buf = malloc(len);
  int byteswritten = write(handle, buf, len);
  if (byteswritten == len)
    printf("Write successful.\n");
  else
    printf("Write failed.\n");
  close(handle);
  return 0;
}

Теперь предположим, что вы работаете на компьютере mycomputer, и C: \ inbox отображается в общую папку \\ mycomputer \ inbox. Тогда наблюдаем следующий эффект:

C:\>a.exe C:\inbox\x
Write successful.

C:\>a.exe \\mycomputer\inbox\x
Write failed.

Обратите внимание, что при изменении len на 60000000 проблем не возникает ...

Основываясь на этой веб-странице support.microsoft.com / kb / 899149 , мы считаем, что это «ограничение операционной системы» (такой же эффект наблюдался в fwrite). Наша работа заключается в том, чтобы попытаться сократить запись на 63 МБ, если это не удастся. Эта проблема, по-видимому, была исправлена ​​в Windows Vista.

Надеюсь, это поможет! Simon

1 голос
/ 27 мая 2011

Согласно http://msdn.microsoft.com/en-us/library/1570wh78(v=VS.90).aspx errno может принимать значения:

- EBADF
- ENOSPC
- EINVAL.

На окнах нет EINTR. Случайные системные прерывания вызывают эту ошибку и не обнаруживаются тестом while (-1==nbytes && EINTR==errno);

1 голос
/ 25 февраля 2009

Вы смотрели на реализацию _write() в источнике CRT (C runtime), который был установлен вместе с Visual Studio (C:\Program Files\Microsoft Visual Studio 8\VC\crt\src\write.c)?

Существует как минимум два условия, которые заставляют _write() установить errno в EINVAL:

  1. buffer равен NULL, как вы упомянули.
  2. count параметр нечетный, когда файл открывается в текстовом режиме в формате UTF-16 (или UTF-8 - комментарии не соответствуют коду). Это текстовый или бинарный файл? Если это текст, есть ли у него метка порядка байтов?
  3. Возможно, другая функция, которую вызывает _write(), также устанавливает errno в EINVAL?

Если вы можете надежно воспроизвести эту проблему, вы сможете сузить источник ошибки, установив точки останова в частях источника CRT, которые устанавливают код ошибки. Также представляется, что отладочная версия CRT способна утверждать, когда возникает ошибка, но для этого может потребоваться настройка некоторых параметров (я не пробовал).

0 голосов
/ 25 февраля 2009

Вы можете уничтожить свой собственный стек, случайно не указав указатель в другом месте - если вы можете найти репро-машину, попробуйте запустить ваше приложение под Application Verifier со всеми включенными проверками памяти

0 голосов
/ 25 февраля 2009

На ум приходят две мысли. Либо вы проходите мимо конца буфера и пытаетесь записать эти данные, либо выделение буфера не удалось. Проблемы, которые в режиме отладки не будут так заметны, как в режиме выпуска.

Вероятно, в любом случае выделять 250 мегабайт памяти плохая идея. Лучше выделить буфер фиксированного размера и писать в блоках.

Вы искали такие вещи, как вирусные сканеры, которые могли бы удерживать файл между операциями записи, что приводило к ошибке записи?

Я не знаю ограничений на количество данных, которые вы можете передать для записи за один вызов, если (как я уже сказал) вы не записываете данные (как часть буфера), которые вам не принадлежат ...

Поскольку большинство этих функций обертывают вызов Kernel WriteFile (), (или NtWriteFile ()), МОЖЕТ быть условие, что недостаточно памяти ядра для обработки буфера для записи. Но в ЭТОМ я не уверен, так как не знаю, КОГДА именно код делает переход с UM на KM.

Не знаю, поможет ли что-нибудь из этого, но надеюсь, что это поможет ...

Если вы можете предоставить более подробную информацию, пожалуйста, сделайте. Иногда просто рассказывая кому-нибудь о проблеме, вы заставляете ваш мозг говорить «Подожди минутку!», И вы поймете это. хех ..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...