Интеграция через файлы: блокировки, атомарный claim и практические рекомендации

· Обновлено: · · Интеграция через файлы, Блокировки, Проектирование, Разработка Windows

История изменений (4 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
В начало статьи добавлен раздел «Карта знаний этой статьи». Это сводка понятий и связей из основного текста: краткое резюме, схема и ссылка на страницу сведений. Утверждения в тексте не менялись.
Текст обновлён по итогам внешнего ревью (1283 замечания). Содержание отдельных правок см. в записи ниже.
Добавлен раздел с заранее зафиксированными терминами и таблица соответствия антипаттернов и мер. Добавлены четыре оговорки для общих папок (rename через разные share не атомарно; rename падает, если файл просто открыт; метки времени гарантированы только при закрытии handle; уведомления об изменениях теряются) и источник того, что byte-range lock игнорируется для memory-mapped файлов.
Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619637)

Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.

Го Комура (2026). Интеграция через файлы: блокировки, атомарный claim и практические рекомендации. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619637 https://comcomponent.com/ru/blog/2026/03/07/001-file-integration-locking-best-practices-komurasoft-style/

DOI (последняя версия)
10.5281/zenodo.21619637
DOI (эта версия)
10.5281/zenodo.21619638

Исключительный доступ при интеграции через файлы почти неизбежно всплывает на общих папках, в ночных batch-заданиях и при обмене между процессами. В поиске чаще всего спрашивают: хватит ли одной файловой блокировки; как не дать нескольким воркерам взять один и тот же файл; как не прочитать файл, который ещё пишется.

В этой статье исключительный доступ при интеграции через файлы разбирается вокруг файловых блокировок, атомарного claim, схемы temp -> rename и идемпотентности.

Сначала зафиксируем термины

В этой области много английских слов, которые так и прижились. Пока смысл плавает, текст читать тяжело. Ниже — значения, которые используются в статье.

Термин Что это значит здесь
Атомарный (atomic) Операция, промежуточное состояние которой снаружи не видно. Либо она полностью прошла, либо ничего не произошло
claim Захват права обработки: «этот файл обрабатываю я». В статье это в основном схема, где владельцем становится только тот, кому удалось сделать rename из incoming в processing/<worker>/
Атомарный claim Тот же claim, выполненный одной операцией. Если проверка и захват разделены, в щель успевает другой процесс (3.1)
lease Владение со сроком действия. В lock-файл пишут «кто» и «до какого момента», чтобы по истечении срока другой воркер мог подобрать работу (4.4)
stale Lock или claim, оставшиеся после аварийного завершения владельца. Если нельзя отличить «ещё жив» от «уже умер», останавливаются все (2.3)
manifest Отдельный файл с описанием содержимого: имя, размер, хеш, число записей и т. п. Приёмник сверяет по нему данные. done-файл — его минимальный вариант (4.2)
Идемпотентность (idempotency) Свойство: повторная обработка того же входа не меняет результат (4.5)
advisory lock Блокировка, которая работает, только если все участники соблюдают соглашение. ОС её не навязывает, поэтому программу, которая читает и пишет в обход, написать легко. К этому типу относится Linux flock
byte-range lock Блокировка не всего файла, а указанного диапазона. Типичный пример — Windows LockFileEx; её ОС уже принудительно соблюдает. Но есть исключения (3.5)

Карта знаний этой статьи

Статья разбирает файловый обмен через общую папку и ночные пакетные задания не как ставку на блокировки ОС, а как проектирование протокола передачи. Право на обработку захватывают атомарным claim до чтения; файл в процессе создания держат под temp-именем, после close публикуют rename на final-имя; завершение не угадывают по размеру или метке времени, а явно фиксируют done и manifest. Если используют lock file, делают его lease с ownerId и expiresAt, чтобы быть готовыми к stale lock; учитывают, что byte-range lock Windows не действует на файл, отображённый в память, а advisory lock бесполезен против стороны, которая не соблюдает договорённость; в итоге на практике силён дизайн, который добивает остаток идемпотентностью: повторная обработка того же ввода не ломает результат.

