返回文章列表
techatomic-file-save-durability
⚙️

The Save Button Returned: Will the File Survive a Crash?

A desktop-editor failure laboratory separates complete writes, atomic replacement, persistence, concurrent revisions, and recovery across fourteen save boundaries.

iBuidl Research2026-10-2216 min 阅读

TL;DR: A successful write, an atomic replacement, and a durable save are different events. For a local-file application, define which failure the Save button promises to survive, then test the complete sequence on the supported operating systems and filesystems. A common Linux replacement pattern writes a temporary file, checks writes, synchronizes that file, renames it into place, and synchronizes the containing directory; every failure and concurrency boundary still needs an explicit policy.

The laboratory in this article belongs to a fictional desktop editor called Copperleaf. It edits a single structured project file. A user changes a drawing, clicks Save, sees a green confirmation, and loses power. On restart, the file exists but contains an older drawing. The support log says the write succeeded. The user's complaint and the log can both be accurate.

This is a failure-point investigation, not a universal filesystem recipe. The controlling references include the Linux manual pages for fsync and rename, and SQLite's explanation of atomic commit. Platform APIs, network mounts, storage devices, and document-provider services can impose different semantics. Copperleaf's experiments below illustrate how to turn those boundaries into a reviewable product contract.

Station one: Define the object that must survive

Before changing the implementation, the team writes down what a project consists of. In the simplest format, one file contains all drawing data. In the expanded format, a manifest references images in a neighboring directory. That difference changes the save problem: replacing one file atomically does not atomically publish a collection of separately written assets.

The team also distinguishes the current document in memory, the last acknowledged saved version, and any recovery draft. A recovery draft can be newer than the last explicit save, but the interface must not confuse it with a completed save to the user's chosen destination. The user may rely on that destination for sharing, backup, or opening the project elsewhere.

For the single-file format, Copperleaf's intended promise is that a successful local save preserves the newly acknowledged version across a process crash and, on supported storage under the documented assumptions, a system restart. It also promises that a failed replacement leaves a usable previous version whenever the platform permits that outcome.

This is stronger than “the operating system accepted some bytes.” It is also narrower than a guarantee against a failed drive, filesystem corruption, or a cloud provider losing its copy. The product's recovery design addresses those separately rather than hiding them inside a vague green checkmark.

Station two: Stop the process during an in-place overwrite

The original implementation opens the destination, truncates it, and writes the new document. A process termination after truncation but before completion leaves an empty or partial file. A careful serializer cannot prevent this; the destination itself is being modified before the replacement content is complete.

Copperleaf's test harness interrupts the process at several write boundaries. The harness records which bytes reached the file and whether the application had already displayed success. The aim is not to create an impressive collection of crash videos. It is to identify states from which the next launch can recover deterministically.

The team decides that readers should see either the previous complete document or the next complete document during normal replacement. That leads to a temporary-file strategy. The application constructs the replacement separately, validates its internal format, and only then attempts to make it the destination.

This choice does not automatically provide durability. It first addresses visibility and partial-content exposure. Separating the goals helps reviewers avoid approving a change that solves one failure mode while retaining another. An atomic-looking Save operation can still lose its newest version after a power failure if persistence was never established.

Station three: Fill the disk before the write finishes

A temporary file can fail to reach its intended length. Storage may fill, a quota may be exceeded, or an I/O error may occur. The application must check the results of its write operations and finalization steps rather than assuming that opening the file successfully guarantees the rest of the operation.

Copperleaf creates the temporary file on the intended destination filesystem and uses a unique name that cannot be confused with another active save. It writes the complete serialized content, verifies the expected length and format, and retains a meaningful error if any step fails. The previous destination remains the user's working file until publication succeeds.

A format checksum can help detect corruption or incomplete content, but it does not make unwritten bytes appear. Nor does a successful read immediately after writing prove that the data has reached durable storage; the read may be served from cached state. The test plan therefore separates content validation from persistence validation.

