Skip to content

Commit

Permalink
attempt atomic file write for cache
Browse files Browse the repository at this point in the history
  • Loading branch information
phfaist committed Feb 14, 2025
1 parent 3249d2f commit e8acda5
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 2 deletions.
8 changes: 7 additions & 1 deletion src/citationmanager/_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const debug = debug_module('zoodb.citationmanager');
import sha256 from 'hash.js/lib/hash/sha/256.js';

import { promisifyMethods } from '../util/prify.js';
import { writeFileAtomic } from '../util/atomicfilewrite.js';

import { Cache, one_day } from './_cache.js';

Expand Down Expand Up @@ -111,7 +112,12 @@ export class CitationDatabaseManager
const fsp = this.cache_fsp;
// debug(`Saving database to cache file ‘${this.cache_file}’`);
// to this.cache_file
await fsp.writeFile(this.cache_file, this.cache.exportJson());
await writeFileAtomic({
fsp,
fileName: this.cache_file,
data: this.cache.exportJson(),
processPid: (process != null) ? process.pid : 'XX',
});
}

/**
Expand Down
58 changes: 58 additions & 0 deletions src/util/atomicfilewrite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import path from 'path';

/**
* Write `data` to the file with the given `fileName`, overwriting it silently if it
* exists. The write first happens to a temporary hidden file, and finally after the
* write succeeds, the temporary file is renamed to the target file. This procedure
* prevents the output file from being corrupted if the process is interrupted while
* data is being written to the file.
*
* Arguments:
*
* - `fsp` a promisified filesystem object compatible with Node.js' promisified `fs`
* object.
*
* - `fileName` the name of the final file to write to.
*
* - `data` the data to write to the file. Can be string, typed array, or whatever
* fsp's `writeFile()` can handle.
*
* - `processPid` - specify `process.pid` here (or whatever you choose to use as relevant
* PID for platforms/browser that might not have process PIDs).
*/
export async function writeFileAtomic({ fsp, fileName, data, useTempDir, processPid })
{
let atomicWriteTempFileName = null;
let counter = 0;
let dataWritten = false;

const fDir = useTempDir ?? path.dirname(fileName) ?? '';
const fBaseTemplate = `.atomicwrite.${path.basename(fileName)}.${processPid}`

while (!dataWritten && counter <= 99) {
atomicWriteTempFileName = path.join(
fDir,
`${fBaseTemplate}.${counter}.tmp`
);
// do not accidentally overwrite a file; use 'wx' as open flags so that open fails
// if the file exists.
try {
await fsp.writeFile(atomicWriteTempFileName, data, {
flag: 'wx',
});
dataWritten = true;
} catch (err) {
// if file already exists, try another one
if (err && err.code === 'EEXIST') {
++ counter;
} else {
// it's another error, send it up the chain
throw err;
}
}
}
if (!dataWritten) {
throw new Error(`writeFileAtomic: Failed to find temporary file to write to!`);
}
await fsp.rename(atomicWriteTempFileName, fileName);
}
8 changes: 7 additions & 1 deletion src/zooflm/citationcompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {

import { split_prefix_label } from '../util/index.js';
import { promisifyMethods } from '../util/prify.js';
import { writeFileAtomic } from '../util/atomicfilewrite.js';

import { Cache, one_day } from '../citationmanager/_cache.js';

Expand Down Expand Up @@ -399,7 +400,12 @@ export class CitationCompiler
{
const fsp = this.cache_fsp;
debug(`Saving compiled citations to cache file ‘${this.cache_file}’`);
await fsp.writeFile(this.cache_file, this.cache.exportJson());
await writeFileAtomic({
fsp,
fileName: this.cache_file,
data: this.cache.exportJson(),
processPid: (process != null) ? process.pid : 'XX',
});
}


Expand Down

0 comments on commit e8acda5

Please sign in to comment.