Допустим, ты пишешь простенький кеш для скачивания файлов, чтобы твой код не качал одно и то же по несколько раз:
function download(file) {
if (exists(file)) {
return file;
}
const data = download(file);
save(file, data);
return file;
}
// было:
function save(filename, data) {
write(filename, data);
}
В таком коде может произойти ситуация, когда файл еще не дописался внутри save()
, но уже кому-то отдался неполный. Потому что exists(file)
станет true
сразу после открытия файла на запись, еще задолго до того, как был записан первый байт.
Чтобы такого не происходило — файл следует писать атомарно:
// стало:
function save(filename, data) {
// придумай временное имя
const tmpFilename = filename + '.tmp';
// если рядом кто-то уже пишет во временный файл —
// это означает что он точно закончит запись
// и целевой файл скоро появится.
if (exists(tmpFilename)) {
return;
}
// неспешно пишем во временный файл
write(tmpFilename, data);
// если целевой файл появился, пока мы писали временный —
// это означает что кто-то быстрее нас скачал и записал.
if (exists(filename)) {
return;
}
// переименовали временный файл в целевой.
// одна операция, файл сразу появится целиком!
rename(tmpFilename, filename);
}
Обрати внимание: в таком коде на любом этапе исключены проблемы с целостностью данных.