Наш продукт содержит дескриптор файла, доступный для записи, в каждом открытом файле документа, чтобы гарантировать, что у нас есть эксклюзивный контроль над записью файла.
Следовательно, Windows не позволит другим процессам делать больше, чем читатьиз файла, и при этом не может быть удален файл из Проводника или другого процесса - из-за открытого (и исключающего запись) дескриптора на нем.
Однако у нас возникли проблемы с очень странными крайними случаями, когдасодержимое файла повреждено.Я думаю, что это связано с ошибками или, возможно, с моим неправильным пониманием того, что гарантировано от API-интерфейсов Windows - т.е. для того, чтобы сохранить файл дизайна поверх предыдущей версии - для которого мы в настоящее время храним дескриптор файла - я должен перемотать дескрипторчтобы начать файл, запишите его, а затем заставьте его сбрасывать и усекать в этой новой позиции (в случае, если файл сократился - нам не нужен лишний ил в конце нашего файла - это тоже будет формой повреждения).Делайте это несколько раз в течение сеанса - каждый раз, когда пользователь редактирует, а затем сохраняет свои изменения ...
Однако иногда наши клиенты сообщают о поврежденных файлах в результате всего этого (только по сети - никогда локально).
Мы думаем, что это может быть связано с тем, что наш фактический процесс сохранения немного сложнее:
- перемотать (уже открытый) дескриптор файла
- writeиз основных данных
- сбросить и усечь дескриптор
- fseek до конца файла
- записать данные миниатюрного изображения (по существу - добавить миниатюру)
- сбросить и усечь дескриптор
Это может быть просто случай «не сбрасывать, искать, записывать, сбрасывать» - что это приводит к незначительным ошибкам в коде сетевой файловой системы MS (или полагаетсяна неопределенности, встроенные в систему - и на которые нельзя надёжно полагаться)?
Итак, я реализую двухслойное исправление:
- выполняю одну перемотку, затем пишуосновные данные + изображение data + flush & truncate (один раз)
- , выполняющий сохранение как темп, закрытие, переименование
Нет.2 имеет несколько приятных особенностей - например, «если при записи нового файла возникла проблема, старый остается нетронутым».Это означает, что в худшем случае их новые данные не сохраняются, но старые данные не теряются.
Это базовое использование классического паттерна «создать новую копию, а затем заменить ее на реальную / активную структуру данных».«
Отлично - но я не знаю, как« поменять содержимое файлов »?
Я могу сделать классическое:
- Write T (temp) полностью и закройте его.
- Переименуйте файл A (фактический) в A.bak.
- Переименуйте T в A
(и, конечно, сначала мне нужно будет удалить любой предыдущий A.bak).
Это нормально - но снова -У нас обычно есть заблокированный дескриптор на A. Так что это расширяется до несколько несовершенного:
- Запись T
- Закройте наш дескриптор на A
- Переименуйте A в A.bak
- Переименуйте T в A
- Получите эксклюзивную ручку для записи на A
Что мне не нравится в этом, так это "слишком много движущихся частей".
- от 2 до 5, любой другой может захватить блокировку А или иным образом помешать нам.
Вы не думаете, что это произойдет - но тогда подайтеСистемное индексирование или антивирусное или резервное программное обеспечение могут мешать и очень-очень-очень часто (по нашему опыту).
Так что - в идеале, я не хочу отказываться от контроля над A влюбая точка!Я хочу гарантировать, что каждая передача не будет защищена от попадания Антивируса или другого программного обеспечения и сбивания с толку.
В идеале, на самом деле, я бы:
- Написать T
- Поменяйте местами буквы T и A (попросите файловую систему фактически связать имя A с содержимым T)
- Живите счастливо всегда больше ...
Итак, есть ли другие схемы, которые другие обнаружили для замены T и A?
Есть ли набор вызовов API, чтобы сделать это лучше / надежнее?
Есть другие мысли, которые могли бы помочь переосмыслить мой подход?
ПРИМЕЧАНИЕ. MS устарела в API транзакционной файловой системы.Так что это звучит как стартер - не говоря уже о том, что он доступен не на всех файловых системах под Windows.
Обновление: FWIW, я реализовал это какзаписать временный файл, переименовать оригинал, переименовать темп в реальный, удалить оригинал (плюс необходимую разблокировку и получить новую блокировку), используя RAII и ScopeGuard для обработки любых откатов при сбое, хотя, конечно, откатов - это побочный эффект и ОСзависимы, являются «наилучшими сценариями» и не так хорошо гарантированы, как сами контракты языка C ++.Тем не менее, во время тестирования это было довольно эффективно - никогда не давал мне плохой файл (и я преднамеренно и непреднамеренно создал ряд проблем, которые создали плохой временный файл или иным образом допустили ошибку (сгенерировали исключение) во время этого алгоритма, вызывая процесс развертывания).
Обновление 2: «Окончательный» алгоритм -
1. (сохранить во временную локальную проверочную копию)
2. сохранить во временном новомфайл
3. (проверить новое сохранение и проверить соответствие)
4. снять блокировку с реального файла
5. переименовать реальный файл во временный старый файл и заменить исходный файл временным файлом(это включает в себя передачу атрибутов, ACL и временных меток - см. ReplaceFile ())
6. получить нашу блокировку (если она была заблокирована)
7. Успех (сбросить нашу охрану)