Карта знаний: исключающий доступ при файловом обменеСхема, которая показывает, как протокол передачи сочетает атомарный claim, публикацию temp→rename, done/manifest, lock file в форме lease и idempotency, и каким антипаттернам это противостоит, чтобы предотвратить двойную обработку и чтение файла во время записииспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетпредотвращаетможет вызватьрекомендуется длярекомендуется дляможет вызватьпредотвращаетможет вызватьрекомендуется дляможет вызватьрекомендуется длярекомендуется длятребуетиспользуетснижаетне рекомендуетсяне рекомендуетсянесовместимо сможет вызватьможет вызватьрекомендуется длярекомендуется дляможет вызватьпротокол передачи файловатомарный claimпубликация через temp -> close -> rename/replaceфайл done/manifestlock-файл с арендой (lease)обработка с расчётом на идемпотентностьфайловая блокировка ОСатомарное создание (CreateNew / O_CREAT|O_EXCL)двойная обработка (двойной учёт, повторная отправка, lost update)двухшаговая проверка Exists→Createпрямая запись в конечное имя файлачтение файла во время записипроверка завершения по стабилизации размера файлаантипаттерн взаимного обновления общего файлаstale lockbyte-range lock (блокировка диапазона)интеграция разнородных системadvisory lockfallback rename между томами (copy + delete)обмен файлами через общую папку (SMB)сбой rename при открытом файлененадёжность меток времени файлапериодический обход каталогапотеря уведомлений об изменениях

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 29, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

Содержание

  1. Сначала вывод (коротко)
  2. Типичные гонки при интеграции через файлы (схемы)
    • 2.1. Читают файл, который ещё пишется
    • 2.2. Несколько воркеров одновременно берут один файл
    • 2.3. Из-за stale lock останавливаются все
  3. Антипаттерны
    • 3.1. Двухшаговая проверка Exists -> Create
    • 3.2. Писать сразу в final-имя
    • 3.3. Считать файл готовым, когда размер перестал расти
    • 3.4. Общий файл обновляют все подряд
    • 3.5. Считать API блокировок универсальным
  4. Практические рекомендации
    • 4.1. Публиковать по схеме temp -> close -> rename / replace
    • 4.2. Явно фиксировать готовность через done / manifest
    • 4.3. Приёмник атомарно берёт claim
    • 4.4. Если опираетесь на lock-файл, делайте его lease
    • 4.5. Заранее исходить из идемпотентности
  5. Псевдокод (фрагменты)
  6. Как выбрать подход
  7. Итог
  8. Источники

В интеграции через файлы ломается не столько сам код, сколько «договорённость о передаче». Юнит-тесты проходят, а на боевой общей папке или в ночном batch иногда падает — и воспроизвести трудно. Такое бывает довольно часто.

Чаще всего виноваты не API файлового ввода-вывода, а три размытых места:

  • когда уже можно читать;
  • кто владеет правом обработки;
  • как восстанавливаться после сбоя.

В этой статье исключительный доступ при интеграции через файлы не сводится к блокировкам ОС: его разбирают как протокол передачи.

Код из статьи опубликован на GitHub как полный набор, который собирается и запускается: библиотека, демонстрация гонки claim между двумя воркерами и перехвата lease, юнит-тесты на гонки, порчу данных и stale lock.

file-integration-locking-best-practices-komurasoft-style - komurasoft-blog-samples (GitHub)

1. Сначала вывод (коротко)

  • Главное в интеграции через файлы — к моменту, когда видно final-имя, состояние уже должно быть «можно читать»
  • Состояния «собирается / опубликовано / в обработке / обработано» нужно выражать именами файлов и каталогами
  • Если воркеров несколько, claim берут атомарно, до чтения
  • Lock-файл и блокировки ОС — вспомогательные средства; в конце всё ловит идемпотентность

Иначе говоря, в интеграции через файлы суть не столько во взаимном исключении, сколько в протоколе передачи. Одним вызовом функции блокировки дело не заканчивается.

2. Типичные гонки при интеграции через файлы (схемы)

2.1. Читают файл, который ещё пишется

Если писать сразу в final-имя, случается именно это. В JSON не будет закрывающей скобки, в CSV не хватит строк, ZIP просто окажется повреждён.

