Repository navigation
Expand file tree
/
Copy pathatomic-write.js
More file actions
53 lines (49 loc) · 3.14 KB
/
Copy pathatomic-write.js
File metadata and controls
53 lines (49 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
'use strict';
/**
* atomic-write.js — атомарная запись файла состояния (BUG-09).
*
* `fs.writeFileSync` поверх старого файла сначала обрезает его до нуля и лишь
* потом пишет. Падение процесса, выключение питания или полный диск в этом
* промежутке оставляли пустой или обрезанный JSON, а читатели (накопитель
* перелимита, снимок восстановления) честно принимали битый файл за «ничего
* нет» — итог мероприятия, то есть деньги, молча становился нулём.
*
* Здесь: временный файл В ТОМ ЖЕ каталоге (rename атомарен только в пределах
* одной файловой системы) → запись → fsync (данные на диске ДО того, как имя
* на них укажет) → rename поверх старого. Любой сбой до rename оставляет
* старый файл нетронутым; временный убирается.
*
* fsync каталога не делается: на Windows каталог так не открыть, а без него
* худший исход после сбоя питания — старая версия файла, не битая.
*
* Модуль без Electron — проверяется в голом `node --test`.
*/
const fs = require('fs');
function writeFileAtomicSync(filePath, data) {
// pid в имени: два экземпляра приложения (мимо single-instance-lock —
// e2e, съёмка) не пишут в один временный файл.
const tmpPath = `${filePath}.${process.pid}.tmp`;
let fd = null;
try {
// Залежавшийся `.tmp` того же pid (например, от прошлого краха этого
// же процесса до rename) не должен ронять запись на EEXIST — `wx`
// бросает именно на существующем файле. Убираем его заранее, а не
// ловим EEXIST в catch: там уже нет пути «попробовать снова».
try { fs.unlinkSync(tmpPath); } catch { /* его могло и не быть */ }
// 0o600: файл состояния может нести итог перелимита (деньги) —
// читать и писать его должен только владелец процесса.
fd = fs.openSync(tmpPath, 'wx', 0o600);
fs.writeSync(fd, data);
fs.fsyncSync(fd);
fs.closeSync(fd);
fd = null;
fs.renameSync(tmpPath, filePath);
} catch (err) {
if (fd !== null) {
try { fs.closeSync(fd); } catch { /* уже неважно */ }
}
try { fs.unlinkSync(tmpPath); } catch { /* его могло и не быть */ }
throw err;
}
}
module.exports = { writeFileAtomicSync };