atomicwrite

package
v0.1.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 27, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package atomicwrite provides the tmp-file-then-rename helper the scanner and enricher both use to commit cached JPEGs to disk without a concurrent reader ever observing a torn file.

Before this package the same pattern lived twice — once in `internal/enrich/enricher.go::writeArtworkAtomic`, once in `internal/manifest/extractors.go::writeArtworkAtomicScan` — with the only meaningful difference being the tmp-file prefix (`.caa-*.jpg.tmp` vs `.scan-*.jpg.tmp`), which lets operators tell from a stale `.tmp` left on disk WHICH side was the writer. Consolidating preserves that diagnostic shape via the `TmpPrefix` option.

The streaming-write variant (`internal/enrich/enricher.go::writeArtworkAtomicStream`) is NOT covered here — that path carries additional invariants (size cap via `io.LimitReader`, zero-byte-refusal, MIME-detection hand-off to the JPEG sniff) that the buffered shape doesn't need. A future refactor could absorb it; for now the buffered helper is what's load-bearing-duplicated.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RenameWithRetry

func RenameWithRetry(src, dst string) error

RenameWithRetry retries `os.Rename` to absorb the transient "Access is denied" / sharing-violation Windows produces under the tmp-file-then-rename pattern. Concurrent scanner workers writing the same content-hash also race here.

Caller is responsible for post-failure semantics — typically the "stat the destination and accept if its bytes match what we tried to write" fallback that `WriteBytes` implements.

func SetRenameFuncForTest

func SetRenameFuncForTest(fn func(src, dst string) error) func(src, dst string) error

SetRenameFuncForTest swaps the rename implementation. Tests `defer atomicwrite.SetRenameFuncForTest(os.Rename)` to restore the production behaviour. Returns the previous value so a test can chain a restore via the standard pattern:

prev := atomicwrite.SetRenameFuncForTest(failOnce)
defer atomicwrite.SetRenameFuncForTest(prev)

Public only because tests in `internal/enrich` and `internal/manifest` both need access; the variable itself stays unexported so accidental package-level mutation is impossible.

func WriteBytes

func WriteBytes(path string, data []byte, tmpPrefix string) error

WriteBytes writes `data` to `path` via tmp-file + rename so a concurrent reader never sees a torn file. The tmp file is created in the SAME directory as `path` so the rename is a same-filesystem atomic operation (cross-FS renames degrade to copy-and-delete, which is racy AND breaks Windows' atomic rename guarantee).

`tmpPrefix` is the `os.CreateTemp` prefix used to name the scratch file. The caller picks a prefix that identifies the caller-side writer (`.caa-*.jpg.tmp` for enricher, `.scan-*.jpg.tmp` for scanner) so a stale tmp on disk after a crash tells operators which subsystem failed.

Cache directory perms are 0o700 (owner-only) — application- owned caches shouldn't be world-readable on POSIX. The MkdirAll call is idempotent; existing dirs with looser perms from prior deployments stay at their previous mode (operators upgrade by `rmdir`-ing the cache).

On rename failure the function reads the existing destination and accepts the write as already-committed if the bytes match `data`. Same rationale the original `writeArtworkAtomic` / `writeArtworkAtomicScan` carried: a concurrent writer of the SAME bytes (mbid-keyed cache, content-hash-keyed scanner path) is functionally indistinguishable from our completed write, and failing the operation just to surface the race would force the caller into a retry loop that performs the same comparison.

Per-error specifics are surfaced unchanged to the caller (we return the original `os.Rename` error if byte-equality also fails) so the caller can route on `errors.Is` / `os.IsNotExist` as appropriate.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL