|
ravel
Deterministic simulation testing for C++. Seed a bug, replay it exact.
|
A virtual disk holding named files, with the failure behavior that makes storage code hard to get right. More...
#include <disk.hpp>
Classes | |
| class | Operation |
| An operation in flight; co_await it to get its result. More... | |
Public Member Functions | |
| Disk (DiskId id, std::string name, DiskFaultSpec fault, Scheduler &scheduler, VirtualRng &rng, Trace &trace) | |
| Created by Simulation::add_disk; you do not construct one yourself. | |
| Disk (const Disk &)=delete | |
| Pending operations refer to this object, so it must never move. | |
| Disk & | operator= (const Disk &)=delete |
| const std::string & | name () const noexcept |
| The name given to add_disk. | |
| const DiskFaultSpec & | fault () const noexcept |
| The fault settings this disk was created with. | |
| Operation< DiskStatus > | write (std::string path, std::uint64_t offset, std::string data) |
Writes data at offset, creating the file if it does not exist and growing it (with zero bytes) if needed. | |
| Operation< ReadResult > | read (std::string path, std::uint64_t offset, std::uint64_t length) |
Reads up to length bytes at offset, seeing every completed write. | |
| Operation< DiskStatus > | sync (std::string path) |
| Makes every write to the file that completed before this call was issued durable, and the file's creation (see above). | |
| Operation< DiskStatus > | rename (std::string from, std::string to) |
Renames a file, replacing to if it exists. NotFound if from does not. | |
| Operation< DiskStatus > | remove (std::string path) |
| Removes a file. NotFound if it does not exist. | |
| Operation< DiskStatus > | sync_dir (std::string dir) |
Makes durable every directory change, made before this call was issued, up to the last one that touched dir (a file created, renamed into or out of it, or removed from it). | |
| Operation< ListResult > | list (std::string dir) |
The entries directly inside dir. | |
| void | crash () |
| Power loss. | |
| std::uint64_t | crash_count () const noexcept |
| How many times crash() has been called. | |
| std::uint64_t | file_size (const std::string &path) const |
| The file's size as reads see it (0 if it does not exist). | |
| std::string | durable_contents (const std::string &path) const |
| The file as a crash right now would leave it at best: only durable data, and empty unless the file's name is durable too. | |
| bool | durable_exists (const std::string &path) const |
| Whether the file's name would survive a crash right now. | |
| std::uint64_t | used_bytes () const |
| Total bytes of all files, as reads see them. | |
Static Public Attributes | |
| static constexpr std::size_t | kSectorSize = 512 |
| A write torn by a crash is cut at a multiple of this many bytes. | |
A virtual disk holding named files, with the failure behavior that makes storage code hard to get right.
Data. Like a real disk with a page cache, a write is visible to reads at once but is only durable after sync(path). crash() models power loss: every write that was not yet synced independently either vanishes, survives whole, or survives only in part (a torn write, cut at a 512-byte sector boundary).
Names. Creating a file, renaming it and removing it are visible at once too, but each changes the directory only in memory until it is made durable. Directory changes reach the disk in the order they were made, so a crash keeps some prefix of the pending ones (possibly none). Two calls make them durable:
The outcome of each crash decision is a random choice, and the simplest outcome (what shrinking steers toward) is always that the change is lost.
co_await disk.write("wal/log", 0, "record"); co_await disk.sync("wal/log"); // "record" now survives crash()
Paths use '/' as the separator and have no leading slash. Directories exist implicitly: a directory is there as long as a file is under it, and "" is the root. All operations are awaited and take virtual time. No real files are touched.
| void ravel::Disk::crash | ( | ) |
Power loss.
Applies the crash rules above to unsynced writes and directory changes, and fails every operation still in flight with DiskStatus::Crashed. Instant: call it from a task, or from an invariant to inspect the aftermath.
|
inlinenoexcept |
How many times crash() has been called.
A process that a crash kills can read this before starting and stop once it changes: an operation that completed just before the crash is not enough to keep going.
| Operation< ListResult > ravel::Disk::list | ( | std::string | dir | ) |
The entries directly inside dir.
NotFound if dir has none (except the root, which may be empty).
| Operation< ReadResult > ravel::Disk::read | ( | std::string | path, |
| std::uint64_t | offset, | ||
| std::uint64_t | length | ||
| ) |
Reads up to length bytes at offset, seeing every completed write.
NotFound if the file does not exist.