Фокусы с cfg, разбор полетов
December 13th, 2009 Begemot Posted in Oбщее
Продолжая тему с потерей конфига. Кратко напомню суть проблемы – периодически при загрузки компа конфиг файлы программы – “плохие”. Либа выдает ошибку конвертации в юникод и данных нет. Как показали эксперименты, файл есть, примерно нужного размера но заполнен нулевыми символами (???). Причем конфига у меня два, первый открыт все время работы программы и закрывается при выходе\получении сообщения о выключении компа. Второй открывается локально при выходе\завершении и тут же закрывается. Проблема с обоими.
Я тут повозился полдня, подебажил сорцы, по перезагружался много раз – результаты такие.
Версия Сергея:
Теоретически, при любом сбое файл настроек должен ведь сохраниться. Я вижу пока только одну возможность – в момент flush когда он уже пометил старый файл пустым и не успел записать данные.
Неправильная, там внутрях используется wxTempFile, который обрабатывает это дело умно – пишет данные в временный файл, убивает оригинал, переименовывает временный в оригинал.
Eсли просто выключать комп все ок.
Если инициировать процесс выключения (в обработчике я пишу в конфиг и удаляю его), и сразу же нажать рессет. То в 60% случаев – проблема проявляется. Причем устойчивое появление проблемы мне удалось наблюдать только если программа стоит в автозагрузке, если же нет – то вроде все ок (это мне вообще мозг ломает…).
Сорцы смотрел, дебажил, эксперементировал. Почему такое может быть понять не могу – в мою картину мира не вписывается, то что трабла происходит только при ресете – ну что может там такого происходить??? Судя по сорцам – все просто и правильно. А то что мне не удается это поймать когда запускаю руками программу, а не из автозагрузки – вообще путает все карты.
Уже не знаю что и думать. Наверное буду писать систему дублирования 🙁
December 14th, 2009 at 11:19 am
> используется wxTempFile, который обрабатывает это дело умно
Использовал в программе аналогичный самописный механизм.
Сначала создается файл name.ext.tmp, потом переименовывается в name.ext с заменой старого. В результате и нулевые, и заполненные нулями файлы точно так же случаются. Ни разу не было “куска” (незаконченного) файла при том, что размер – сотни килобайт. MFC, запись файла std::ofstream.
December 14th, 2009 at 1:01 pm
> трабла происходит только при ресете
Вероятно все таки ОС не успевает сбрасывать файл на диск. Flush это программный механизм, который сообщает ОС что файл необходимо в ближайшее время записать на диск. А вот насколько это время будет “ближайшим” решает ядро ОС. Но даже это не дает полных гарантий записи файла. Следующий “этап” это контроллер жесткого диска. Сначала данные попадают к нему в кэш и в дело вступает планировщик запросов жесткого диска (со своими хитрыми алгоритмами).
Это я все к тому что успешный вызов Flush не означает что данные записаны на диск, а гарантированного способа записи данных не существует.
December 14th, 2009 at 2:17 pm
ну flush flushу рознь:) Конкретно здесь flush для настроек означает следующее
1. записать данные из памяти в временный файл
2. удалить оригинал
3. переименовать временный в оригинал
по симптомам он пишет временный заполняя его 0x00, и потом проходит оставшиеся 2 шага – удаление\переименование.
Может ли тут быть задержка ОС?
December 14th, 2009 at 3:00 pm
Только что глянул реализацию файлового ввода-вывода в wxWidgets – она реализована с помощью стандартной библиотеки C, не нативными функциями WinAPI. У нее еще один буфер есть. ))) А про Flush вообще написано:
Note that wxFile::Flush is not implemented on some Windows compilers due to a missing fsync function, which reduces the usefulness of this function (it can still be called but it will do nothing on unsupported compilers).
Может стоит попробовать сделать этот модуль программы платформеннозависимым и написать на чистом WinAPI?
December 14th, 2009 at 3:43 pm
Еще раз повторюсь, wxFileConfig::Flush != wxFile::Flush
December 14th, 2009 at 4:40 pm
Ну как бы там ни было, ошибка то возникает при записи в файл. А она реализована в wxFile. Так что я считаю копать надо в эту сторону.
Если хотите, я могу попробовать переписать wxFile с использованием функций WinAPI, пришлю Вам реализацию, а Вы пропатчите wxWidgets и посмотрим что получится. 🙂
December 14th, 2009 at 6:10 pm
Поздно, я уже написал систему бекапа файла настроек:) Хотя ради академического интереса можно наверное, но есть ли смысл?
Но там по алгоритму файл явно *закрывается*, потом переименовывается – разве это не гарантирует отсутствие проблем с отложенным кешем?
December 14th, 2009 at 6:52 pm
Смысл, честно говоря, очень сомнительный. Только лишь для нас убедиться в правильности/не правильности идеи. 🙂
Я еще внутрях откопал интересную штуку.
Временный файл в wxTempFile создается с помощью функции wxFileName::CreateTempFileName(), которая довольно запутанная.
В ней фигурирует функция с интересным названием: wxOpenWithDeleteOnClose (которая, вообще говоря, реализована только для Windows). Тык вот не возможен ли такой случай что программа, получив сигнал завершения, в какой-то момент удаляет этот файл, не закончив сохранять в него конфигурацию или переименовывать?
Из вариантов решения – попробовать отказаться от временных файлов.
December 14th, 2009 at 7:02 pm
Не думаю нет, я тестировал как ведет себя либа при получении сообщения о закрытии сессии, вроде бы ничего лишнего не делает, к тому же обработка все таки последовательная по идее пока я обрабатываю сообщение, либа ждет…
December 14th, 2009 at 7:14 pm
Дело в том что в функции wxOpenWithDeleteOnClose вызывается API-функция Windows CreateFile с флагом FILE_FLAG_DELETE_ON_CLOSE. Т.е. вся ответственность за удаление файла при выходе ложится на ОС и либа с этим никак не работает. А в какой именно момент Windows решит его удалить – неизвестно.
Хотя… Если порассуждать логически, то удаление возможно после закрытия файла и до его переименования (т.е. файл закрыт и событие DELETE_ON_CLOSE также выполняется).
Это хорошо состыкуется с логикой функции wxTempFile::Commit().
December 14th, 2009 at 8:39 pm
эээ как это состыкуется? если бы винда удаляет файл до переименования, что тогда переименовывает вх? тогда бы терялись все настройки…
December 14th, 2009 at 9:02 pm
> тогда бы терялись все настройки…
Ну в общем-то они и теряются. )))
Винда может начать удалять В МОМЕНТ переименовывания – среда же многозадачная, никаких критических секций в коде WX нету. А как побочный эффект такого вмешательства – заполнение нулями. По коду в wx не могу сказать чтобы он где-то сам нули в файл писал.
В общем это все догадки, они имеют место быть, но все их надо проверять.
December 15th, 2009 at 9:38 pm
Может производить контрольное чтение после записи файла)? Типа:
do
{
save(…);
} while( check(…) );
Или опять кэш(а их несколько…) будет мешать?
Ещё как вариант: аппаратная зависимость от фирмвари ЖД.
December 15th, 2009 at 11:15 pm
эээ в тот момент когда юзер нажал ресет производить или немного подождать сперва? 🙂
December 17th, 2009 at 1:45 am
Ну с ресетом понятно – нечего держать открытми файлы 🙂 . Я говорю про сохранение во время выключения. Вообще, судя про прочитанному выше про “умную” реализацию wxTempFile, я бы остерегался её использовать. Лучше самому написать нечто подобное. Один из простых вариантов: “крутить” несколько файлов. Тоесть сохранять в лоб под новым именем(лишний файл с “головы” удалять). Загрузку осуществлять по последнему валидному.
Мне вот интересно как БД работают 🙂 – ничего же не пропадает. Где-то слышал про такие матерные слова как транзакции, журналирование…
December 17th, 2009 at 1:50 am
Реализовывать журналирование для сохранения конфигурации – это слишком сурово я считаю. )))
December 17th, 2009 at 11:14 am
baralgin, возможно я выше неправильно что-то написал о работе wxTempFile, оригинал тут – http://docs.wxwidgets.org/stable/wx_wxtempfile.html
И я не могу понять что вам там кажется “умным”, как по мне так совершенно логичная схема, безопасной записи, в которой я не вижу изъянов.
p.s. и файлы открытыми никто не держит.
и да журналирование и стек из нескольких файлов для конфигурации это конечно реально жесть 🙂 хотя с другой стороны я сделал резервное копирование\востановление, в принципе это тот же стек из двух файлов 🙂
December 19th, 2009 at 1:13 am
Попробовал. В итоге получается три вызова: конструктор, Write и Commit. Между какими жать ресет для повторения? 🙂 … может ещё есть какие особенности для надёжного повторения?
Кстати, если не вызывать Commit или Discard временный файл живёт, он после перезагрузки то хоть удалиться или так останется мусором?
December 20th, 2009 at 11:41 am
>Между какими жать ресет для повторения?
Если я правильно понимаю в коммите между записью и переименованием:)
>может ещё есть какие особенности для надёжного повторения?
У меня более менее стабильно повторялось если прога в атозагрузке, нажать выключение и сразу же ресет.
>Кстати, если не вызывать Commit или Discard временный файл живёт, он после перезагрузки то хоть удалиться или так останется мусором?
Сорцы либы не правил, ничего не могу сказать.