When a disk-full error occurs, the interface preserves the unsaved document in memory and offers a different destination where appropriate. It does not clear the dirty indicator merely because a temporary file exists. A user should be able to distinguish “your document is still open and unsaved” from “your new version replaced the original.”

Station four: Synchronize the replacement's contents

On Linux, fsync requests synchronization of a file's modified state with storage and reports failure if the operation cannot complete as required. Its documentation also makes clear that synchronizing the file does not necessarily synchronize the directory entry that names it. Those are separate parts of the save sequence.

Copperleaf invokes the appropriate persistence operation for its supported platform after completing the temporary file. It checks the return value. The application does not treat a failed synchronization as a harmless performance warning and proceed to claim a durable save. The new bytes may be readable while the promised persistence remains uncertain.

The team instruments this stage independently from serialization. For a large project, encoding time and storage synchronization time can have very different distributions. If users experience long saves, the team needs to know which part is responsible before changing batching or feedback behavior.

The interface may show “Writing” and then “Finishing save” for a sufficiently long operation, but it should avoid exposing low-level system-call names to ordinary users. The important distinction is between work in progress and a completed promise. A progress animation is helpful only if its completion point corresponds to the actual contract.

Station five: Replace the name readers use

The Linux rename documentation describes atomic replacement of an existing destination name under the applicable conditions. That gives readers a useful namespace property: they need not encounter a partially constructed new file merely because the writer is publishing a replacement. Open file descriptors and other links can still refer to earlier objects.

Copperleaf keeps the temporary file in the destination directory for its local replacement path. This avoids accidentally turning the operation into a cross-filesystem move, which has different constraints and may fail. A fallback that copies bytes over the original destination would reintroduce the in-place overwrite risk the team was trying to remove.

A reader that already opened the old file may continue reading the old object. A reader opening the destination after replacement can see the new one. That behavior is often desirable, but applications coordinating multiple processes must account for it. The filename is not a magical live pointer that rewrites every reader's existing file handle.

After the rename succeeds, Copperleaf records that the visible destination may already contain the new document even if a later step fails. Error handling must reflect that state. Reporting “nothing was saved” after successful replacement would be misleading; the more accurate outcome is that publication occurred but the requested durability confirmation did not complete.

Station six: Persist the directory change

The containing directory records the association between a name and a file. A save can synchronize the replacement's bytes and still need to persist the namespace change that made those bytes available under the destination name. On Linux, the fsync documentation specifically calls out the need to synchronize the directory separately.

Copperleaf's local Linux path therefore includes a checked directory synchronization after replacement. The team does not assume the same call sequence is portable to every operating system or filesystem. Supported platform implementations must be reviewed against their own APIs and tested under the product's actual storage configurations.

A failure at this late stage is awkward. The new version may be visible, and a subsequent normal read may succeed, while recovery after a sudden system failure remains uncertain. Copperleaf retains the document state and displays an actionable failure message rather than rolling back blindly. A rollback itself would be another filesystem mutation with its own failure possibilities.

The application log captures the stage reached and the destination identity without dumping document contents. That information lets support distinguish a write failure from a publication failure or an incomplete durability step. Collapsing all three into “save failed” would make diagnosis harder and encourage unsafe retry advice.

Station seven: Two saves race each other

The user edits again while a slow save is still running. The first save represents document revision 41; the second represents revision 42. If both complete independently, revision 41 could finish last and overwrite the newer state. Each individual replacement could be atomic and durable while the final result is still wrong.

Copperleaf serializes publication per document and associates each save with an immutable in-memory revision. A completed save acknowledges that revision, not whatever the editor happens to contain when the operation finishes. If the user has since changed the document, the dirty indicator remains set for the newer revision.

The team considers coalescing pending saves. If revisions 42 and 43 are waiting, it may be efficient to save only 43, provided the product does not promise separate historical versions for every click. That optimization must preserve any explicit export or versioning requests. A user-created snapshot is a distinct artifact, not an expendable intermediate autosave.

A second application process creates a harder race. Atomic replacement does not by itself coordinate independent writers. Copperleaf needs a chosen policy for external modifications: detect and ask, merge supported content, or use platform coordination mechanisms. The durability investigation does not replace that concurrency design; it makes clear which layer owns it.

