Как да създам атомарно заключен файл в Linux?

Сценарий: Имам много работещи процеси, които трябва да извличат файлове по мрежата. Ако файлът вече е изтеглен, искам да се кешира на диска. Ако друг процес изтегля файла, блокирайте, докато изтеглянето приключи.

Опитвах се да намеря най-лесния начин да направя това. Очевидният начин е да:

create file w/ an exclusive lock active on it only if it doesn't exist (O_CREAT | O_EXCL)
if file exists already:
   open file and acquire exclusive lock
else:
   download to newly created file
release lock

Тази система постига горните цели без (привидно) без условия за състезание

За съжаление, не можах да намеря документация за това как да използвам open() и т.н., за да създам файл, който е заключен в Linux. Ако разделя стъпката за създаване на:

open w/ O_CREAT | O_EXCL
flock

сега съществува състояние на състезание между създаването и заключването (процесът, който не създава, придобива заключването преди създателя).

Осъзнавам, че мога да използвам външен заключващ файл за всеки файл (напр. име на файл + '.lock), който получавам, преди да се опитам да създам име на файл, но това ми се струва... неелегантно (и сега трябва да се тревожа как да файловете, които всъщност имат .lock суфикс!)

Има ли все пак атомно създаване и заключване (както предлага Windows) или външният метод на заключващ файл е почти това, което е стандартно/изисквано?


person UsAaR33    schedule 27.06.2013    source източник


Отговори (3)


Така или иначе състезанието съществува. Ако файлът може да съществува или да не съществува, трябва да проверите съществуването му, преди да опитате да го заключите. Но ако файлът е вашият mutex, тогава не можете да направите това и интервалът между „ако файлът вече съществува“ (false) и „изтегляне в новосъздадения файл“ е неограничен. Друг процес може да дойде и да създаде файла и да започне изтеглянето, преди вашето изтегляне да започне, и вие ще го изтриете.

По принцип не използвайте fcntl ключалки тук, използвайте съществуването на самия файл. open() с O_CREAT и O_EXCL ще се провали, ако файлът вече съществува, което ви казва, че някой друг е стигнал до него пръв.

person Andy Ross    schedule 27.06.2013
comment
Не съм сигурен как ще съществува състезанието, ако мога атомарно да създам заключен файл, само ако файлът не съществува. (условието също е атомно). O_CREAT | O_EXCL е атомно създаване, ако файлът не съществува; Просто искам да направя това с ключалка. Друг процес не може да започне изтеглянето, тъй като ще установи, че файлът съществува. И накрая, не мога да използвам open сам, тъй като трябва да блокирам, докато изтеглянето приключи; в моето решение разчитам на (изключително четене/запис) заключване. - person UsAaR33; 28.06.2013
comment
Прав си, атомарното създаване и заключване на файл ще реши проблема ти. Но това не е начинът, по който fcntl заключванията работят, съжалявам. Ако искате да използвате fcntl заключване, файлът, който заключвате, трябва да съществува, преди да бъде предприето каквото и да е действие за синхронизиране от вашата програма. Намерете начин да преместите създаването извън този if() във вашия псевдокод, или в противен случай изработете друг протокол за синхронизация, който не използва файла за изтегляне като mutex. - person Andy Ross; 28.06.2013
comment
Разбрах, така че отговорът е, че Linux не предлага атомни ключалки в стил Windows с отваряне. Ще се придържам към метода на заключващия файл. - person UsAaR33; 28.06.2013

Защо не използвате помощна програма за заключващ файл?

Примери

Да предположим, че искате да се уверите, че достъпът до файла „важен“ е сериализиран, т.е. не повече от една програма или скрипт на обвивка трябва да имат достъп до него. За по-голяма простота, нека предположим, че това е shell скрипт. В този случай можете да го решите по следния начин:

...
lockfile important.lock
...
access_"important"_to_your_hearts_content
...
rm -f important.lock
...
person Jasio    schedule 02.09.2016

В момента се боря с това как да реша подобен проблем, което ме доведе до вашия въпрос. Както виждам, същността е следната:

int fd = open(path, O_CREAT|O_RDWR|O_EXCL, mode);
if (fd == -1)
    {
    /* File already exists. */
    the_file_already_exists(fd);
    }
else
    {
    /* I just now created the file.  Now I'll lock it. */

    /* But first I'll deliberately create a race condition!! */
    deliberately_fork_another_process_that_handles_file(path);

    int code = flock(fd,LOCK_EX);
    if (code < 0)
        {
        perror("flock");
        exit(1);
        }

    /* I now have the exclusive lock.  I can write to the file at will --
    or CAN I??  See below. */
    write_to_the_file_at_will(fd);
    }

Очевидно в реалния живот никога не бих създал това състезателно състояние, но неговият еквивалент със сигурност може да се случи случайно в реална система. Този друг процес може например да отвори файла за четене, да получи споделена ключалка за него и да прочете файла. Ще види празен файл. Това може да означава, че е в ход операция за запис, но може да означава, че файлът е просто празен и това е правилният и окончателен отговор.

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

Ако празните файлове СА позволени, значи сте в малко затруднение и аз нямам готов отговор за това.

Проблемът, който имам, е, че когато файлът е създаден за първи път, искам да запиша някаква стойност по подразбиране в него, защото искам да „автоматично инициализирам“ нова система, без да се налага да създавам предварително всеки възможен файл, който може да се нуждае. Този друг процес, който обработва файла, може самият вече да го е инициализирал! Доколкото знам, три други процеса може също да са стартирали междувременно и да са променили стойността. В такъв случай със сигурност не искам да "пиша във файла по желание" след получаване на изключителното заключване, защото ще изтрия всички тези промени.

Предполагам, че отговорът е за моя код по-горе, за да се увери, че файлът е празен, преди да пише в него. Ако НЕ е празен, тогава кодът трябва да се държи точно така, сякаш файлът вече съществува: т.е. трябва да извика:

the_file_already_exists(fd);

Може би най-важното в цялата тази дискусия е, че всеки процес, който обработва файла по някакъв начин, трябва да провери дали е празен и да се държи по съответния начин. Отново обаче, ако празните файлове СА разрешени, тогава все още не мога да се сетя за никакво гарантирано решение. Нищо от това не би било необходимо, ако имаше някакъв начин да се създаде файлът и да се заключи като единична атомна последователност, но не мисля, че има начин да се направи това.

person Patrick Chkoreff    schedule 21.06.2018
comment
Една малка точка: възможно е да заключите самата родителска директория. Просто отворете директорията само за четене, точно както всеки друг файл, и вземете изключителното заключване на този манипулатор. Докато всички играят по правилата, сега можете да създавате и изтривате файлове, както желаете, докато имате заключването. Недостатъкът е, че заключването на родителската директория е по-малко детайлно от заключването на отделни файлове. - person Patrick Chkoreff; 21.06.2018