Перемещение каталога атомарно

У меня есть два каталога в одном родительском каталоге. Назовите родительский каталог base и дочерние каталоги alpha и bravo. Я хочу заменить alpha на bravo. Самый простой метод:

rm -rf alpha
mv bravo alpha

Команда mv атомарна, а rm -rf — нет. Есть ли простой способ в bash атомарно заменить alpha на bravo? Если нет, есть ли сложный способ?

ДОПОЛНЕНИЕ:

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


person dirtside    schedule 21.11.2008    source источник
comment
Ваш тест в приложении небезопасен - есть состояние гонки. Подумайте, что произойдет, если сначала запустится проверка (и альфа существует), а затем она отключится, а второй процесс удалит альфа, а затем снова включится, чтобы продолжить выполнение, но теперь альфа отсутствует.   -  person Oddthinking    schedule 21.11.2008


Ответы (16)


Вы можете сделать это, если используете символические ссылки:

Допустим, alpha — это символическая ссылка на каталог alpha_1, и вы хотите переключить символическую ссылку, чтобы она указывала на alpha_2. Вот как это выглядит до переключения:

$ ls -l
lrwxrwxrwx alpha -> alpha_1
drwxr-xr-x alpha_1
drwxr-xr-x alpha_2

Чтобы альфа ссылалась на альфа_2, используйте ln -nsf:

$ ln -nsf alpha_2 alpha
$ ls -l
lrwxrwxrwx alpha -> alpha_2
drwxr-xr-x alpha_1
drwxr-xr-x alpha_2

Теперь вы можете удалить старый каталог:

$ rm -rf alpha_1

Обратите внимание, что на самом деле это НЕ полностью атомарная операция, но это происходит очень быстро, поскольку команда «ln» одновременно удаляет ссылку, а затем немедленно воссоздает символическую ссылку. Вы можете проверить это поведение с помощью strace:

$ strace ln -nsf alpha_2 alpha
...
symlink("alpha_2", "alpha")             = -1 EEXIST (File exists)
unlink("alpha")                         = 0
symlink("alpha_2", "alpha")             = 0
...

Вы можете повторить эту процедуру по желанию: например. когда у вас есть новая версия, alpha_3:

$ ln -nsf alpha_3 alpha
$ rm -rf alpha_2
person Doug Currie    schedule 21.11.2008
comment
linux VFS не поддерживает жесткие ссылки на несколько каталогов. Некоторые другие *nix имеют ограниченную поддержку, ограниченную суперпользователем. Вам также все равно придется собрать все теперь потерянные ссылки подкаталогов и файлов. - person JimB; 21.11.2008
comment
Да, это должна быть программная ссылка, чтобы быть общеприменимой. Я отредактировал свой ответ. Однако я не верю, что будут сироты, если альфа всегда является ссылкой, что я и имел в виду, слегка изменив вопрос. Конечно, вам всегда придется удалять предыдущую версию каталога. - person Doug Currie; 21.11.2008
comment
Очень близко; оказывается, вам также нужен флаг -n, иначе вы создадите символическую ссылку в исходном каталоге. На самом деле я попробовал вашу идею, прежде чем публиковать вопрос, и это не сработало, но когда я снова посмотрел и заметил флаг -n, это сработало. И похуй на того, кто тебя проголосовал :) - person dirtside; 21.11.2008
comment
процесс A пытается что-то сделать в альфа-версии, что бы вы ни делали после этого момента, может быть атомарным или нет, вы все равно можете стереть каталог, пока он используется. Быть атомарным бесполезно, вам нужна сериализация, а не атомарность, если только ваш код, обращающийся к альфа-каналу, также не является атомарным. - person shodanex; 16.11.2009
comment
@dirtside Почему вы приняли этот ответ? Он явно не атомный. - person Navin; 14.05.2017

Окончательное решение сочетает подход с символической ссылкой и переименованием:

mkdir alpha_real
ln -s alpha_real alpha

# now use "alpha"

mkdir beta_real
ln -s beta_real tmp 

# atomically rename "tmp" to "alpha"
# use -T to actually replace "alpha" instead of moving *into* "alpha"
mv -T tmp alpha

Конечно, приложение, получающее доступ к альфе, должно иметь возможность обрабатывать символические ссылки, изменяющиеся в пути.

