|
1 | 1 | use std::{ |
2 | 2 | cmp, |
3 | | - fmt::Debug, |
| 3 | + fmt::{self, Debug}, |
4 | 4 | io::{self, Seek as _, SeekFrom}, |
5 | | - iter::repeat, |
| 5 | + iter::{self, repeat}, |
| 6 | + num::NonZeroU16, |
6 | 7 | sync::RwLockWriteGuard, |
7 | 8 | }; |
8 | 9 |
|
9 | 10 | use log::debug; |
| 11 | +use pretty_assertions::assert_matches; |
10 | 12 |
|
11 | 13 | use crate::{ |
12 | 14 | commitlog, error, payload, |
13 | 15 | repo::{self, Repo, SegmentLen}, |
14 | | - segment::FileLike, |
15 | | - tests::helpers::enable_logging, |
| 16 | + segment::{self, FileLike}, |
| 17 | + tests::helpers::{enable_logging, fill_log_with}, |
16 | 18 | Commit, Encode, Options, DEFAULT_LOG_FORMAT_VERSION, |
17 | 19 | }; |
18 | 20 |
|
@@ -140,6 +142,42 @@ fn overwrite_reopen() { |
140 | 142 | ); |
141 | 143 | } |
142 | 144 |
|
| 145 | +/// Edge case surfaced in production: |
| 146 | +/// |
| 147 | +/// If the first commit in the last segment is corrupt, creating a new segment |
| 148 | +/// would fail because the `tx_range` is the same as the corrupt segment. |
| 149 | +/// |
| 150 | +/// We don't automatically recover from that, but test that `open` returns an |
| 151 | +/// error providing some context. |
| 152 | +#[test] |
| 153 | +fn first_commit_in_last_segment_corrupt() { |
| 154 | + enable_logging(); |
| 155 | + |
| 156 | + let repo = repo::Memory::new(); |
| 157 | + let options = Options { |
| 158 | + max_segment_size: 512, |
| 159 | + max_records_in_commit: NonZeroU16::new(1).unwrap(), |
| 160 | + ..<_>::default() |
| 161 | + }; |
| 162 | + { |
| 163 | + let mut log = commitlog::Generic::open(repo.clone(), options).unwrap(); |
| 164 | + fill_log_with(&mut log, iter::once([b'x'; 64]).cycle().take(9)); |
| 165 | + } |
| 166 | + let segments = repo.existing_offsets().unwrap(); |
| 167 | + assert_eq!(2, segments.len(), "repo should contain 2 segments"); |
| 168 | + |
| 169 | + { |
| 170 | + let last_segment = repo.open_segment_writer(*segments.last().unwrap()).unwrap(); |
| 171 | + let mut data = last_segment.buf_mut(); |
| 172 | + data[segment::Header::LEN + 1..].fill(0); |
| 173 | + } |
| 174 | + |
| 175 | + assert_matches!( |
| 176 | + commitlog::Generic::<_, [u8; 64]>::open(repo, options), |
| 177 | + Err(e) if e.kind() == io::ErrorKind::InvalidData, |
| 178 | + ); |
| 179 | +} |
| 180 | + |
143 | 181 | fn open_log<T>(repo: ShortMem) -> commitlog::Generic<ShortMem, T> { |
144 | 182 | commitlog::Generic::open( |
145 | 183 | repo, |
@@ -229,6 +267,12 @@ impl ShortMem { |
229 | 267 | } |
230 | 268 | } |
231 | 269 |
|
| 270 | +impl fmt::Display for ShortMem { |
| 271 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 272 | + fmt::Display::fmt(&self.inner, f) |
| 273 | + } |
| 274 | +} |
| 275 | + |
232 | 276 | impl Repo for ShortMem { |
233 | 277 | type SegmentWriter = ShortSegment; |
234 | 278 | type SegmentReader = io::BufReader<repo::mem::Segment>; |
|
0 commit comments