1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! Read-only, lock-free view of a [`RecordLog`](crate::RecordLog) directory.
//!
//! [`RecordLogReader`] is the lightweight counterpart of [`RecordLog`]:
//!
//! - It opens a log directory **without** taking the cooperative
//! single-writer lock (`<dir>/.lock`). Multiple readers may coexist with
//! each other and with a live writer in the same or a different
//! process.
//! - It snapshots the set of segment ids at construction time. Segments
//! created or rotated in after [`RecordLogReader::open`] returns are
//! **not** observed by this handle. Reopen the reader to pick them up.
//! - It exposes one method, [`RecordLogReader::scan_iter`], which yields
//! records lazily one at a time. The returned [`RecordIter`] decodes
//! one segment file at a time; peak memory is bounded by the size of
//! the **largest** segment in the snapshot.
//!
//! # What this is for
//!
//! Observing the contents of a log that is being written to by another
//! handle (in the same or a different process), without disturbing the
//! writer. Typical uses:
//!
//! - An out-of-band inspector / dashboard.
//! - A tailing process that periodically re-opens and scans the tail.
//! - A test harness that wants to compare on-disk state against an
//! in-memory oracle while the writer keeps appending.
//!
//! # What this is not
//!
//! - **Not** a live tail. The snapshot is taken at `open()` time and is
//! not refreshed by the iterator. Reopen to widen the snapshot.
//! - **Not** a concurrent reader for the *same process's* writer
//! instance. The writer's [`RecordLog`] already exposes
//! [`RecordLog::scan_iter`] for that purpose; that variant snapshots
//! the segment list the same way and borrows immutably.
//! - **Not** a substitute for the writer's recovery. The reader does
//! *not* run [`RecordLog`]'s longest-valid-prefix recovery into
//! `next_txid` or update any persistent state; it only decodes the
//! bytes it finds.
//!
//! # Concurrency semantics with a live writer
//!
//! The wire format is a series of self-delimiting frames with a 24-byte
//! header, key + payload bytes, and a 4-byte CRC trailer. A reader that
//! opens after the writer has appended some records sees exactly those
//! frames; a reader that opens **before** the writer's next append
//! still sees only the frames that were present at open time *plus*
//! any bytes that happened to be on disk when each segment file is
//! loaded.
//!
//! In practice: the iterator loads one full segment file into memory
//! when it advances to that segment. A frame whose **header** is on
//! disk but whose **body or CRC** is not yet on disk shows up to the
//! reader as a tail-truncated or CRC-mismatched record on the **last**
//! segment of the snapshot. The iterator treats this as a clean end of
//! the active segment (consistent with [`RecordLog::scan_iter`]) and
//! reports the discarded byte count via
//! [`RecordIter::recovery_report`]. Sealed (non-last) segments are
//! treated strictly: any truncation or CRC mismatch there is a hard
//! error, because the writer should never leave a sealed segment in a
//! bad state.
//!
//! The reader does not coordinate with the writer's `fsync`. A record
//! that the writer has appended but not yet fsynced may already be
//! visible to a reader because the OS page cache makes it readable
//! through a fresh `open()`, but the same record is *not yet durable*
//! across a crash. The durability contract belongs to the writer.
//!
//! # Examples
//!
//! ```no_run
//! use datawal::RecordLogReader;
//!
//! let reader = RecordLogReader::open(std::path::Path::new("/var/lib/my-app/log"))?;
//! let mut count = 0u64;
//! for rec in reader.scan_iter()? {
//! let rec = rec?;
//! count += 1;
//! let _ = rec;
//! }
//! println!("snapshot contained {count} records");
//! # Ok::<(), anyhow::Error>(())
//! ```
use ;
use ;
use crateRecordIter;
use cratelist_segment_ids;
/// Read-only, lock-free handle on a [`RecordLog`](crate::RecordLog) directory.
///
/// See the documentation on each method for the full contract: snapshot
/// at open, no fs2 lock, one-segment-at-a-time decode, tail truncation on
/// the last segment is tolerated, mid-stream errors on sealed segments
/// are hard errors.