Station eight: The save succeeds but the reply is lost

Suppose the file replacement and durability steps finish, but the process crashes before the interface receives the completion message. The destination contains revision 42, while the next launch only knows that the previous session ended unexpectedly. Retrying the same document content may be harmless, but the recovery interface still needs to identify what actually survived.

Copperleaf embeds a document revision identifier and format version in the saved content. On restart, it compares the destination with any recovery draft and recorded save intent. It can tell the user that the destination already contains the recovered revision instead of presenting two indistinguishable copies and asking for a blind choice.

The revision identifier is not evidence that every asset referenced by the document is present. For the single-file format, it helps identify content. For the multi-file format, the manifest must also describe the required asset set. A successful manifest parse cannot prove that all referenced files were durably published.

The team avoids an automatic “newest timestamp wins” rule. Filesystem modification times may have limited precision, be altered by copying, or reflect a different operation than the document's semantic revision. Recovery should compare meaningful content identities and preserve alternatives when certainty is unavailable.

Station nine: Add images and break the one-file assumption

The expanded Copperleaf format stores images separately. A new manifest references image B, but the image write fails before publication. Replacing the manifest now would create a structurally valid document with a missing dependency. The single-file save algorithm has reached the boundary of its responsibility.

One design writes immutable assets first and publishes a manifest only after all required assets are available under stable names. Another packages the document into one container. A third uses a database or transactional storage layer designed to coordinate the relevant changes. The right choice depends on editing patterns, project size, portability, and recovery requirements.

Copperleaf chooses immutable asset objects and a versioned manifest for this hypothetical format. Old manifests remain usable while new assets are prepared. Garbage collection removes unreferenced assets only after a retention period and a complete reference scan. Deleting an apparently unused image during an incomplete save could otherwise destroy the previous recoverable version.

This design still needs persistence ordering and failure tests. Immutability simplifies reasoning; it does not grant a multi-file transaction automatically. The acceptance condition is that every acknowledged manifest resolves to a complete asset set after the supported failure scenarios. The team tests that condition directly rather than assuming a collection of successful writes composes into one successful project save.

Station ten: A network mount enters the support matrix

A user saves to a network share. Another saves into a folder managed by a synchronization service. The local path looks ordinary in the file picker, but the end-to-end storage system now includes components outside Copperleaf's direct control. A local completion signal may not mean a remote copy is already durable or visible elsewhere.

Copperleaf documents which storage paths it supports for its strongest local-save promise. For synchronized folders, it distinguishes local save completion from provider upload status when the platform exposes reliable information. It does not infer remote synchronization merely because the file appears in a familiar directory.

Network failure tests include delayed acknowledgments, reconnects, and competing readers. The team consults the relevant filesystem and provider documentation rather than projecting local Linux semantics onto every mount. A support matrix should state tested behavior in concrete terms, including any reduced guarantees or required recovery steps.

The product can offer a local recovery copy even when the chosen destination is remote. That copy serves a separate purpose and should be clearly identified. Users must know which version is available locally and which version has reached the shared destination, especially when colleagues are waiting to open the project.

Station eleven: Recovery files become evidence

Temporary files left after a crash are not automatically garbage. Some may contain the only complete copy of a user's latest work. Copperleaf uses recognizable metadata inside recovery candidates and a controlled discovery process at startup. It does not delete every temporary-looking filename before examining whether it belongs to an interrupted save.

The recovery screen presents document identity, revision, destination, and a readable preview where possible. It offers to open recovered content without immediately overwriting the existing destination. That gives the user a reversible way to compare versions before choosing which one to retain.

Retention has a cost. Large abandoned projects can consume storage, so the application provides a managed cleanup policy with visible age and size information. Cleanup should run outside active save publication and should not race with a newly created temporary file. A file's name alone is not enough to establish that its writer is gone.

SQLite's atomic-commit discussion is a useful reminder that recovery depends on preserving the state required by the recovery protocol. Copperleaf's protocol is different, but the design lesson applies: well-intentioned cleanup can destroy evidence. Recovery artifacts should have documented ownership and lifecycle rules rather than being treated as incidental clutter.

