Class AtomicConfigWriter
Normally the new UTF-8 text is written to a same-directory temporary file with the target's POSIX
permissions, forced to disk, and atomically moved over the target. Eligible atomic-replace failures
(unsupported atomic moves, EBUSY or EXDEV) and temporary-creation permission/read-only refusals use a
narrow fallback: exclusively create a backup named <file name>.ultitools-backup-<16 lower-case hex>,
copy and force the old content, then overwrite and force the existing target in place. A backup this server
run already recorded for the same target, whose bytes still match the record, is refreshed through a forced
same-directory temporary and atomic replacement before the target is opened. A complete forced backup
remains until the next successful strict UTF-8 load, which deletes it only while its bytes still match what
the writer recorded; any other file of that pattern is kept and named once at INFO. Each fallback attempt
logs its path, cause and outcome.
Why it cannot overwrite operator content. The writer reads, writes or deletes only the target it was
given and names of its own two patterns, <file name>.tmp-<16 lower-case hex> and the backup pattern
above; an operator's <file>.bak, or any other name, is never touched (UltiKits/UltiTools-Reborn#601).
Before the in-place target is opened, failures leave its bytes unchanged. Once it is open, a failure (a write
that stops part-way, a failed force or close) puts the target back before the failure is reported: the bytes the
target held immediately before - exactly what the forced backup holds - are written back in place, the same way,
because the atomic move this fallback stands in for was refused; the backup is then removed, so the target holds
its previous bytes when the IOException reaches the caller (UltiKits/UltiTools-Reborn#622). If putting it
back fails too, one SEVERE names the target and the backup (never content), the backup is kept and no later load
deletes it, and the failure is a AtomicConfigWriter.RestoreFailedException: the target may hold part of the new content. A
symbolic link stays a link: writes and backup cleanup use its resolved target.
Stale temporary files are cleaned on load; live staged writes are protected. The atomic path copies only
POSIX permission bits, not ownership, ACLs or extended attributes. Directory syncing is best effort.
This is a framework-internal writer for config files; crash-safe writes of a module's own files are a
separate, later feature (UltiKits/UltiTools-Reborn#545).
- Since:
- 6.3.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescription(package private) static interfaceThe file-system steps of a write and a load, as a seam for fault-injection tests.private static final classA backup this server run wrote, and the SHA-256 of the bytes it wrote there.static final classThrown when an in-place write failed after the target was opened and putting the target back from its framework backup failed too (#622): the target may hold part of the new content, and the backup holding its previous content is kept.static final classA temporary file written beside its target and not yet moved over it. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final Stringprivate static final Map<Path, AtomicConfigWriter.RecordedBackup> The backup this server run created for each target (resolved path), with its SHA-256; guarded by WRITE_LOAD_LOCK.private static final AtomicConfigWriter.FileOperations(package private) static final AtomicConfigWriter.FileOperationsThe real file operations, forConfigDocument.load(Path).private static final LoggerBackups of the framework's pattern already named at INFO this server run; guarded by WRITE_LOAD_LOCK.private static final SecureRandomprivate static final Stringprivate static final Pattern(package private) static final ObjectSerializes replacement and successful-load backup cleanup within this JVM. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static booleanatomicReplacementRefused(IOException failure) (package private) static voiddeleteBackupAfterLoad(Path destination, AtomicConfigWriter.FileOperations files) After a successful strict load: deletes the backup this server run recorded fordestinationwhile its bytes still match the record, and names once at INFO every other file of the backup pattern beside it (left by an earlier run, or changed since it was written), which is kept.private static voiddeleteQuietly(Path temporary) private static voiddeleteRecorded(Path destination, Path backup, AtomicConfigWriter.FileOperations files) (package private) static voiddeleteStaleTemporaries(Path target) Deletes the temporary files an interrupted write left besidetarget: only names of the exact form<target file name>.tmp-<16 lower-case hex digits>.(package private) static booleanisBackupOf(String fileName, String candidate) Whethercandidatehas the exact form<fileName>.ultitools-backup-<16 lower-case hex>.(package private) static booleanisTemporaryOf(String fileName, String candidate) private static StringrandomName(String fileName, String infix) private static void(package private) static PathThe file actually replaced: the target itself, or the file an existing symbolic link points to.Asstage(Path, String), for content that is not UTF-8 text: the bytes are written exactly as given.(package private) static AtomicConfigWriter.StagedWritestage(Path target, byte[] content, AtomicConfigWriter.FileOperations files) Writestextto a temporary file besidetargetwithout touching the target, so several files can be prepared before any of them is replaced.(package private) static AtomicConfigWriter.StagedWritestage(Path target, String text, AtomicConfigWriter.FileOperations files) private static voidsyncDirectory(Path directory) private static booleantemporaryCreationRefused(IOException failure) private static PathtemporaryIdentity(Path path) Identifies a reserved file through its real parent, even before the file exists.(package private) static StringtemporaryName(String fileName) static <T> TunderWriteLock(Supplier<T> section) Runssectionwhile holding the lock every configuration load, last-moment check and replacement in this JVM holds.static voidReplacestargetwithtext, encoded as UTF-8.(package private) static voidwrite(Path target, String text, AtomicConfigWriter.FileOperations files) private static voidwriteAll(AtomicConfigWriter.FileOperations files, FileChannel channel, byte[] data)
-
Field Details
-
LOGGER
-
RANDOM
-
TEMPORARY_INFIX
- See Also:
-
BACKUP_INFIX
- See Also:
-
TEMPORARY_SUFFIX
-
LIVE_TEMPORARIES
-
WRITE_LOAD_LOCK
Serializes replacement and successful-load backup cleanup within this JVM. -
BACKUPS
The backup this server run created for each target (resolved path), with its SHA-256; guarded by WRITE_LOAD_LOCK. -
NAMED_LEFTOVERS
Backups of the framework's pattern already named at INFO this server run; guarded by WRITE_LOAD_LOCK. -
FILES
-
FILES_FOR_LOAD
The real file operations, forConfigDocument.load(Path).
-
-
Constructor Details
-
AtomicConfigWriter
private AtomicConfigWriter()
-
-
Method Details
-
underWriteLock
Runssectionwhile holding the lock every configuration load, last-moment check and replacement in this JVM holds. A writer outside this package that compares a file's bytes and then replaces them (the panel'sfiles.write) runs both steps here, so a configuration commit can never land between its comparison and its replacement, and its replacement can never land between a configuration commit's re-read and its rename: one of the two sees the other's bytes and refuses. The section must not block on anything but the file system.- Type Parameters:
T- the section's result type- Parameters:
section- the compare-and-replace to run- Returns:
- what
sectionreturned - Since:
- 6.3.1
-
write
Replacestargetwithtext, encoded as UTF-8.- Parameters:
target- the file to replace; its parent directory must existtext- the complete new content- Throws:
IOException- if a step fails; the target then holds its previous bytes, except after aAtomicConfigWriter.RestoreFailedException, when its complete backup remains beside it
-
write
static void write(Path target, String text, AtomicConfigWriter.FileOperations files) throws IOException - Throws:
IOException
-
stage
Writestextto a temporary file besidetargetwithout touching the target, so several files can be prepared before any of them is replaced. The caller mustcommitordiscardit, and may load the target in between; in-process staged files are protected from stale cleanup. If temporary creation is refused for permissions or a read-only file system, staging keeps the UTF-8 bytes in memory without touching the target or backup. Commit attempts the backed in-place path.- Parameters:
target- the file to replace later; its parent directory must existtext- the complete new content- Returns:
- the staged write
- Throws:
IOException- if the temporary file cannot be written; nothing is then left behind
-
stage
static AtomicConfigWriter.StagedWrite stage(Path target, String text, AtomicConfigWriter.FileOperations files) throws IOException - Throws:
IOException
-
stage
Asstage(Path, String), for content that is not UTF-8 text: the bytes are written exactly as given. Used for a file whose reader is not UTF-8 only (server.properties, which the server reads as ISO-8859-1 when it is not valid UTF-8), so its other lines can keep their bytes.- Parameters:
target- the file to replace later; its parent directory must existdata- the complete new content- Returns:
- the staged write
- Throws:
IOException- if the temporary file cannot be written; nothing is then left behind
-
stage
static AtomicConfigWriter.StagedWrite stage(Path target, byte[] content, AtomicConfigWriter.FileOperations files) throws IOException - Throws:
IOException
-
deleteStaleTemporaries
Deletes the temporary files an interrupted write left besidetarget: only names of the exact form<target file name>.tmp-<16 lower-case hex digits>. Each deletion is logged at FINE. Failures are ignored; the next load tries again.- Parameters:
target- the config file about to be loaded
-
release
-
temporaryIdentity
Identifies a reserved file through its real parent, even before the file exists.- Throws:
IOException
-
isTemporaryOf
-
temporaryName
-
isBackupOf
Whethercandidatehas the exact form<fileName>.ultitools-backup-<16 lower-case hex>. -
randomName
-
temporaryCreationRefused
-
atomicReplacementRefused
-
writeAll
private static void writeAll(AtomicConfigWriter.FileOperations files, FileChannel channel, byte[] data) throws IOException - Throws:
IOException
-
deleteBackupAfterLoad
After a successful strict load: deletes the backup this server run recorded fordestinationwhile its bytes still match the record, and names once at INFO every other file of the backup pattern beside it (left by an earlier run, or changed since it was written), which is kept. Nothing else is read or deleted. Failures are logged at FINE; the next load tries again. Called underWRITE_LOAD_LOCK.- Parameters:
destination- the loaded file (a symbolic link already resolved)files- the file operations
-
deleteRecorded
private static void deleteRecorded(Path destination, Path backup, AtomicConfigWriter.FileOperations files) -
resolve
The file actually replaced: the target itself, or the file an existing symbolic link points to.- Throws:
IOException
-
deleteQuietly
-
syncDirectory
-