SOFTWARE / SYSTEMS / AIEngineering news. Technical depth.
Architecture / 3 MIN READ

How do we update a memory-mapped file safely when truncation, writer races, and partial persistence can crash readers?

Safe mmap updates depend on avoiding live truncation, isolating readers from in-place mutation, and defining a real commit boundary for visibility and durability.

Treat a mapped file like a shared on-disk data structure, not a convenient byte array. If readers must not crash or see torn state, do not resize or mutate the live file in place. The safest design is write a new file, flush it, and replace the pathname atomically; the next best option is append-only data with a versioned root or index written last. Use in-place updates only when you can define retryable reader behavior and an explicit commit marker.

Why is truncation the first thing to ban?

Resizing under active mappings breaks the contract you want. POSIX says that if the mapped file changes size after mmap(), access to mapped bytes corresponding to added or removed file portions has unspecified effects (Open Group mmap specification). Linux documents the same family of visibility rules and notes that shared mappings propagate updates to the file, with msync(2) needed when you need precise control of writeback timing (Linux mmap(2)).

On Windows, mapping size must be chosen up front: the documentation says file mapping objects are static in size once created (Microsoft Creating a File Mapping Object). That makes the decision straightforward: never shrink a file that existing readers may still have mapped, and do not rely on growing an already-published mapping to extend a live structure safely.

What update pattern gives readers a real safety boundary?

Prefer immutable replacement. Write the next generation to a temporary file, including a header with magic, format version, declared length, and checksum. Flush file contents, then atomically replace the published pathname. Readers reopen, check size with fstat or equivalent, map the file, validate the header, and only then switch to the new generation.

That pattern gives a real ordering boundary: payload first, publication second. Readers either use the old complete file or the new complete file.

If full replacement is too expensive, use append-only layout plus a small committed root record. The key is still ordering: write new payload pages first, flush them, then write the new root offset or generation field last. A check-then-write protocol without that final publication boundary does not prevent readers from following half-built pointers.

What does msync() or a view flush actually guarantee?

Visibility is not durability. On Linux, MAP_SHARED means updates are shared and carried through to the underlying file, but mmap(2) explicitly points you to msync(2) for precise control (Linux mmap(2)). The msync(2) page says it flushes changes from the in-core copy back to the filesystem, and without it there is no guarantee changes are written back before munmap(2) (Linux msync(2)).

That still does not magically make your format crash-safe. You need both a flush sequence and a recoverable layout. In practice:

  • mapped writes make data visible;
  • msync(MS_SYNC) or the Windows view-flush call pushes dirty mapped pages;
  • a file-level flush is still the boundary for durable file state and metadata.

What should every mapped-file reader validate?

Use this checklist:

  • fixed header: magic, schema version, file length, generation;
  • bounds-check every offset before dereference;
  • commit marker, root pointer, or generation written last;
  • checksum for committed region or footer;
  • remap only after reopening and re-reading current size;
  • startup recovery rejects incomplete generations.

Follow-up Q&A

Can I safely patch a few fields in place?
Yes, but only if each field update is independently valid or readers can detect an in-progress state and retry using version counters or state flags.

When should we avoid mmap entirely?
Skip it when files resize frequently, update visibility must be tightly staged, or crash consistency spans multiple records. Buffered I/O with an explicit commit log is easier to reason about.

Next step: write a one-page reader contract that states exactly what readers may observe during updates—old snapshot, new snapshot, or retry—and then choose replacement, append-only, or in-place format rules to match it.

Reviewed: 2026-09-05.

SOURCES & REVIEW

Sources are linked throughout this guide. Product capabilities can change; consult the linked documentation for your deployment.

Read our editorial approach ↗