person David Schmitt    schedule 04.06.2012
comment
rcrowley.org/2010/01/06/things- unix-can-do-atomically.html для объяснения флага -T для mv, который позволяет атомарно обменивать две символические ссылки. - person PypeBros; 03.09.2015

Взяв здесь решение Дэвида, которое полностью атомарно ... единственная проблема, с которой вы столкнетесь, заключается в том, что параметр -T для mv не является POSIX, и поэтому некоторые ОС POSIX могут его не поддерживать (FreeBSD, Solaris и т. ... http://pubs.opengroup.org/onlinepubs/9699919799/utilities/mv.html). С небольшой модификацией этот подход можно изменить, чтобы он стал полностью атомарным и переносимым на все ОС POSIX:

mkdir -p tmp/real_dir1 tmp/real_dir2
touch tmp/real_dir1/a tmp/real_dir2/a
# start with ./target_dir pointing to tmp/real_dir1
ln -s tmp/real_dir1 target_dir
# create a symlink named target_dir in tmp, pointing to real_dir2
ln -sf tmp/real_dir2 tmp/target_dir
# atomically mv it into ./ replacing ./target_dir
mv tmp/target_dir ./

пример через: http://axialcorps.wordpress.com/2013/07/03/atomically-replacing-files-and-directories/

person mssaxm    schedule 05.07.2013

Начиная с Linux 3.15, новый системный вызов renameat2 может атомарно обмениваться двумя путями в одной и той же файловой системе. Однако для него пока нет даже оболочки glibc, не говоря уже о способе доступа к нему с помощью coreutils. Таким образом, это будет выглядеть примерно так:

int dirfd = open(".../base", O_PATH | O_DIRECTORY | O_CLOEXEC);
syscall(SYS_renameat2, dirfd, "alpha", dirfd, "bravo", RENAME_EXCHANGE);
close(dirfd);
system("rm -rf alpha");

(Конечно, вы должны правильно обрабатывать ошибки и т. д. — см. эту суть для более сложной renameat2 обертка.)

Тем не менее, решение для символических ссылок, упомянутое другими, является более простым и переносимым, поэтому, если bravo уже не существует и вы должны обновить его атомарно, вместо этого используйте символическую ссылку.


Обновление 2020: оболочка glibc для этого системного вызова доступна, начиная с glibc 2.28, выпущенного 01 августа 2018 г. (Debian Stretch, Fedora 29). Однако он по-прежнему недоступен через coreutils.

int dirfd = open(".../base", O_PATH | O_DIRECTORY | O_CLOEXEC);
renameat2(dirfd, "alpha", dirfd, "bravo", RENAME_EXCHANGE);
close(dirfd);
system("rm -rf alpha");
person Lucas Werkmeister    schedule 15.05.2018
comment
Спасибо за указание на это! Хотя это и не переносимо, это помогает мне в ситуации, когда атомарного перемещения символической ссылки недостаточно. - person schieferstapel; 25.08.2020

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

Итак, если операции создания и удаления файла атомарны:

1) создайте файл с именем «семафор».

2) Если и только если это успешно (нет конфликта с существующим файлом), выполните операцию (либо обработайте альфа-канал, либо переместите каталог, в зависимости от процесса)

3) rm семафор.

person Oddthinking    schedule 21.11.2008
comment
это поможет только в том случае, если какая-либо операция, которая должна выполняться в альфа-версии, будет переписана, чтобы сначала проверить semaphore и дождаться возможности заблокировать сам семафор ... и если они не позволят вам создать свой семафор, когда они начинают свою собственную операцию. - person PypeBros; 02.09.2015
comment
@PypeBros: Да. Если вы не проверите его перед выполнением операции, он не используется в качестве семафора. Если он может быть создан двумя параллельными процессами, это не семафор. - person Oddthinking; 02.09.2015
comment
Это отличное дополнение к другим ответам - это, вероятно, единственный способ по-настоящему сделать процесс атомарным. - person Ken Williams; 22.03.2021

Если вы имеете в виду атомарность для обеих операций, я в это не верю. Ближе всего будет:

mv alpha delta
mv bravo alpha
rm -rf delta

но у этого все еще было бы маленькое окно, где альфа не существовало.

Чтобы свести к минимуму вероятность того, что что-либо попытается использовать альфу, пока ее нет, вы можете (если у вас есть полномочия):

nice --20 ( mv alpha delta ; mv bravo alpha )
rm -rf delta

что значительно повысит приоритет вашего процесса, пока выполняются операции mv.

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

person paxdiablo    schedule 21.11.2008
comment
Это примерно так же быстро, как вы можете сделать это в оболочке; вы можете написать собственный фрагмент C для перемещения двух каталогов, что сократит временной интервал на несколько миллисекунд, или использовать сценарий Perl (или выбрать свой собственный яд). Однако нет смысла переписывать 'rm -fr'. - person Jonathan Leffler; 21.11.2008

Раздел документации SQLite Блокировка файлов и параллелизм в SQLite версии 3 содержит хорошо написанное описание протокола эскалации блокировки для управления одновременным чтением, эксклюзивной записью и откатом после сбоя. . Некоторые из этих идей применимы и здесь.

person seh    schedule 07.11.2009

Это должно помочь:

mkdir bravo_dir alpha_dir
ln -s bravo_dir bravo
ln -s alpha_dir alpha
mv -fT bravo alpha

strace mv -fT bravo alpha показывает:

rename("bravo", "alpha")

который выглядит довольно атомарным для меня.

person Peter    schedule 06.01.2014

Даже если бы вы обращались к инодам напрямую, все равно не было бы возможности атомарно поменять местами значения инодов в пользовательском пространстве.

person Tyler McHenry    schedule 21.11.2008

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

Семафорный подход странного мышления — единственный выход.

Если вы не можете изменить другую задачу, вам нужно убедиться, что она не запущена, прежде чем выполнять замену.

person Loren Pechtel    schedule 21.11.2008

Я не верю, что есть атомный способ сделать это. Лучше всего сделать что-то вроде этого:

mv alpha delme
mv bravo alpha
rm -rf delme
person Chris Charabaruk    schedule 21.11.2008

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

person Brian C. Lane    schedule 21.11.2008

mv и ln могут использоваться для атомарных операций. Я использовал ln(1) для атомарного развертывания веб-приложений.

Правильный способ замены символической ссылки — с помощью ln -nsf

ln -nsf <target> <link_name>

e.g.

$ mkdir dir1
$ mkdir dir2
$ ln -s dir1 mylink
$ ls -l mylink
lrwxrwxrwx  1 phil phil 4 Nov 16 14:45 mylink -> dir1
$ ln -nsf dir2 mylink
$ ls -l mylink
lrwxrwxrwx  1 phil phil 4 Nov 16 14:46 mylink -> dir2
person Philip Reynolds    schedule 16.11.2009
comment
ln -nsf быстрый, но на самом деле он не атомарный. Процесс ln фактически отключает связь, а затем немедленно воссоздает символическую ссылку. Вы можете проверить это с помощью strace. См. blog.moertel.com/articles/2005. /22/08/ - person Daniel S. Sterling; 24.03.2011

Также можно заменить целые части контента сразу в каком-то префиксе (здесь Z) с помощью unionfs-fuse:

# mkdir a b c Z
# touch a/1 b/2 c/3
# ln -s a X
# ln -s b Y
# unionfs X=RW:Y=RW Z
# shopt -s globstar
# file **
a:   directory
a/1: empty
b:   directory
b/2: empty
c:   directory
c/3: empty
X:   symbolic link to a
Y:   symbolic link to b
Z:   directory
Z/1: empty
Z/2: empty
# ln -sfn c Y
# file **/*
a:   directory
a/1: empty
b:   directory
b/2: empty
c:   directory
c/3: empty
X:   symbolic link to a
X/1: empty
Y:   symbolic link to c
Y/3: empty
Z:   directory
Z/1: empty
Z/3: empty
# fusermount -u Z
# rm -r a b c X Y Z
person Tomilov Anatoliy    schedule 12.01.2018

mount --bind bravo alpha должен сделать это в Linux

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

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

Вы также должны быть осторожны с процессами, имеющими открытый дескриптор в каталоге или подкаталоге alpha (например, cwd).

У других *nix могут быть подобные трюки в рукаве, но они не стандартизированы.

person William Hay    schedule 01.11.2019

Почему бы вам просто не сделать что-то вроде:

rm -rf alpha/*
mv bravo/* alpha/
rm -rf bravo/

Это означает, что все в альфа-версии уничтожается, альфа-версия никогда не удаляется, а все содержимое перемещается.

person Dan Fego    schedule 21.11.2008