Files
OrcaSlicer/tests
Hanif Koh af97d59870 Lock Config and Preset Files Across Instances and Write Them Atomically
Every running instance shares one OrcaSlicer.conf and one user preset
tree, and nothing kept their writers apart. Two instances saving at the
same moment, or the cloud preset sync thread writing while the GUI thread
saved, could interleave, and a reader in another instance could open a
preset JSON or .info file between truncate and close and get a partial
file, dropping that preset for the session with a parse error.

Add InstanceLock, a scoped guard that serialises the threads of one
process through a recursive mutex and other processes through an advisory
OS file lock: flock on POSIX, held on the guard's own descriptor so no
other close in the process can drop it, and LockFileEx on Windows. The
outermost guard opens the lock file and closes it on release, so nothing
stays open between saves and a data dir can be removed once nothing is
saving into it; the file itself is kept, since deleting it would let a
third instance lock a fresh file while the second still holds the old
one. It is best effort: when the lock file cannot be opened or locked, or
another instance still holds it after a second, the guard logs once and
lets the write proceed, then leaves the file alone for ten seconds, so
a hung instance never blocks every other one and a holder stuck in a
debugger does not cost a stall per save. The
guard sits at the leaf readers and writers: set_sync_info_and_save()
calls save_info() under the preset collection mutex, so a batch lock
around save_user_presets() would invert the order against the sync
thread. The preset scan re-takes the guard every 32 files rather than
holding it across the scan, and a bundle or user folder is renamed into
cache/ under the lock and removed outside it, so a save never waits for
a whole tree. Read-only scans, which is what the CLI does, take no lock
and create no lock file.

AppConfig holds OrcaSlicer.conf.lock in load() and save(); load is
included because the Windows path restores from the .bak copy. Every
user preset writer and reader holds user.lock: Preset::save(), which
writes no .info when the preset itself could not be written, since an
.info without its preset reads as a cloud deletion request, save_info(),
reload() and remove_files(), each file read by the preset scan, the
bundle metadata reads and write, the .info removal after a cloud-confirmed
delete, the orphaned-.info scan on the sync thread, the bundle folder
removal on unsubscribe and the physical printer writers and delete
paths. A bundle import extracts under cache/ into a folder per process
and per import, where no scan reads.

Preset JSON, .info, bundle metadata, physical printer and config files,
and the caches and state files that already used a temporary by hand,
now go through write_file_atomically(), which writes <file>.<pid>.<n>.tmp
beside the target and renames it over, so a reader that never waits sees
a complete old or new file. A target this process may not write is
refused before anything is written, unless the caller says the file was
always replaced, as the config was; a symlink is followed;
a target that is not a regular file is written in place; and when the
rename itself is refused, by a Windows reader holding the file open or
a mount that cannot replace in one step, the helper writes in place as
before, since losing the save is worse than a torn read. On POSIX the
rename replaces the target atomically where the old code removed it
first and left a window with no file at all; only a mount that refuses a
one-step replace gets the old remove-then-rename. A crash between
temporary and rename leaves the temporary,
which the scans of the directories the application owns remove once it
is an hour old; only the exact shapes this code writes qualify, so a
user's numbered backup or an export folder is never touched. A failed
config write keeps the config dirty, and the idle handler waits ten
seconds before retrying while an explicit save always tries.
2026-09-25 06:08:54 +08:00
..
2025-12-08 22:42:11 +08:00

OrcaSlicer tests

Building, running and writing tests is documented on the wiki, under How to Test.

Two files here rather than there, because coding agents only read what is in the repository:

  • AGENTS.md is the same guidance in short form, and is what an agent working under tests/ picks up.
  • CATCH2.md is the Catch2 reference, including the mistakes that break a test at runtime.