ПолучательОбщая папкаОтправительПолучательОбщая папкаОтправительФайл ещё не готовНе хватает строк / ошибка разбора / обработана только частьСоздаёт orders.csv под final-именемПишет строки с 1-й по 5000-юОбнаруживает orders.csvСразу начинает читатьДописывает остаток

2.2. Несколько воркеров одновременно берут один файл

Схема «посмотрели список, открыли, если ещё не обработан» позволяет двум воркерам схватить один и тот же файл. Отсюда начинаются двойной учёт и повторная отправка.

incomingВоркер 2Воркер 1incomingВоркер 2Воркер 1Один и тот же вход обрабатывается дваждыНаходит a.csvНаходит a.csvНачинает читатьНачинает читать

2.3. Из-за stale lock останавливаются все

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

Воркер Block-файлВоркер AВоркер Block-файлВоркер AЗдесь аварийно завершаетсяНельзя понять, stale это или нет, — все стоятСоздаёт lockПроверяет, что lock естьОткладывает старт обработкиЖдёт дальше

3. Антипаттерны

3.1. Двухшаговая проверка Exists -> Create

Проблема в том, что «проверить» и «занять» — разные операции. Между ними может вклиниться другой процесс, поэтому взаимного исключения нет.

Файловая системаПроцесс BПроцесс AФайловая системаПроцесс BПроцесс AОба идут дальшеПроверяет, что lock нетПроверяет, что lock нетНетНетСоздаёт lockСоздаёт lock

Типичный плохой пример выглядит так.

if (!File.Exists(lockPath))
{
    File.WriteAllText(lockPath, Environment.ProcessId.ToString());
    ProcessFile();
}

Нужно, чтобы «создать, если нет» было одной операцией. В .NET это семейство FileMode.CreateNew, в POSIX — атомарное создание вроде O_CREAT | O_EXCL.

3.2. Писать сразу в final-имя

Если приёмник понимает появление имени как «уже можно читать», запись сразу в final-имя — уже проигрыш. Базовое правило: быть видимым и быть готовым к чтению — не одно и то же.

Видно final-имяПриёмник обнаруживает файлОтправитель ещё пишетЧитают неполные данные
using var writer = OpenForWrite(finalPath); // Здесь finalPath уже виден
foreach (var row in rows)
{
    writer.WriteLine(row);
}

Так вы сами вызываете аварию из раздела 2.1.

3.3. Считать файл готовым, когда размер перестал расти

На вид удобно, на деле довольно опасно. Копирование по сети, пауза на стороне отправителя, буферизация и повторные попытки легко дают ложную «стабильность».

ПолучательОбщая папкаОтправительПолучательОбщая папкаОтправительОшибочно считает файл готовымНачинает копировать data.zipНа середине делает паузуРазмер 10 секунд не меняетсяНачинает читатьВозобновляет копирование
if (currentLength == lastLength && stableSeconds >= 10)
{
    return Ready;
}

Если готовность угадывают, общие папки и большие файлы рано или поздно подводят. Надёжнее явно сообщать о завершении через manifest или done-файл.

3.4. Общий файл обновляют все подряд

Схема, в которой все читают и правят один status.csv или counter.json, обычно заканчивается тем, что побеждает последний записавший. Как только файловый обмен начинают использовать как упрощённую БД, боль начинается именно здесь.

status.csvBatch BBatch Astatus.csvBatch BBatch AОбновление от A пропадаетЧитает v1Читает v1Пишет v2-AПишет v2-B

Можно уйти в append-only, но смысл этой схемы плавает в зависимости от файловой системы и размещения. Если общее обновление действительно нужно, файловым обменом здесь лучше не насиловать задачу.

3.5. Считать API блокировок универсальным

API блокировок важны, но работают только когда все участники играют по одним правилам. При интеграции разнородных систем на них лучше не опираться слишком сильно.

Дополнительно:

  • Linux flock — advisory lock, поэтому программу, которая игнорирует соглашение, написать легко
  • Windows byte-range lock не действует на memory-mapped файлы
  • Иными словами, на одни блокировки ОС не стоит вешать ещё и уведомление о готовности, и владение

