Line data Source code
1 : use std::{ffi::CString, sync::Arc};
2 :
3 : use byteorder::{LittleEndian, WriteBytesExt};
4 : use crc32c::crc32c_append;
5 : use parking_lot::{Mutex, MutexGuard};
6 : use postgres_ffi::{
7 : pg_constants::{
8 : RM_LOGICALMSG_ID, XLOG_LOGICAL_MESSAGE, XLP_LONG_HEADER, XLR_BLOCK_ID_DATA_LONG,
9 : XLR_BLOCK_ID_DATA_SHORT,
10 : },
11 : v16::{
12 : wal_craft_test_export::{XLogLongPageHeaderData, XLogPageHeaderData, XLOG_PAGE_MAGIC},
13 : xlog_utils::{
14 : XLogSegNoOffsetToRecPtr, XlLogicalMessage, XLOG_RECORD_CRC_OFFS,
15 : XLOG_SIZE_OF_XLOG_LONG_PHD, XLOG_SIZE_OF_XLOG_RECORD, XLOG_SIZE_OF_XLOG_SHORT_PHD,
16 : XLP_FIRST_IS_CONTRECORD,
17 : },
18 : XLogRecord,
19 : },
20 : WAL_SEGMENT_SIZE, XLOG_BLCKSZ,
21 : };
22 : use utils::lsn::Lsn;
23 :
24 : use super::block_storage::BlockStorage;
25 :
26 : /// Simulation implementation of walproposer WAL storage.
27 : pub struct DiskWalProposer {
28 : state: Mutex<State>,
29 : }
30 :
31 : impl DiskWalProposer {
32 73507 : pub fn new() -> Arc<DiskWalProposer> {
33 73507 : Arc::new(DiskWalProposer {
34 73507 : state: Mutex::new(State {
35 73507 : internal_available_lsn: Lsn(0),
36 73507 : prev_lsn: Lsn(0),
37 73507 : disk: BlockStorage::new(),
38 73507 : }),
39 73507 : })
40 73507 : }
41 :
42 72549 : pub fn lock(&self) -> MutexGuard<State> {
43 72549 : self.state.lock()
44 72549 : }
45 : }
46 :
47 : pub struct State {
48 : // flush_lsn
49 : internal_available_lsn: Lsn,
50 : // needed for WAL generation
51 : prev_lsn: Lsn,
52 : // actual WAL storage
53 : disk: BlockStorage,
54 : }
55 :
56 : impl State {
57 25959 : pub fn read(&self, pos: u64, buf: &mut [u8]) {
58 25959 : self.disk.read(pos, buf);
59 25959 : // TODO: fail on reading uninitialized data
60 25959 : }
61 :
62 129499 : pub fn write(&mut self, pos: u64, buf: &[u8]) {
63 129499 : self.disk.write(pos, buf);
64 129499 : }
65 :
66 : /// Update the internal available LSN to the given value.
67 2588 : pub fn reset_to(&mut self, lsn: Lsn) {
68 2588 : self.internal_available_lsn = lsn;
69 2588 : }
70 :
71 : /// Get current LSN.
72 10613 : pub fn flush_rec_ptr(&self) -> Lsn {
73 10613 : self.internal_available_lsn
74 10613 : }
75 :
76 : /// Generate a new WAL record at the current LSN.
77 31916 : pub fn insert_logical_message(&mut self, prefix: &str, msg: &[u8]) -> anyhow::Result<()> {
78 31916 : let prefix_cstr = CString::new(prefix)?;
79 31916 : let prefix_bytes = prefix_cstr.as_bytes_with_nul();
80 31916 :
81 31916 : let lm = XlLogicalMessage {
82 31916 : db_id: 0,
83 31916 : transactional: 0,
84 31916 : prefix_size: prefix_bytes.len() as ::std::os::raw::c_ulong,
85 31916 : message_size: msg.len() as ::std::os::raw::c_ulong,
86 31916 : };
87 31916 :
88 31916 : let record_bytes = lm.encode();
89 31916 : let rdatas: Vec<&[u8]> = vec![&record_bytes, prefix_bytes, msg];
90 31916 : insert_wal_record(self, rdatas, RM_LOGICALMSG_ID, XLOG_LOGICAL_MESSAGE)
91 31916 : }
92 : }
93 :
94 31916 : fn insert_wal_record(
95 31916 : state: &mut State,
96 31916 : rdatas: Vec<&[u8]>,
97 31916 : rmid: u8,
98 31916 : info: u8,
99 31916 : ) -> anyhow::Result<()> {
100 31916 : // bytes right after the header, in the same rdata block
101 31916 : let mut scratch = Vec::new();
102 95748 : let mainrdata_len: usize = rdatas.iter().map(|rdata| rdata.len()).sum();
103 31916 :
104 31916 : if mainrdata_len > 0 {
105 31916 : if mainrdata_len > 255 {
106 0 : scratch.push(XLR_BLOCK_ID_DATA_LONG);
107 0 : // TODO: verify endiness
108 0 : let _ = scratch.write_u32::<LittleEndian>(mainrdata_len as u32);
109 31916 : } else {
110 31916 : scratch.push(XLR_BLOCK_ID_DATA_SHORT);
111 31916 : scratch.push(mainrdata_len as u8);
112 31916 : }
113 0 : }
114 :
115 31916 : let total_len: u32 = (XLOG_SIZE_OF_XLOG_RECORD + scratch.len() + mainrdata_len) as u32;
116 31916 : let size = maxalign(total_len);
117 31916 : assert!(size as usize > XLOG_SIZE_OF_XLOG_RECORD);
118 :
119 31916 : let start_bytepos = recptr_to_bytepos(state.internal_available_lsn);
120 31916 : let end_bytepos = start_bytepos + size as u64;
121 31916 :
122 31916 : let start_recptr = bytepos_to_recptr(start_bytepos);
123 31916 : let end_recptr = bytepos_to_recptr(end_bytepos);
124 31916 :
125 31916 : assert!(recptr_to_bytepos(start_recptr) == start_bytepos);
126 31916 : assert!(recptr_to_bytepos(end_recptr) == end_bytepos);
127 :
128 31916 : let mut crc = crc32c_append(0, &scratch);
129 127664 : for rdata in &rdatas {
130 95748 : crc = crc32c_append(crc, rdata);
131 95748 : }
132 :
133 31916 : let mut header = XLogRecord {
134 31916 : xl_tot_len: total_len,
135 31916 : xl_xid: 0,
136 31916 : xl_prev: state.prev_lsn.0,
137 31916 : xl_info: info,
138 31916 : xl_rmid: rmid,
139 31916 : __bindgen_padding_0: [0u8; 2usize],
140 31916 : xl_crc: crc,
141 31916 : };
142 :
143 : // now we have the header and can finish the crc
144 31916 : let header_bytes = header.encode()?;
145 31916 : let crc = crc32c_append(crc, &header_bytes[0..XLOG_RECORD_CRC_OFFS]);
146 31916 : header.xl_crc = crc;
147 :
148 31916 : let mut header_bytes = header.encode()?.to_vec();
149 31916 : assert!(header_bytes.len() == XLOG_SIZE_OF_XLOG_RECORD);
150 :
151 31916 : header_bytes.extend_from_slice(&scratch);
152 31916 :
153 31916 : // finish rdatas
154 31916 : let mut rdatas = rdatas;
155 31916 : rdatas.insert(0, &header_bytes);
156 31916 :
157 31916 : write_walrecord_to_disk(state, total_len as u64, rdatas, start_recptr, end_recptr)?;
158 :
159 31916 : state.internal_available_lsn = end_recptr;
160 31916 : state.prev_lsn = start_recptr;
161 31916 : Ok(())
162 31916 : }
163 :
164 31916 : fn write_walrecord_to_disk(
165 31916 : state: &mut State,
166 31916 : total_len: u64,
167 31916 : rdatas: Vec<&[u8]>,
168 31916 : start: Lsn,
169 31916 : end: Lsn,
170 31916 : ) -> anyhow::Result<()> {
171 31916 : let mut curr_ptr = start;
172 31916 : let mut freespace = insert_freespace(curr_ptr);
173 31916 : let mut written: usize = 0;
174 31916 :
175 31916 : assert!(freespace >= std::mem::size_of::<u32>());
176 :
177 159580 : for mut rdata in rdatas {
178 127845 : while rdata.len() >= freespace {
179 181 : assert!(
180 181 : curr_ptr.segment_offset(WAL_SEGMENT_SIZE) >= XLOG_SIZE_OF_XLOG_SHORT_PHD
181 0 : || freespace == 0
182 : );
183 :
184 181 : state.write(curr_ptr.0, &rdata[..freespace]);
185 181 : rdata = &rdata[freespace..];
186 181 : written += freespace;
187 181 : curr_ptr = Lsn(curr_ptr.0 + freespace as u64);
188 181 :
189 181 : let mut new_page = XLogPageHeaderData {
190 181 : xlp_magic: XLOG_PAGE_MAGIC as u16,
191 181 : xlp_info: XLP_BKP_REMOVABLE,
192 181 : xlp_tli: 1,
193 181 : xlp_pageaddr: curr_ptr.0,
194 181 : xlp_rem_len: (total_len - written as u64) as u32,
195 181 : ..Default::default() // Put 0 in padding fields.
196 181 : };
197 181 : if new_page.xlp_rem_len > 0 {
198 163 : new_page.xlp_info |= XLP_FIRST_IS_CONTRECORD;
199 163 : }
200 :
201 181 : if curr_ptr.segment_offset(WAL_SEGMENT_SIZE) == 0 {
202 0 : new_page.xlp_info |= XLP_LONG_HEADER;
203 0 : let long_page = XLogLongPageHeaderData {
204 0 : std: new_page,
205 0 : xlp_sysid: 0,
206 0 : xlp_seg_size: WAL_SEGMENT_SIZE as u32,
207 0 : xlp_xlog_blcksz: XLOG_BLCKSZ as u32,
208 0 : };
209 0 : let header_bytes = long_page.encode()?;
210 0 : assert!(header_bytes.len() == XLOG_SIZE_OF_XLOG_LONG_PHD);
211 0 : state.write(curr_ptr.0, &header_bytes);
212 0 : curr_ptr = Lsn(curr_ptr.0 + header_bytes.len() as u64);
213 : } else {
214 181 : let header_bytes = new_page.encode()?;
215 181 : assert!(header_bytes.len() == XLOG_SIZE_OF_XLOG_SHORT_PHD);
216 181 : state.write(curr_ptr.0, &header_bytes);
217 181 : curr_ptr = Lsn(curr_ptr.0 + header_bytes.len() as u64);
218 : }
219 181 : freespace = insert_freespace(curr_ptr);
220 : }
221 :
222 127664 : assert!(
223 127664 : curr_ptr.segment_offset(WAL_SEGMENT_SIZE) >= XLOG_SIZE_OF_XLOG_SHORT_PHD
224 0 : || rdata.is_empty()
225 : );
226 127664 : state.write(curr_ptr.0, rdata);
227 127664 : curr_ptr = Lsn(curr_ptr.0 + rdata.len() as u64);
228 127664 : written += rdata.len();
229 127664 : freespace -= rdata.len();
230 : }
231 :
232 31916 : assert!(written == total_len as usize);
233 31916 : curr_ptr.0 = maxalign(curr_ptr.0);
234 31916 : assert!(curr_ptr == end);
235 31916 : Ok(())
236 31916 : }
237 :
238 63832 : fn maxalign<T>(size: T) -> T
239 63832 : where
240 63832 : T: std::ops::BitAnd<Output = T>
241 63832 : + std::ops::Add<Output = T>
242 63832 : + std::ops::Not<Output = T>
243 63832 : + From<u8>,
244 63832 : {
245 63832 : (size + T::from(7)) & !T::from(7)
246 63832 : }
247 :
248 32097 : fn insert_freespace(ptr: Lsn) -> usize {
249 32097 : if ptr.block_offset() == 0 {
250 0 : 0
251 : } else {
252 32097 : (XLOG_BLCKSZ as u64 - ptr.block_offset()) as usize
253 : }
254 32097 : }
255 :
256 : const XLP_BKP_REMOVABLE: u16 = 0x0004;
257 : const USABLE_BYTES_IN_PAGE: u64 = (XLOG_BLCKSZ - XLOG_SIZE_OF_XLOG_SHORT_PHD) as u64;
258 : const USABLE_BYTES_IN_SEGMENT: u64 = ((WAL_SEGMENT_SIZE / XLOG_BLCKSZ) as u64
259 : * USABLE_BYTES_IN_PAGE)
260 : - (XLOG_SIZE_OF_XLOG_RECORD - XLOG_SIZE_OF_XLOG_SHORT_PHD) as u64;
261 :
262 63832 : fn bytepos_to_recptr(bytepos: u64) -> Lsn {
263 63832 : let fullsegs = bytepos / USABLE_BYTES_IN_SEGMENT;
264 63832 : let mut bytesleft = bytepos % USABLE_BYTES_IN_SEGMENT;
265 :
266 63832 : let seg_offset = if bytesleft < (XLOG_BLCKSZ - XLOG_SIZE_OF_XLOG_SHORT_PHD) as u64 {
267 : // fits on first page of segment
268 0 : bytesleft + XLOG_SIZE_OF_XLOG_SHORT_PHD as u64
269 : } else {
270 : // account for the first page on segment with long header
271 63832 : bytesleft -= (XLOG_BLCKSZ - XLOG_SIZE_OF_XLOG_SHORT_PHD) as u64;
272 63832 : let fullpages = bytesleft / USABLE_BYTES_IN_PAGE;
273 63832 : bytesleft %= USABLE_BYTES_IN_PAGE;
274 63832 :
275 63832 : XLOG_BLCKSZ as u64
276 63832 : + fullpages * XLOG_BLCKSZ as u64
277 63832 : + bytesleft
278 63832 : + XLOG_SIZE_OF_XLOG_SHORT_PHD as u64
279 : };
280 :
281 63832 : Lsn(XLogSegNoOffsetToRecPtr(
282 63832 : fullsegs,
283 63832 : seg_offset as u32,
284 63832 : WAL_SEGMENT_SIZE,
285 63832 : ))
286 63832 : }
287 :
288 95748 : fn recptr_to_bytepos(ptr: Lsn) -> u64 {
289 95748 : let fullsegs = ptr.segment_number(WAL_SEGMENT_SIZE);
290 95748 : let offset = ptr.segment_offset(WAL_SEGMENT_SIZE) as u64;
291 95748 :
292 95748 : let fullpages = offset / XLOG_BLCKSZ as u64;
293 95748 : let offset = offset % XLOG_BLCKSZ as u64;
294 95748 :
295 95748 : if fullpages == 0 {
296 0 : fullsegs * USABLE_BYTES_IN_SEGMENT
297 0 : + if offset > 0 {
298 0 : assert!(offset >= XLOG_SIZE_OF_XLOG_SHORT_PHD as u64);
299 0 : offset - XLOG_SIZE_OF_XLOG_SHORT_PHD as u64
300 : } else {
301 0 : 0
302 : }
303 : } else {
304 95748 : fullsegs * USABLE_BYTES_IN_SEGMENT
305 95748 : + (XLOG_BLCKSZ - XLOG_SIZE_OF_XLOG_SHORT_PHD) as u64
306 95748 : + (fullpages - 1) * USABLE_BYTES_IN_PAGE
307 95748 : + if offset > 0 {
308 95748 : assert!(offset >= XLOG_SIZE_OF_XLOG_SHORT_PHD as u64);
309 95748 : offset - XLOG_SIZE_OF_XLOG_SHORT_PHD as u64
310 : } else {
311 0 : 0
312 : }
313 : }
314 95748 : }
|