Это довольно длинная предыстория перед фактическим вопросом, однако, оно имеет некоторые объяснения, как мы надеемся, отсеять немного красной сельди.
Наше приложение, разработанное в 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 МБ за один вызов, в зависимости от входных данных. Когда мы наложили искусственное ограничение на объем данных, передаваемых этим методом, мы, похоже, решили проблему. Это, однако, попахивает исправлением кода для проблемы, которая зависит от машины / разрешения зависят / зависят от фазы луны. Итак, теперь вопросы:
- Кто-нибудь знает об ограничении количества данных, которые _write может обработать за один вызов? Или - за исключением _write - какой-либо поддержки команд ввода / вывода из файла Visual C ++?
- Поскольку это происходит не на всех компьютерах - или даже на каждом вызове достаточного размера (один вызов с 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.
Если кто-то найдет хороший ответ для этого, пожалуйста, опубликуйте его здесь, и я отмечу его как ответ. Я бы хотел получить заключение для этой саги, даже если оно больше не относится ко мне напрямую.