Source code for signal_dataset.storage.backends.local.capabilities

"""What a filesystem under the local backend can actually do.

A mounted bucket -- gcsfuse, or Mountpoint for Amazon S3 -- presents a path
that looks local and is not. The gap is narrow and specific: a handful of
syscalls the backend leans on for atomic publication are absent. Naming them
individually, rather than adding a "mount mode" flag, keeps each branch honest
about which guarantee it is trading away, and lets the whole mount path be
exercised in CI against an ordinary directory.

This is a declaration, not a measurement. Nothing probes the filesystem: a
caller says what it has, through `capabilities=` or the
``SIGNAL_DATASET_MOUNT_ROOTS`` environment variable. Probing would mean
degrading silently on any filesystem that refused a link for an unrelated
reason, which is exactly the "appears to support it" behaviour worth avoiding.
"""

from __future__ import annotations

from dataclasses import dataclass


[docs] @dataclass(frozen=True, slots=True, kw_only=True) class FilesystemCapabilities: """Which primitives the filesystem beneath a local root provides. hard_links ``link(2)``. The backend publishes by writing a temporary file and linking it to its final name, which is atomic and fails if the name is taken -- create-only semantics for free. gcsfuse and Mountpoint implement no hard links at all. file_locks ``flock(2)``. The only compare-and-swap in the format guards ``annotations/catalog.json``, the one mutable object, and it needs a critical section. directory_fsync ``fsync`` on a directory descriptor, which is what makes a newly linked name durable. A mount rejects it, and commits on ``close`` regardless. atomic_rename ``rename(2)`` replacing an existing name in one step. On a mounted bucket this is copy-then-delete, so a reader can observe the target missing. stable_inodes ``(st_dev, st_ino)`` identifying a directory across opens. A mount synthesizes inode numbers, so the identity check neither detects a replaced root nor is safe to rely on. """ hard_links: bool file_locks: bool directory_fsync: bool atomic_rename: bool stable_inodes: bool
#: An ordinary POSIX filesystem. The default, and what every existing caller gets. POSIX = FilesystemCapabilities( hard_links=True, file_locks=True, directory_fsync=True, atomic_rename=True, stable_inodes=True, ) #: A bucket mounted through gcsfuse or Mountpoint for Amazon S3. #: #: Publication still works, and a dataset written this way is byte-identical to #: one written natively. What is lost is that creation stops being #: all-or-nothing: the name appears when the file is opened and the bytes #: arrive when it is closed, so an interrupted writer can leave an empty object #: at a final name. Annotations are refused outright, because their catalog #: update needs a lock the mount does not provide. MOUNTED_BUCKET = FilesystemCapabilities( hard_links=False, file_locks=False, directory_fsync=False, atomic_rename=False, stable_inodes=False, )