xapian-core  2.0.0
honey_freelist.cc
Go to the documentation of this file.
1 
4 /* Copyright 2014,2015,2016,2020 Olly Betts
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see
18  * <https://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #include "honey_freelist.h"
24 
25 #include "honey_table.h"
26 #include "xapian/error.h"
27 
28 #include "omassert.h"
29 #include "popcount.h"
30 #include "wordaccess.h"
31 #include <cstring>
32 
33 using namespace std;
34 using namespace Honey;
35 
36 // Allow forcing the freelist to be shorter to tickle bugs.
37 // FIXME: Sort out a way we can set this dynamically while running the
38 // testsuite.
39 #ifdef HONEY_FREELIST_SIZE
40 # define FREELIST_END_ \
41  (8 + (HONEY_FREELIST_SIZE < 3 ? 3 : HONEY_FREELIST_SIZE) * 4)
42 # define FREELIST_END (FREELIST_END_ < 2048 ? FREELIST_END_ : 2048)
43 #else
44 # define FREELIST_END block_size
45 #endif
46 
53 const unsigned C_BASE = 8;
54 
56 const uint4 UNUSED = static_cast<uint4>(-1);
57 
58 void
59 HoneyFreeList::read_block(const HoneyTable* B, uint4 n, uint8_t* ptr)
60 {
61 #ifdef SST_SEARCH
62  (void)B; (void)n; (void)ptr;
63 #else
64  B->read_block(n, ptr);
65  if (rare(GET_LEVEL(ptr) != LEVEL_FREELIST))
66  throw Xapian::DatabaseCorruptError("Freelist corrupt");
67 #endif
68 }
69 
70 void
71 HoneyFreeList::write_block(const HoneyTable* B, uint4 n, uint8_t* ptr,
72  uint4 rev)
73 {
74 #ifdef SST_SEARCH
75  (void)B; (void)n; (void)ptr; (void)rev;
76 #else
77  SET_REVISION(ptr, rev);
78  aligned_write4(ptr + 4, 0);
80  B->write_block(n, ptr, flw_appending);
81 #endif
82 }
83 
84 uint4
86  uint4 * blk_to_free)
87 {
88  if (fl == fl_end) {
89  return first_unused_block++;
90  }
91 
92  if (p == 0) {
93  if (fl.n == UNUSED) {
94  throw Xapian::DatabaseCorruptError("Freelist pointer invalid");
95  }
96  // Actually read the current freelist block.
97  p = new uint8_t[block_size];
98  read_block(B, fl.n, p);
99  }
100 
101  // Either the freelist end is in this block, or this freelist block has a
102  // next pointer.
103  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
104 
105  if (fl.c != FREELIST_END - 4) {
106  uint4 blk = aligned_read4(p + fl.c);
107  if (blk == UNUSED) {
108  throw Xapian::DatabaseCorruptError("Ran off end of freelist (" +
109  str(fl.n) + ", " + str(fl.c) +
110  ")");
111  }
112  fl.c += 4;
113  return blk;
114  }
115 
116  // Delay handling marking old block as unused until after we've
117  // started a new one.
118  uint4 old_fl_blk = fl.n;
119 
120  fl.n = aligned_read4(p + fl.c);
121  if (fl.n == UNUSED) {
122  throw Xapian::DatabaseCorruptError("Freelist next pointer invalid");
123  }
124  // Allow for mini-header at start of freelist block.
125  fl.c = C_BASE;
126  read_block(B, fl.n, p);
127 
128  // Either the freelist end is in this block, or this freelist block has
129  // a next pointer.
130  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
131 
132  if (blk_to_free) {
133  Assert(*blk_to_free == BLK_UNUSED);
134  *blk_to_free = old_fl_blk;
135  } else {
136  mark_block_unused(B, block_size, old_fl_blk);
137  }
138 
139  return get_block(B, block_size);
140 }
141 
142 uint4
143 HoneyFreeList::walk(const HoneyTable* B, uint4 block_size, bool inclusive)
144 {
145  if (fl == fl_end) {
146  // It's expected that the caller checks !empty() first.
147  return UNUSED;
148  }
149 
150  if (p == 0) {
151  if (fl.n == UNUSED) {
152  throw Xapian::DatabaseCorruptError("Freelist pointer invalid");
153  }
154  p = new uint8_t[block_size];
155  read_block(B, fl.n, p);
156  if (inclusive) {
157  Assert(fl.n == fl_end.n ||
158  aligned_read4(p + FREELIST_END - 4) != UNUSED);
159  return fl.n;
160  }
161  }
162 
163  // Either the freelist end is in this block, or this freelist block has
164  // a next pointer.
165  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
166 
167  if (fl.c != FREELIST_END - 4) {
168  uint4 blk = aligned_read4(p + fl.c);
169  fl.c += 4;
170  return blk;
171  }
172 
173  fl.n = aligned_read4(p + fl.c);
174  if (fl.n == UNUSED) {
175  throw Xapian::DatabaseCorruptError("Freelist next pointer invalid");
176  }
177  // Allow for mini-header at start of freelist block.
178  fl.c = C_BASE;
179  read_block(B, fl.n, p);
180 
181  // Either the freelist end is in this block, or this freelist block has
182  // a next pointer.
183  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
184 
185  if (inclusive)
186  return fl.n;
187  return walk(B, block_size, inclusive);
188 }
189 
190 void
192  uint4 block_size,
193  uint4 blk)
194 {
195  // If the current flw block is full, we need to call get_block(), and if
196  // the returned block is the last entry in its freelist block, that block
197  // needs to be marked as unused. The recursion this would create is
198  // problematic, so we instead note down that block and mark it as unused
199  // once we've processed the original request.
200  uint4 blk_to_free = BLK_UNUSED;
201 
202  if (!pw) {
203  pw = new uint8_t[block_size];
204  if (flw.c != 0) {
205  read_block(B, flw.n, pw);
206  flw_appending = true;
207  }
208  }
209  if (flw.c == 0) {
210  uint4 n = get_block(B, block_size, &blk_to_free);
211  flw.n = n;
212  flw.c = C_BASE;
213  if (fl.c == 0) {
214  fl = fl_end = flw;
215  }
216  flw_appending = (n == first_unused_block - 1);
218  } else if (flw.c == FREELIST_END - 4) {
219  // blk is free *after* the current revision gets released, so we can't
220  // just use blk as the next block in the freelist chain.
221  uint4 n = get_block(B, block_size, &blk_to_free);
222  aligned_write4(pw + flw.c, n);
223 #ifdef HONEY_FREELIST_SIZE
224  if (block_size != FREELIST_END) {
225  memset(pw + FREELIST_END, 0, block_size - FREELIST_END);
226  }
227 #endif
228  write_block(B, flw.n, pw, revision + 1);
229  if (p && flw.n == fl.n) {
230  // FIXME: share and refcount?
231  memcpy(p, pw, block_size);
232  Assert(fl.n == fl_end.n ||
233  aligned_read4(p + FREELIST_END - 4) != UNUSED);
234  }
235  flw.n = n;
236  flw.c = C_BASE;
237  flw_appending = (n == first_unused_block - 1);
239  }
240 
241  aligned_write4(pw + flw.c, blk);
242  flw.c += 4;
243 
244  if (blk_to_free != BLK_UNUSED)
245  mark_block_unused(B, block_size, blk_to_free);
246 }
247 
248 void
250 {
251  if (pw && flw.c != 0) {
252  memset(pw + flw.c, 255, FREELIST_END - flw.c - 4);
253 #ifdef HONEY_FREELIST_SIZE
254  if (block_size != FREELIST_END) {
255  memset(pw + FREELIST_END, 0xaa, block_size - FREELIST_END);
256  }
257 #endif
258  write_block(B, flw.n, pw, revision);
259  if (p && flw.n == fl.n) {
260  // FIXME: share and refcount?
261  memcpy(p, pw, block_size);
262  Assert(fl.n == fl_end.n ||
263  aligned_read4(p + FREELIST_END - 4) != UNUSED);
264  }
265  flw_appending = true;
266  fl_end = flw;
267  }
268 }
269 
271 {
272  const unsigned BITS_PER_ELT = sizeof(elt_type) * 8;
273  const elt_type ALL_BITS = static_cast<elt_type>(-1);
274  uint4 first_unused = fl.get_first_unused_block();
275  bitmap_size = (first_unused + BITS_PER_ELT - 1) / BITS_PER_ELT;
276  bitmap = new elt_type[bitmap_size];
277  std::fill_n(bitmap, bitmap_size - 1, ALL_BITS);
278  // Only set the bits in the final element of bitmap which correspond to
279  // blocks < first_unused.
280  uint4 remainder = first_unused & (BITS_PER_ELT - 1);
281  if (remainder)
282  bitmap[bitmap_size - 1] = (static_cast<elt_type>(1) << remainder) - 1;
283  else
284  bitmap[bitmap_size - 1] = ALL_BITS;
285 }
286 
287 uint4
289 {
290  const unsigned BITS_PER_ELT = sizeof(elt_type) * 8;
291  uint4 c = 0;
292  for (uint4 i = 0; i < bitmap_size; ++i) {
293  elt_type elt = bitmap[i];
294  if (usual(elt == 0))
295  continue;
296  if (p_first_bad_blk) {
297  uint4 first_bad_blk = i * BITS_PER_ELT;
298  if (false) {
299 #if HAVE_DECL___BUILTIN_CTZ
300  } else if constexpr(sizeof(elt_type) == sizeof(unsigned)) {
301  first_bad_blk += __builtin_ctz(elt);
302 #endif
303 #if HAVE_DECL___BUILTIN_CTZL
304  } else if constexpr(sizeof(elt_type) == sizeof(unsigned long)) {
305  first_bad_blk += __builtin_ctzl(elt);
306 #endif
307 #if HAVE_DECL___BUILTIN_CTZLL
308  } else if constexpr(sizeof(elt_type) == sizeof(unsigned long long)) {
309  first_bad_blk += __builtin_ctzll(elt);
310 #endif
311  } else {
312  for (elt_type mask = 1; (elt & mask) == 0; mask <<= 1) {
313  ++first_bad_blk;
314  }
315  }
316  *p_first_bad_blk = first_bad_blk;
317  p_first_bad_blk = nullptr;
318  }
319 
320  // Count set bits in elt.
321  add_popcount(c, elt);
322  }
323  return c;
324 }
Definition: unittest.cc:660
uint4 count_set_bits(uint4 *p_first_bad_blk) const
Count how many bits are still set.
unsigned long elt_type
HoneyFreeListChecker(const HoneyFreeListChecker &)
void write_block(const HoneyTable *B, uint4 n, uint8_t *p, uint4 rev)
void mark_block_unused(const HoneyTable *B, uint4 block_size, uint4 n)
uint4 get_first_unused_block() const
uint4 walk(const HoneyTable *B, uint4 block_size, bool inclusive)
void commit(const HoneyTable *B, uint4 block_size)
void read_block(const HoneyTable *B, uint4 n, uint8_t *p)
uint4 get_block(const HoneyTable *B, uint4 block_size, uint4 *blk_to_free=NULL)
DatabaseCorruptError indicates database corruption was detected.
Definition: error.h:397
#define usual(COND)
Definition: config.h:608
#define rare(COND)
Definition: config.h:607
PositionList * p
Hierarchy of classes which Xapian can throw as exceptions.
const uint4 UNUSED
Invalid freelist block value, so we can detect overreading bugs, etc.
const unsigned C_BASE
The first offset to use for storing free block info.
#define FREELIST_END
Honey freelist.
HoneyTable class.
const uint4 BLK_UNUSED
Definition: honey_table.h:73
uint32_t uint4
Definition: internaltypes.h:31
const int LEVEL_FREELIST
Freelist blocks have their level set to LEVEL_FREELIST.
Definition: glass_table.h:138
void SET_LEVEL(uint8_t *b, int x)
Definition: glass_table.h:117
int GET_LEVEL(const uint8_t *b)
Definition: glass_table.h:110
void SET_REVISION(uint8_t *b, uint4 rev)
Definition: glass_table.h:116
string str(int value)
Convert int to std::string.
Definition: str.cc:91
int revision()
Report the revision of the library which the program is linked with.
Definition: xapian.h:146
XAPIAN_REVISION_TYPE rev
Revision number of a database.
Definition: types.h:108
Various assertion macros.
#define Assert(COND)
Definition: omassert.h:122
Count the number of set bits in an integer type.
static void add_popcount(A &accumulator, V value)
Add the number of set bits in value to accumulator.
Definition: popcount.h:39
functions for reading and writing different width words
void aligned_write4(unsigned char *ptr, T value)
Definition: wordaccess.h:166
uint32_t aligned_read4(const unsigned char *ptr)
Definition: wordaccess.h:141