6 May 2019

Снова про дырявые абстракции (или про непредсказуемое окружение)

Abnormal programmingC++Development for Windows
Итак, довольно простая часть программы под Windows. Есть файл, содержащий несколько записей. И их надо определенным образом отфильтровать.

Решение довольно простое — открываем файл, читаем записи одну за другой, нужные нам записываем во временный файл. Закрываем файл. Удаляем его. Переименовываем временный в оригинальный. Настолько все просто, что даже код приводить не буду. Неужели же это достаточный повод для статьи?

Пока все работает, повода об этом писать и правда нет. Но потом вдруг однажды «все падает», т.к. переименовывание не происходит из-за ошибки «Access denied». Случается это очень редко, но все же гораздо чаще, чтобы заподозрить космические лучи.

Начинаем раскопки. Первая найденная зацепка: в процессе копания насторожила вот эта вот цитата из документации Микрософта:
The DeleteFile function marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed. Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED.
По симптоматике ну очень уж похоже, но вот только откуда же берутся эти «другие хендлеры», если кроме нас, с этим файлом никто ничего не делает и не должен делать? И у нас нет никаких других потоков с нитями, которые бы что-то делали с этим файлом?

Виновник нашелся благодаря SysInternals и их ProcessMonitor. Запускаем его, устанавливаем фильтр на наш многострадальный файл и долго и настойчиво пытаемся воспроизвести. Воспроизводим. Смотрим. И что же мы там видим?
01. 2:25:28.3162097 PM our_prog.exe 1288 CreateFile our.file SUCCESS Desired Access: Generic Read/Write
02. 2:25:28.3164513 PM our_prog.exe 1288 WriteFile our.file SUCCESS Offset: 0, Length: 898, Priority: Normal

34. 2:25:28.3173405 PM our_prog.exe 1288 WriteFile our.file SUCCESS Offset: 35,290, Length: 1,113
35. 2:25:28.3173493 PM our_prog.exe 1288 WriteFile our.file SUCCESS Offset: 36,403, Length: 1,128
36. 2:25:28.3173736 PM our_prog.exe 1288 FlushBuffersFile our.file SUCCESS
37. 2:25:28.3174212 PM our_prog.exe 1288 WriteFile our.file SUCCESS Offset: 0, Length: 40,960,
38. 2:25:28.3175927 PM Explorer.EXE 1884 QueryBasicInformationFile our.file SUCCESS
39. 2:25:28.3176144 PM Explorer.EXE 1884 CloseFile our.file SUCCESS
40. 2:25:28.3263642 PM Explorer.EXE 1884 CreateFile our.file SUCCESS Desired Access: Read Attributes,
41. 2:25:28.3294990 PM our_prog.exe 1288 CloseFile our.file SUCCESS
42. 2:25:28.3351356 PM our_prog.exe 1288 CreateFile our.file SUCCESS Desired Access: Read Attributes, Delete,
43. 2:25:28.3351856 PM our_prog.exe 1288 QueryAttributeTagFile our.file SUCCESS Attributes: A, ReparseTag: 0x0
44. 2:25:28.3352020 PM our_prog.exe 1288 SetDispositionInformationFile our.file SUCCESS Delete: True
45. 2:25:28.3352218 PM our_prog.exe 1288 CloseFile our.file SUCCESS
46. 2:25:28.3358275 PM our_prog.exe 1288 CreateFile our.file DELETE PENDING Desired Access: Generic Read/Write,
47. 2:25:28.3362207 PM our_prog.exe 1288 CreateFile our.file DELETE PENDING Desired Access: Generic Read/Write,
48. 2:25:28.3367696 PM Explorer.EXE 1884 QueryBasicInformationFile our.file SUCCESS
49. 2:25:28.4279152 PM Explorer.EXE 1884 CloseFile our.file SUCCESS
50. 2:25:28.4282859 PM Explorer.EXE 1884 CreateFile our.file NAME NOT FOUND Desired Access: Read Attributes,

83. 2:25:29.3497760 PM our_prog.exe 1288 CreateFile our.file SUCCESS Desired Access: Generic Read/Write,
А видим мы там следующее (лишние данные удалены, чтоб не загромождать). Строки с 1 по 36 — мы создаем файл, пишем в него, делаем flush. Самое интересное начинается в строках 38-40. В них откуда ни возьмись появяется explorer.exe и начинает наш файл читать.

В строке 41 мы закрываем наш файл. В строке 42 — удаляем. И поскольку explorer.exe его все еще читает, файл не удаляется. Что мы можем увидеть в строках 46 и 47, когда мы пытаемся переименовать наш временный файл в основной (статус DELETE PENDING вместо SUCCESS).

Explorer.exe заканчивает чтение только в строке 49. Только в этот момент файл физически удаляется (о чем нам косвенно говорит строчка 50, где наш настойчивый explorer.exe вновь пытается открыть файл для чтения, но у него не получается, т.к. файла уже нет).

Что из этого следует? Что благодаря Микрософт Виндоуз даже простую операцию удаления файла теперь нужно делать в стиле параноидального программирования. Вызвали функцию удалить, убедились, что она вернула ОК, и вошли в цикл ожидания «пока файл еще не удален физически». Ну и да, без знания того, что «внутре у ней неонка» уже не получается сделать практически ничего…

Но вот гложет меня сомнение, что такой подход является общепринятой практикой. Чтобы при любой работе с файлами в Виндоуз держать в голове, что ОС в любой момент решит что-нибудь с файлами поделать без вашего ведома, и писать код, устойчивый к этому. В связи с чем опрос ниже.
Only registered users can participate in poll. Log in, please.
Сталкивались ли вы с ситуацией отложенного удаления файла под Windows?
46.9% Нет 53
53.1% Да 60
113 users voted. 44 users abstained.
Tags:WindowsExplorer.exeдырявые абстракцииненормальное программированиеотложенное удаление
Hubs: Abnormal programming C++ Development for Windows
+7
3.6k 27
Comments 20
Popular right now
Top of the last 24 hours