xapian-core  2.0.0
glass_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 "glass_freelist.h"
24 
25 #include "glass_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 Glass;
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 GLASS_FREELIST_SIZE
40 # define FREELIST_END_ \
41  (8 + (GLASS_FREELIST_SIZE < 3 ? 3 : GLASS_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 GlassFreeList::read_block(const GlassTable * B, uint4 n, uint8_t * ptr)
60 {
61  B->read_block(n, ptr);
62  if (rare(GET_LEVEL(ptr) != LEVEL_FREELIST))
63  throw Xapian::DatabaseCorruptError("Freelist corrupt");
64 }
65 
66 void
67 GlassFreeList::write_block(const GlassTable * B, uint4 n, uint8_t * ptr,
68  uint4 rev)
69 {
70  SET_REVISION(ptr, rev);
71  aligned_write4(ptr + 4, 0);
73  B->write_block(n, ptr, flw_appending);
74 }
75 
76 uint4
78  uint4 * blk_to_free)
79 {
80  if (fl == fl_end) {
81  return first_unused_block++;
82  }
83 
84  if (p == 0) {
85  if (fl.n == UNUSED) {
86  throw Xapian::DatabaseCorruptError("Freelist pointer invalid");
87  }
88  // Actually read the current freelist block.
89  p = new uint8_t[block_size];
90  read_block(B, fl.n, p);
91  }
92 
93  // Either the freelist end is in this block, or this freelist block has a
94  // next pointer.
95  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
96 
97  if (fl.c != FREELIST_END - 4) {
98  uint4 blk = aligned_read4(p + fl.c);
99  if (blk == UNUSED)
100  throw Xapian::DatabaseCorruptError("Ran off end of freelist (" + str(fl.n) + ", " + str(fl.c) + ")");
101  fl.c += 4;
102  return blk;
103  }
104 
105  // Delay handling marking old block as unused until after we've
106  // started a new one.
107  uint4 old_fl_blk = fl.n;
108 
109  fl.n = aligned_read4(p + fl.c);
110  if (fl.n == UNUSED) {
111  throw Xapian::DatabaseCorruptError("Freelist next pointer invalid");
112  }
113  // Allow for mini-header at start of freelist block.
114  fl.c = C_BASE;
115  read_block(B, fl.n, p);
116 
117  // Either the freelist end is in this block, or this freelist block has
118  // a next pointer.
119  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
120 
121  if (blk_to_free) {
122  Assert(*blk_to_free == BLK_UNUSED);
123  *blk_to_free = old_fl_blk;
124  } else {
125  mark_block_unused(B, block_size, old_fl_blk);
126  }
127 
128  return get_block(B, block_size);
129 }
130 
131 uint4
132 GlassFreeList::walk(const GlassTable *B, uint4 block_size, bool inclusive)
133 {
134  if (fl == fl_end) {
135  // It's expected that the caller checks !empty() first.
136  return UNUSED;
137  }
138 
139  if (p == 0) {
140  if (fl.n == UNUSED) {
141  throw Xapian::DatabaseCorruptError("Freelist pointer invalid");
142  }
143  p = new uint8_t[block_size];
144  read_block(B, fl.n, p);
145  if (inclusive) {
146  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
147  return fl.n;
148  }
149  }
150 
151  // Either the freelist end is in this block, or this freelist block has
152  // a next pointer.
153  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
154 
155  if (fl.c != FREELIST_END - 4) {
156  uint4 blk = aligned_read4(p + fl.c);
157  fl.c += 4;
158  return blk;
159  }
160 
161  fl.n = aligned_read4(p + fl.c);
162  if (fl.n == UNUSED) {
163  throw Xapian::DatabaseCorruptError("Freelist next pointer invalid");
164  }
165  // Allow for mini-header at start of freelist block.
166  fl.c = C_BASE;
167  read_block(B, fl.n, p);
168 
169  // Either the freelist end is in this block, or this freelist block has
170  // a next pointer.
171  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
172 
173  if (inclusive)
174  return fl.n;
175  return walk(B, block_size, inclusive);
176 }
177 
178 void
180 {
181  // If the current flw block is full, we need to call get_block(), and if
182  // the returned block is the last entry in its freelist block, that block
183  // needs to be marked as unused. The recursion this would create is
184  // problematic, so we instead note down that block and mark it as unused
185  // once we've processed the original request.
186  uint4 blk_to_free = BLK_UNUSED;
187 
188  if (!pw) {
189  pw = new uint8_t[block_size];
190  if (flw.c != 0) {
191  read_block(B, flw.n, pw);
192  flw_appending = true;
193  }
194  }
195  if (flw.c == 0) {
196  uint4 n = get_block(B, block_size, &blk_to_free);
197  flw.n = n;
198  flw.c = C_BASE;
199  if (fl.c == 0) {
200  fl = fl_end = flw;
201  }
202  flw_appending = (n == first_unused_block - 1);
204  } else if (flw.c == FREELIST_END - 4) {
205  // blk is free *after* the current revision gets released, so we can't
206  // just use blk as the next block in the freelist chain.
207  uint4 n = get_block(B, block_size, &blk_to_free);
208  aligned_write4(pw + flw.c, n);
209 #ifdef GLASS_FREELIST_SIZE
210  if (block_size != FREELIST_END) {
211  memset(pw + FREELIST_END, 0, block_size - FREELIST_END);
212  }
213 #endif
214  write_block(B, flw.n, pw, revision + 1);
215  if (p && flw.n == fl.n) {
216  // FIXME: share and refcount?
217  memcpy(p, pw, block_size);
218  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
219  }
220  flw.n = n;
221  flw.c = C_BASE;
222  flw_appending = (n == first_unused_block - 1);
224  }
225 
226  aligned_write4(pw + flw.c, blk);
227  flw.c += 4;
228 
229  if (blk_to_free != BLK_UNUSED)
230  mark_block_unused(B, block_size, blk_to_free);
231 }
232 
233 void
235 {
236  if (pw && flw.c != 0) {
237  memset(pw + flw.c, 255, FREELIST_END - flw.c - 4);
238 #ifdef GLASS_FREELIST_SIZE
239  if (block_size != FREELIST_END) {
240  memset(pw + FREELIST_END, 0xaa, block_size - FREELIST_END);
241  }
242 #endif
243  write_block(B, flw.n, pw, revision);
244  if (p && flw.n == fl.n) {
245  // FIXME: share and refcount?
246  memcpy(p, pw, block_size);
247  Assert(fl.n == fl_end.n || aligned_read4(p + FREELIST_END - 4) != UNUSED);
248  }
249  flw_appending = true;
250  fl_end = flw;
251  }
252 }
253 
255 {
256  const unsigned BITS_PER_ELT = sizeof(elt_type) * 8;
257  const elt_type ALL_BITS = static_cast<elt_type>(-1);
258  uint4 first_unused = fl.get_first_unused_block();
259  bitmap_size = (first_unused + BITS_PER_ELT - 1) / BITS_PER_ELT;
260  bitmap = new elt_type[bitmap_size];
261  std::fill_n(bitmap, bitmap_size - 1, ALL_BITS);
262  // Only set the bits in the final element of bitmap which correspond to
263  // blocks < first_unused.
264  uint4 remainder = first_unused & (BITS_PER_ELT - 1);
265  if (remainder)
266  bitmap[bitmap_size - 1] = (static_cast<elt_type>(1) << remainder) - 1;
267  else
268  bitmap[bitmap_size - 1] = ALL_BITS;
269 }
270 
271 uint4
273 {
274  const unsigned BITS_PER_ELT = sizeof(elt_type) * 8;
275  uint4 c = 0;
276  for (uint4 i = 0; i < bitmap_size; ++i) {
277  elt_type elt = bitmap[i];
278  if (usual(elt == 0))
279  continue;
280  if (p_first_bad_blk) {
281  uint4 first_bad_blk = i * BITS_PER_ELT;
282  if (false) {
283 #if HAVE_DECL___BUILTIN_CTZ
284  } else if constexpr(sizeof(elt_type) == sizeof(unsigned)) {
285  first_bad_blk += __builtin_ctz(elt);
286 #endif
287 #if HAVE_DECL___BUILTIN_CTZL
288  } else if constexpr(sizeof(elt_type) == sizeof(unsigned long)) {
289  first_bad_blk += __builtin_ctzl(elt);
290 #endif
291 #if HAVE_DECL___BUILTIN_CTZLL
292  } else if constexpr(sizeof(elt_type) == sizeof(unsigned long long)) {
293  first_bad_blk += __builtin_ctzll(elt);
294 #endif
295  } else {
296  for (elt_type mask = 1; (elt & mask) == 0; mask <<= 1) {
297  ++first_bad_blk;
298  }
299  }
300  *p_first_bad_blk = first_bad_blk;
301  p_first_bad_blk = nullptr;
302  }
303 
304  // Count set bits in elt.
305  add_popcount(c, elt);
306  }
307  return c;
308 }
Definition: unittest.cc:660
unsigned long elt_type
GlassFreeListChecker(const GlassFreeListChecker &)
uint4 count_set_bits(uint4 *p_first_bad_blk) const
Count how many bits are still set.
void write_block(const GlassTable *B, uint4 n, uint8_t *p, uint4 rev)
uint4 get_first_unused_block() const
void mark_block_unused(const GlassTable *B, uint4 block_size, uint4 n)
uint4 walk(const GlassTable *B, uint4 block_size, bool inclusive)
void commit(const GlassTable *B, uint4 block_size)
void read_block(const GlassTable *B, uint4 n, uint8_t *p)
uint4 get_block(const GlassTable *B, uint4 block_size, uint4 *blk_to_free=NULL)
Class managing a Btree table in a Glass database.
Definition: glass_table.h:432
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
Glass freelist.
Btree implementation.
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