Второй пункт прямо зафиксирован в спецификации Windows. В Microsoft Learn, Locking and Unlocking Byte Ranges in Files, сразу после фразы, что доступ другого процесса к уже заблокированному диапазону всегда завершается ошибкой (то есть диапазонная блокировка Windows не advisory, а принудительная), стоит предупреждение: для memory-mapped файлов byte-range lock игнорируется. Если другая сторона трогает тот же файл через CreateFileMapping, ваша блокировка её не остановит.

Диапазонную блокировку в .NET берут через FileStream.Lock / Unlock (на Windows).

using var stream = new FileStream(
    path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);

// Эксклюзивно блокируем только первый байт как метку «идёт обработка»
stream.Lock(0, 1);
try
{
    // Здесь читаем и пишем сами данные
}
finally
{
    // Снимаем блокировку обязательно до закрытия
    stream.Unlock(0, 1);
}

Такая схема работает между приложениями, которые играют по одним правилам. Но, как сказано выше, через memory-mapped файл она не действует, и нет гарантии, что другая система вообще посмотрит на эту метку. Поэтому основу задаёт протокол передачи из главы 4.

4. Практические рекомендации

Сначала — соответствие антипаттернам из главы 3. Если узнаёте один из них, можно сразу перейти к парному разделу.

Антипаттерн Что происходит Что делать
3.1. Двухшаговая проверка Exists -> Create В щель между проверкой и захватом вклинивается другой процесс, и оба идут дальше 4.3 брать claim атомарно (rename или FileMode.CreateNew)
3.2. Писать сразу в final-имя Приёмник читает файл, который ещё пишется 4.1 публиковать по схеме temp -> close -> rename / replace
3.3. Считать файл готовым, когда размер перестал расти Паузу копирования принимают за завершение 4.2 явно фиксировать готовность через done / manifest
3.4. Общий файл обновляют все подряд Поздняя запись затирает более раннюю, обновление пропадает 4.3 сузить писателя до одного, 4.5 поглотить повторную обработку. Если этого мало — критерий отступления в главе 6
3.5. Считать API блокировок универсальным Схему ломает сторона, которая не соблюдает соглашение, или доступ через memory-mapped файл 4.4 lock-файл как lease и 4.5 идемпотентность как последняя линия

4.1. Публиковать по схеме temp -> close -> rename / replace

Это проверенный путь. Пока файл собирается, он живёт под temp-именем; после close его переключают на final-имя. Приёмник смотрит только на final-имя.

Создать уникальное temp-имяЗаписать в temp всё содержимоеflush / closerename / replace на final-имя в том же каталогеПриёмник следит только за final-именем

На что смотреть:

  • temp и final лежат в одном каталоге, как минимум на одном томе / в одной файловой системе
  • в Windows / .NET можно рассмотреть семейство File.Replace
  • договорённость: как только видно final-имя, содержимое уже готово

Если temp положить на другой диск, rename фактически превращается в копирование, либо Replace падает. Предпосылка скучная, но очень важная.

Через общую папку (SMB) плавают ещё четыре пункта. Именно это — основная сцена статьи, поэтому вынесем отдельно.

  • rename внутри одного каталога одной share выполняется на стороне сервера. Свойство «промежуточное имя не видно» при этом сохраняется. Если же переходить с \\server\shareA на \\server\shareB, это уже другой том: Windows MoveFileEx при MOVEFILE_COPY_ALLOWED подменяет перемещение копированием и удалением. Атомарности нет, промежуточное состояние видно. Держать temp и final, incoming и processing внутри одной share на общих папках ещё важнее, чем локально
  • rename падает, даже если файл просто кем-то открыт. На общую папку заходят антивирус, поисковый индексер, клиенты с других площадок — стороны, о которых вы можете не знать. Rename при публикации и claim лучше считать обычной веткой, а не аварией: короткая пауза и повтор
  • Метки времени — плохой критерий. В Microsoft Learn, File Times, гарантировано только одно: метка корректно отражается в момент закрытия handle, которым меняли файл. Время последнего изменения во время записи полностью не обновляется, пока не закрыты все handle на запись. Гранулярность зависит от файловой системы: на FAT время изменения идёт с шагом 2 секунды, на NTFS время последнего доступа может отставать до часа. Через SMB метку ставит часы сервера, поэтому при расхождении часов клиента и сервера съезжает и правило «обработать через N минут после изменения». Именно поэтому готовность смотрят не по времени и размеру, а по done / manifest из 4.2
  • Уведомления об изменениях тоже теряются. Не опирайтесь только на события: стабильнее сочетать их с периодическим перечислением каталога. Это разобрано в практическом руководстве по FileSystemWatcher — пропуски и дубли