Station twelve: A process kill is not a power failure

Terminating the editor tests application interruption. It does not necessarily discard the operating system's caches. Rebooting a virtual machine may test a different path from abruptly removing power from a physical storage device. A test report should state which failure was actually injected.

Copperleaf divides validation into layers. Unit-level fault injection makes each filesystem operation fail and checks the resulting state machine. Process-crash tests interrupt the application at selected boundaries. System-level experiments investigate persistence under the supported environment's crash model. Hardware-specific claims require corresponding evidence and cannot be inferred from a mocked filesystem.

The test oracle opens the destination after recovery, validates its format, identifies the revision, and checks all required assets. It also examines whether the application had acknowledged success before the failure. The central question is whether the observed recovered state matches the promise made at that acknowledgment point.

The team keeps the test environment description with the results: operating system, filesystem, storage path type, relevant mount settings, and device configuration where material. This is not paperwork for its own sake. A durability result without its environment can be misapplied to a configuration that provides different guarantees.

Station thirteen: Performance pressure reaches the green checkmark

Synchronization can be expensive, particularly when frequent autosaves compete with other storage work. The product team asks whether the editor can show success earlier and finish persistence in the background. That would improve the visible latency by changing the meaning of success, so it requires an explicit product decision.

Copperleaf instead distinguishes a saved local revision from a draft queued for persistence. It can debounce autosaves, coalesce pending revisions, and show quiet progress while keeping the stronger acknowledgment tied to completion. Explicit user saves receive priority without allowing older queued work to overwrite them afterward.

For a fictional workload, suppose serialization takes 40 milliseconds, writing takes 60, and synchronization takes 180. The total is 280 milliseconds before other overhead. Reducing serialization by half saves 20 milliseconds, while removing synchronization appears to save 180 but also removes part of the promised behavior. A performance proposal should identify which category of improvement it offers.

The team measures tail latency as well as the median because users are most likely to close a laptop or quit during an unusually slow save. The interface keeps the current state visible and handles close requests deliberately. A fast average does not justify abandoning an in-progress document without a clear decision.

Station fourteen: Read the final failure map

At the end of the investigation, Copperleaf has a state map with separate milestones: replacement content complete, replacement content synchronized, destination published, directory update synchronized, and revision acknowledged. Each transition has an owner, an error result, and a recovery interpretation. The map is small enough for a reviewer to inspect in one sitting.

A separate review checks metadata preservation. Replacing a file can affect permissions, ownership, extended attributes, or other platform-specific properties if the new object does not inherit the intended values. Copperleaf lists which properties are part of its document contract and verifies them explicitly. Preserving bytes while unexpectedly changing who can open the document would still be a failed save experience. The exact mechanism belongs to the platform implementation, with errors treated as part of publication rather than ignored decoration.

The team also checks cancellation. Before publication, a user cancellation can normally abandon the candidate and retain the previous destination. After publication, cancellation cannot truthfully mean that no change occurred. The interface therefore treats a late cancellation as a request to stop subsequent optional work, while completing or reporting the required persistence steps already associated with the published version. This prevents a button labeled Cancel from creating a new ambiguous recovery state at precisely the most sensitive point in the sequence.

The Save button now corresponds to the last required milestone for the chosen storage path. If a late persistence step fails after publication, the interface describes the uncertainty accurately and preserves recovery options. If a user continues editing during a save, acknowledgment applies only to the saved revision. If the project contains multiple assets, its manifest is published only after its dependencies are ready.

The tests support a bounded claim: the implementation survived the specified injected failures on the listed configurations. They do not claim immunity to every storage fault. That boundary makes the evidence useful, because future platform changes can be evaluated against the same explicit promise.

A trustworthy save experience comes from aligning the visible acknowledgment with the actual sequence that protects the user's work. Atomic replacement is one necessary tool in that sequence. Durability, concurrency, recovery, and truthful interface state complete the engineering problem.

Sources

更多文章