4.2. Явно фиксировать готовность через done / manifest

Если помимо самих данных отдельно сказать, «что именно готово», приёмник становится устойчивее. Особенно это помогает при интеграции разнородных систем.

Собрать data.tmpОпубликовать как data.csvСоздать data.done / manifest.jsonПриёмник обнаруживает done / manifestПроверяет имя файла, размер и хеш

В manifest обычно кладут примерно такие поля.

  • имя целевого файла
  • размер
  • хеш
  • число записей
  • ID интеграции / ключ идемпотентности
  • время генерации

Порядок тоже важен. Если положить done раньше публикации самих данных, это уже не уведомление о готовности, а предупреждение о будущем сбое.

4.3. Приёмник атомарно берёт claim

Если за одним incoming следят несколько воркеров, понятнее всего «переложить файл к себе до чтения». Обрабатывает файл только тот воркер, у которого прошёл rename из incoming в processing/<worker>/.

processingincomingВоркер 2Воркер 1processingincomingВоркер 2Воркер 1Владение получает только тот, кто успел первымНаходит a.csvНаходит a.csvДелает rename a.csvДелает rename a.csv

На практике каталоги тоже лучше развести — так проще отслеживать состояние.

publishclaimуспехошибкаtempincomingprocessingarchiveerror

И rename для claim предполагается на той же файловой системе.

4.4. Если опираетесь на lock-файл, делайте его lease

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

lock.jsonownerIdhostpidacquiredAtexpiresAtheartbeatAt

На что смотреть:

  • создание атомарно
  • остановку обновлений используют как признак stale
  • удаляет, как правило, только создатель
  • восстановление продумывают заранее, исходя из того, что блокировку могут не снять

Lock-файл — всего лишь маркер для согласования. Пытаться одним таким файлом гарантировать полную согласованность обычно оказывается тяжело.

4.5. Заранее исходить из идемпотентности

Исключительный доступ важен, но в реальной эксплуатации «иногда приходит дважды» и «перезапуск на середине» до нуля не свести. В конце выручает схема, которая не ломается, даже если тот же вход обработать ещё раз.

данетВход + ключ идемпотентностиУже обработано?Считать успехом без повторного выполненияВыполнить обработкуЗаписать в журнал обработанных

Например, каждому принятому файлу дают ID интеграции и пишут его в журнал обработанных. Если результат не задваивается даже после разового нарушения исключительного доступа, жить с системой гораздо проще.

5. Псевдокод (фрагменты)

MakeTempPathSameDirectory и TryClaimBundleByRename ниже — вымышленные имена функций, чтобы показать порядок. Рабочая реализация есть в наборе примеров, на который ссылается начало статьи.

Реализация этого псевдокода (библиотека, демонстрация гонки claim между двумя воркерами, юнит-тесты) - komurasoft-blog-samples (GitHub)

5.1. Типичный проигрышный паттерн

var lockPath = finalPath + ".lock";

if (!File.Exists(lockPath))
{
    File.WriteAllText(lockPath, "");
    using var writer = OpenForWrite(finalPath); // Пишем сразу в final-имя
    WritePayload(writer);

    File.Delete(lockPath);
}

Проблем три.

  • Exists и WriteAllText — разные операции
  • finalPath становится виден ещё во время записи
  • при аварийном завершении lock остаётся

5.2. Пример в правильную сторону (грубо)

var tempPath = MakeTempPathSameDirectory(finalPath);
WritePayload(tempPath);
FlushAndClose(tempPath);

PublishByRenameOrReplace(tempPath, finalPath); // Предполагается одна ФС / один том
PublishDoneFile(finalPath + ".done", new
{
    FileName = Path.GetFileName(finalPath),
    Size = GetFileSize(finalPath),
    Hash = ComputeHash(finalPath),
    IdempotencyKey = integrationId
});
if (!TryClaimBundleByRename(baseName, incomingDir, processingDir))
{
    return; // Другой воркер захватил файл раньше
}

var manifest = ReadDoneFile(Path.Combine(processingDir, baseName + ".done"));
VerifyPayload(Path.Combine(processingDir, baseName), manifest);

if (AlreadyProcessed(manifest.IdempotencyKey))
{
    MoveBundle(processingDir, archiveDir, baseName);
    return;
}

Process(Path.Combine(processingDir, baseName));
RecordProcessed(manifest.IdempotencyKey);
MoveBundle(processingDir, archiveDir, baseName);

Здесь важнее не детали реализации, а порядок. Если не смешивать «записать», «опубликовать», «взять владение» и «отметить обработанным», ломается заметно реже.

6. Как выбрать подход

  • Один writer / один reader / один хост — уже одной схемы temp -> rename часто хватает для приличной устойчивости
  • Несколько consumer — добавьте claim-rename incoming -> processing
  • Разнородные системы, NAS, общие папки — безопаснее довести до manifest / done и идемпотентности
  • Несколько writer хотят обновлять одно логическое состояние — не выжимайте файловый обмен сверх меры, рассмотрите БД или очередь
  • Блокировки ОС полезны внутри одной группы приложений с одними допущениями, но протокол передачи они не заменяют

Последний пункт — ещё и критерий отступления. Есть задачи, которые файлами решать действительно тяжело.

7. Итог

Исключительный доступ при интеграции через файлы — это не вызов функции блокировки, а договорённость о переходах состояния. В этом суть статьи. Состояния «собирается / опубликовано / в обработке / обработано» выражают именами и каталогами; двухшаговой проверки Exists -> Create, записи сразу в final-имя, ожидания «размер замер», взаимного обновления общего файла и слепой веры в API блокировок избегают. Если поверх этого сочетать temp -> close -> rename / replace, done / manifest, claim-rename, lease и идемпотентность, аварий на общих папках становится заметно меньше.

Приём простой: не отождествлять «файл технически читается» с «его уже можно читать». Одно это разделение сильно сокращает сбои, которые проявляются только ночью.

8. Источники

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Достаточно ли API блокировок для исключительного доступа при интеграции через файлы?
Часто нет. Linux flock — advisory lock: программа, которая игнорирует соглашение, спокойно читает и пишет. Windows byte-range lock не действует на memory-mapped файлы. Блокировки ОС полезны внутри одной группы приложений с одними допущениями, но это вспомогательное средство. Основу стоит строить как протокол передачи: temp -> rename, done/manifest, атомарный claim и идемпотентность.
Как не дать прочитать файл, который ещё пишется?
Проверенный способ — публиковать по схеме temp -> close -> rename/replace. Пока файл собирается, он живёт под temp-именем; после close его переключают на final-имя в том же каталоге, а приёмник смотрит только на final-имя. Исходная предпосылка: temp и final лежат в одном каталоге, как минимум на одном томе / в одной файловой системе. Договорённость: как только видно final-имя, содержимое уже готово.
Как не дать нескольким воркерам обработать один и тот же файл?
Claim нужно брать атомарно, до чтения. Обрабатывает файл только тот воркер, у которого прошёл rename из incoming в processing/<worker>/. Двухшаговая проверка Exists -> Create взаимного исключения не даёт: «проверить» и «занять» — разные операции, и между ними может вклиниться другой процесс. Для атомарного создания берите семейство FileMode.CreateNew в .NET или O_CREAT | O_EXCL в POSIX.
На что смотреть, если используете lock-файл?
Это не пустой файл, а lease — запись о владении со сроком действия: ownerId, host, pid, acquiredAt, expiresAt, heartbeatAt. Создавайте атомарно, остановку обновлений используйте как признак stale, удаляйте, как правило, только создатель, и заранее решите, как восстанавливаться, если блокировку не сняли. Одним lock-файлом полную согласованность обычно не закрывают: на практике сильнее работает схема, где в конце всё ловит идемпотентность.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог