]>
Commit | Line | Data |
---|---|---|
886f2785 | 1 | |
8638fc66 | 2 | /* |
262a0e14 | 3 | * $Id$ |
8638fc66 | 4 | * |
5 | * DEBUG: section 20 Store Rebuild Routines | |
6 | * AUTHOR: Duane Wessels | |
7 | * | |
2b6662ba | 8 | * SQUID Web Proxy Cache http://www.squid-cache.org/ |
e25c139f | 9 | * ---------------------------------------------------------- |
8638fc66 | 10 | * |
2b6662ba | 11 | * Squid is the result of efforts by numerous individuals from |
12 | * the Internet community; see the CONTRIBUTORS file for full | |
13 | * details. Many organizations have provided support for Squid's | |
14 | * development; see the SPONSORS file for full details. Squid is | |
15 | * Copyrighted (C) 2001 by the Regents of the University of | |
16 | * California; see the COPYRIGHT file for full details. Squid | |
17 | * incorporates software developed and/or copyrighted by other | |
18 | * sources; see the CREDITS file for full details. | |
8638fc66 | 19 | * |
20 | * This program is free software; you can redistribute it and/or modify | |
21 | * it under the terms of the GNU General Public License as published by | |
22 | * the Free Software Foundation; either version 2 of the License, or | |
23 | * (at your option) any later version. | |
26ac0430 | 24 | * |
8638fc66 | 25 | * This program is distributed in the hope that it will be useful, |
26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
28 | * GNU General Public License for more details. | |
26ac0430 | 29 | * |
8638fc66 | 30 | * You should have received a copy of the GNU General Public License |
31 | * along with this program; if not, write to the Free Software | |
cbdec147 | 32 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. |
e25c139f | 33 | * |
8638fc66 | 34 | */ |
35 | ||
582c2af2 | 36 | #include "squid.h" |
a553a5a3 | 37 | #include "event.h" |
582c2af2 FC |
38 | #include "globals.h" |
39 | #include "md5.h" | |
40 | #include "protos.h" | |
e4f1fdae | 41 | #include "StatCounters.h" |
e6ccf245 | 42 | #include "Store.h" |
fc54b8d2 | 43 | #include "store_key_md5.h" |
d3b3ab85 | 44 | #include "SwapDir.h" |
c8f4eac4 | 45 | #include "StoreSearch.h" |
985c86bc | 46 | #include "SquidTime.h" |
f09f5b26 | 47 | |
21d845b1 FC |
48 | #if HAVE_ERRNO_H |
49 | #include <errno.h> | |
50 | #endif | |
b2c141d4 | 51 | static struct _store_rebuild_data counts; |
62e76326 | 52 | |
b2c141d4 | 53 | static struct timeval rebuild_start; |
9bea1d5b | 54 | static void storeCleanup(void *); |
f09f5b26 | 55 | |
26ac0430 | 56 | typedef struct { |
5a797987 | 57 | /* total number of "swap.state" entries that will be read */ |
58 | int total; | |
59 | /* number of entries read so far */ | |
60 | int scanned; | |
2fadd50d | 61 | } store_rebuild_progress; |
5a797987 | 62 | |
63 | static store_rebuild_progress *RebuildProgress = NULL; | |
64 | ||
68ea4c8b | 65 | static int |
9bea1d5b | 66 | storeCleanupDoubleCheck(StoreEntry * e) |
68ea4c8b | 67 | { |
c8f4eac4 | 68 | SwapDir *SD = dynamic_cast<SwapDir *>(INDEXSD(e->swap_dirn)); |
d3b3ab85 | 69 | return (SD->doubleCheck(*e)); |
68ea4c8b | 70 | } |
71 | ||
12784378 | 72 | static void |
9bea1d5b | 73 | storeCleanup(void *datanotused) |
f09f5b26 | 74 | { |
9bea1d5b | 75 | static int store_errors = 0; |
c8f4eac4 | 76 | static StoreSearchPointer currentSearch; |
77 | static int validated = 0; | |
78 | ||
79 | if (currentSearch == NULL || currentSearch->isDone()) | |
80 | currentSearch = Store::Root().search(NULL, NULL); | |
81 | ||
82 | size_t statCount = 500; | |
83 | ||
26031546 | 84 | // TODO: Avoid the loop (and ENTRY_VALIDATED) unless opt_store_doublecheck. |
c8f4eac4 | 85 | while (statCount-- && !currentSearch->isDone() && currentSearch->next()) { |
c8f4eac4 | 86 | StoreEntry *e; |
87 | ||
88 | e = currentSearch->currentItem(); | |
89 | ||
90 | if (EBIT_TEST(e->flags, ENTRY_VALIDATED)) | |
91 | continue; | |
92 | ||
93 | /* | |
5f33b71d | 94 | * Calling StoreEntry->release() has no effect because we're |
c8f4eac4 | 95 | * still in 'store_rebuilding' state |
96 | */ | |
97 | if (e->swap_filen < 0) | |
98 | continue; | |
99 | ||
100 | if (opt_store_doublecheck) | |
101 | if (storeCleanupDoubleCheck(e)) | |
5db6bf73 | 102 | ++store_errors; |
c8f4eac4 | 103 | |
104 | EBIT_SET(e->flags, ENTRY_VALIDATED); | |
105 | ||
106 | /* | |
107 | * Only set the file bit if we know its a valid entry | |
108 | * otherwise, set it in the validation procedure | |
109 | */ | |
e2851fe7 | 110 | |
c8f4eac4 | 111 | if ((++validated & 0x3FFFF) == 0) |
112 | /* TODO format the int with with a stream operator */ | |
e0236918 | 113 | debugs(20, DBG_IMPORTANT, " " << validated << " Entries Validated so far."); |
f09f5b26 | 114 | } |
62e76326 | 115 | |
c8f4eac4 | 116 | if (currentSearch->isDone()) { |
e0236918 FC |
117 | debugs(20, DBG_IMPORTANT, " Completed Validation Procedure"); |
118 | debugs(20, DBG_IMPORTANT, " Validated " << validated << " Entries"); | |
119 | debugs(20, DBG_IMPORTANT, " store_swap_size = " << Store::Root().currentSize() / 1024.0 << " KB"); | |
5e263176 | 120 | --StoreController::store_dirs_rebuilding; |
bef81ea5 | 121 | assert(0 == StoreController::store_dirs_rebuilding); |
c8f4eac4 | 122 | |
2f6fcab1 AR |
123 | if (opt_store_doublecheck && store_errors) { |
124 | fatalf("Quitting after finding %d cache index inconsistencies. " \ | |
125 | "Removing cache index will force its slow rebuild. " \ | |
126 | "Removing -S will let Squid start with an inconsistent " \ | |
127 | "cache index (at your own risk).\n", store_errors); | |
128 | } | |
c8f4eac4 | 129 | |
130 | if (store_digest) | |
131 | storeDigestNoteStoreReady(); | |
132 | ||
133 | currentSearch = NULL; | |
134 | } else | |
135 | eventAdd("storeCleanup", storeCleanup, NULL, 0.0, 1); | |
f09f5b26 | 136 | } |
137 | ||
f09f5b26 | 138 | /* meta data recreated from disk image in swap directory */ |
b2c141d4 | 139 | void |
62e76326 | 140 | |
9bea1d5b | 141 | storeRebuildComplete(struct _store_rebuild_data *dc) |
f09f5b26 | 142 | { |
9bea1d5b | 143 | double dt; |
144 | counts.objcount += dc->objcount; | |
145 | counts.expcount += dc->expcount; | |
146 | counts.scancount += dc->scancount; | |
147 | counts.clashcount += dc->clashcount; | |
148 | counts.dupcount += dc->dupcount; | |
149 | counts.cancelcount += dc->cancelcount; | |
150 | counts.invalid += dc->invalid; | |
151 | counts.badflags += dc->badflags; | |
152 | counts.bad_log_op += dc->bad_log_op; | |
153 | counts.zero_object_sz += dc->zero_object_sz; | |
154 | /* | |
b07b21cc | 155 | * When store_dirs_rebuilding == 1, it means we are done reading |
9bea1d5b | 156 | * or scanning all cache_dirs. Now report the stats and start |
157 | * the validation (storeCleanup()) thread. | |
158 | */ | |
62e76326 | 159 | |
e4b1808b | 160 | if (StoreController::store_dirs_rebuilding > 1) |
62e76326 | 161 | return; |
162 | ||
9bea1d5b | 163 | dt = tvSubDsec(rebuild_start, current_time); |
62e76326 | 164 | |
e0236918 FC |
165 | debugs(20, DBG_IMPORTANT, "Finished rebuilding storage from disk."); |
166 | debugs(20, DBG_IMPORTANT, " " << std::setw(7) << counts.scancount << " Entries scanned"); | |
167 | debugs(20, DBG_IMPORTANT, " " << std::setw(7) << counts.invalid << " Invalid entries."); | |
168 | debugs(20, DBG_IMPORTANT, " " << std::setw(7) << counts.badflags << " With invalid flags."); | |
169 | debugs(20, DBG_IMPORTANT, " " << std::setw(7) << counts.objcount << " Objects loaded."); | |
170 | debugs(20, DBG_IMPORTANT, " " << std::setw(7) << counts.expcount << " Objects expired."); | |
171 | debugs(20, DBG_IMPORTANT, " " << std::setw(7) << counts.cancelcount << " Objects cancelled."); | |
172 | debugs(20, DBG_IMPORTANT, " " << std::setw(7) << counts.dupcount << " Duplicate URLs purged."); | |
173 | debugs(20, DBG_IMPORTANT, " " << std::setw(7) << counts.clashcount << " Swapfile clashes avoided."); | |
174 | debugs(20, DBG_IMPORTANT, " Took "<< std::setw(3)<< std::setprecision(2) << dt << " seconds ("<< std::setw(6) << | |
bf8fe701 | 175 | ((double) counts.objcount / (dt > 0.0 ? dt : 1.0)) << " objects/sec)."); |
e0236918 | 176 | debugs(20, DBG_IMPORTANT, "Beginning Validation Procedure"); |
62e76326 | 177 | |
9bea1d5b | 178 | eventAdd("storeCleanup", storeCleanup, NULL, 0.0, 1); |
62e76326 | 179 | |
9bea1d5b | 180 | xfree(RebuildProgress); |
62e76326 | 181 | |
9bea1d5b | 182 | RebuildProgress = NULL; |
f09f5b26 | 183 | } |
184 | ||
b2c141d4 | 185 | /* |
186 | * this is ugly. We don't actually start any rebuild threads here, | |
187 | * but only initialize counters, etc. The rebuild threads are | |
188 | * actually started by the filesystem "fooDirInit" function. | |
189 | */ | |
f09f5b26 | 190 | void |
9bea1d5b | 191 | storeRebuildStart(void) |
f09f5b26 | 192 | { |
9bea1d5b | 193 | memset(&counts, '\0', sizeof(counts)); |
194 | rebuild_start = current_time; | |
195 | /* | |
b07b21cc | 196 | * Note: store_dirs_rebuilding is initialized to 1. |
26ac0430 AJ |
197 | * |
198 | * When we parse the configuration and construct each swap dir, | |
bef81ea5 | 199 | * the construction of that raises the rebuild count. |
200 | * | |
9bea1d5b | 201 | * This prevents us from trying to write clean logs until we |
bef81ea5 | 202 | * finished rebuilding - including after a reconfiguration that opens an |
203 | * existing swapdir. The corresponding decrement * occurs in | |
204 | * storeCleanup(), when it is finished. | |
9bea1d5b | 205 | */ |
e6ccf245 | 206 | RebuildProgress = (store_rebuild_progress *)xcalloc(Config.cacheSwap.n_configured, |
62e76326 | 207 | sizeof(store_rebuild_progress)); |
5a797987 | 208 | } |
209 | ||
210 | /* | |
211 | * A fs-specific rebuild procedure periodically reports its | |
212 | * progress. | |
213 | */ | |
214 | void | |
9bea1d5b | 215 | storeRebuildProgress(int sd_index, int total, int sofar) |
5a797987 | 216 | { |
9bea1d5b | 217 | static time_t last_report = 0; |
218 | double n = 0.0; | |
219 | double d = 0.0; | |
62e76326 | 220 | |
9bea1d5b | 221 | if (sd_index < 0) |
62e76326 | 222 | return; |
223 | ||
9bea1d5b | 224 | if (sd_index >= Config.cacheSwap.n_configured) |
62e76326 | 225 | return; |
226 | ||
9bea1d5b | 227 | if (NULL == RebuildProgress) |
62e76326 | 228 | return; |
229 | ||
9bea1d5b | 230 | RebuildProgress[sd_index].total = total; |
62e76326 | 231 | |
9bea1d5b | 232 | RebuildProgress[sd_index].scanned = sofar; |
62e76326 | 233 | |
9bea1d5b | 234 | if (squid_curtime - last_report < 15) |
62e76326 | 235 | return; |
236 | ||
5db6bf73 | 237 | for (sd_index = 0; sd_index < Config.cacheSwap.n_configured; ++sd_index) { |
62e76326 | 238 | n += (double) RebuildProgress[sd_index].scanned; |
239 | d += (double) RebuildProgress[sd_index].total; | |
5a797987 | 240 | } |
62e76326 | 241 | |
e0236918 | 242 | debugs(20, DBG_IMPORTANT, "Store rebuilding is "<< std::setw(4)<< std::setprecision(2) << 100.0 * n / d << "% complete"); |
9bea1d5b | 243 | last_report = squid_curtime; |
f09f5b26 | 244 | } |
e2851fe7 AR |
245 | |
246 | #include "fde.h" | |
247 | #include "StoreMetaUnpacker.h" | |
248 | #include "StoreMeta.h" | |
249 | #include "Generic.h" | |
250 | ||
251 | struct InitStoreEntry : public unary_function<StoreMeta, void> { | |
252 | InitStoreEntry(StoreEntry *anEntry, cache_key *aKey):what(anEntry),index(aKey) {} | |
253 | ||
254 | void operator()(StoreMeta const &x) { | |
255 | switch (x.getType()) { | |
256 | ||
257 | case STORE_META_KEY: | |
258 | assert(x.length == SQUID_MD5_DIGEST_LENGTH); | |
259 | memcpy(index, x.value, SQUID_MD5_DIGEST_LENGTH); | |
260 | break; | |
261 | ||
262 | case STORE_META_STD: | |
263 | struct old_metahdr { | |
264 | time_t timestamp; | |
265 | time_t lastref; | |
266 | time_t expires; | |
267 | time_t lastmod; | |
268 | size_t swap_file_sz; | |
89924985 AR |
269 | uint16_t refcount; |
270 | uint16_t flags; | |
e2851fe7 AR |
271 | } *tmp; |
272 | tmp = (struct old_metahdr *)x.value; | |
273 | assert(x.length == STORE_HDR_METASIZE_OLD); | |
274 | what->timestamp = tmp->timestamp; | |
275 | what->lastref = tmp->lastref; | |
276 | what->expires = tmp->expires; | |
277 | what->lastmod = tmp->lastmod; | |
278 | what->swap_file_sz = tmp->swap_file_sz; | |
279 | what->refcount = tmp->refcount; | |
280 | what->flags = tmp->flags; | |
281 | break; | |
282 | ||
283 | case STORE_META_STD_LFS: | |
284 | assert(x.length == STORE_HDR_METASIZE); | |
285 | memcpy(&what->timestamp, x.value, STORE_HDR_METASIZE); | |
286 | break; | |
287 | ||
288 | default: | |
289 | break; | |
290 | } | |
291 | } | |
292 | ||
293 | StoreEntry *what; | |
294 | cache_key *index; | |
295 | }; | |
296 | ||
297 | bool | |
aa1a691e AR |
298 | storeRebuildLoadEntry(int fd, int diskIndex, MemBuf &buf, |
299 | struct _store_rebuild_data &counts) | |
e2851fe7 AR |
300 | { |
301 | if (fd < 0) | |
302 | return false; | |
303 | ||
aa1a691e | 304 | assert(buf.hasSpace()); // caller must allocate |
e2851fe7 | 305 | |
aa1a691e | 306 | const int len = FD_READ_METHOD(fd, buf.space(), buf.spaceSize()); |
5db6bf73 | 307 | ++ statCounter.syscalls.disk.reads; |
aa1a691e AR |
308 | if (len < 0) { |
309 | const int xerrno = errno; | |
28edca61 | 310 | debugs(47, DBG_IMPORTANT, "WARNING: cache_dir[" << diskIndex << "]: " << |
9199139f | 311 | "Ignoring cached entry after meta data read failure: " << xstrerr(xerrno)); |
e2851fe7 AR |
312 | return false; |
313 | } | |
314 | ||
aa1a691e AR |
315 | buf.appended(len); |
316 | return true; | |
317 | } | |
318 | ||
319 | bool | |
320 | storeRebuildParseEntry(MemBuf &buf, StoreEntry &tmpe, cache_key *key, | |
321 | struct _store_rebuild_data &counts, | |
322 | uint64_t expectedSize) | |
323 | { | |
e2851fe7 | 324 | int swap_hdr_len = 0; |
aa1a691e | 325 | StoreMetaUnpacker aBuilder(buf.content(), buf.contentSize(), &swap_hdr_len); |
e2851fe7 AR |
326 | if (aBuilder.isBufferZero()) { |
327 | debugs(47,5, HERE << "skipping empty record."); | |
328 | return false; | |
329 | } | |
330 | ||
331 | if (!aBuilder.isBufferSane()) { | |
28edca61 | 332 | debugs(47, DBG_IMPORTANT, "WARNING: Ignoring malformed cache entry."); |
e2851fe7 AR |
333 | return false; |
334 | } | |
335 | ||
336 | StoreMeta *tlv_list = aBuilder.createStoreMeta(); | |
337 | if (!tlv_list) { | |
28edca61 AR |
338 | debugs(47, DBG_IMPORTANT, "WARNING: Ignoring cache entry with invalid " << |
339 | "meta data"); | |
e2851fe7 AR |
340 | return false; |
341 | } | |
342 | ||
aa1a691e AR |
343 | // TODO: consume parsed metadata? |
344 | ||
e2851fe7 AR |
345 | debugs(47,7, HERE << "successful swap meta unpacking"); |
346 | memset(key, '\0', SQUID_MD5_DIGEST_LENGTH); | |
347 | ||
348 | InitStoreEntry visitor(&tmpe, key); | |
349 | for_each(*tlv_list, visitor); | |
350 | storeSwapTLVFree(tlv_list); | |
351 | tlv_list = NULL; | |
352 | ||
353 | if (storeKeyNull(key)) { | |
28edca61 | 354 | debugs(47, DBG_IMPORTANT, "WARNING: Ignoring keyless cache entry"); |
e2851fe7 AR |
355 | return false; |
356 | } | |
357 | ||
358 | tmpe.key = key; | |
359 | /* check sizes */ | |
360 | ||
361 | if (expectedSize > 0) { | |
362 | if (tmpe.swap_file_sz == 0) { | |
363 | tmpe.swap_file_sz = expectedSize; | |
364 | } else if (tmpe.swap_file_sz == (uint64_t)(expectedSize - swap_hdr_len)) { | |
365 | tmpe.swap_file_sz = expectedSize; | |
366 | } else if (tmpe.swap_file_sz != expectedSize) { | |
28edca61 AR |
367 | debugs(47, DBG_IMPORTANT, "WARNING: Ignoring cache entry due to a " << |
368 | "SIZE MISMATCH " << tmpe.swap_file_sz << "!=" << expectedSize); | |
e2851fe7 AR |
369 | return false; |
370 | } | |
9199139f | 371 | } else if (tmpe.swap_file_sz <= 0) { |
28edca61 AR |
372 | debugs(47, DBG_IMPORTANT, "WARNING: Ignoring cache entry with " << |
373 | "unknown size: " << tmpe); | |
aa1a691e | 374 | return false; |
e2851fe7 AR |
375 | } |
376 | ||
377 | if (EBIT_TEST(tmpe.flags, KEY_PRIVATE)) { | |
5db6bf73 | 378 | ++ counts.badflags; |
e2851fe7 AR |
379 | return false; |
380 | } | |
381 | ||
382 | return true; | |
383 | } | |
384 | ||
385 | bool | |
386 | storeRebuildKeepEntry(const StoreEntry &tmpe, const cache_key *key, | |
387 | struct _store_rebuild_data &counts) | |
388 | { | |
389 | /* this needs to become | |
390 | * 1) unpack url | |
391 | * 2) make synthetic request with headers ?? or otherwise search | |
392 | * for a matching object in the store | |
393 | * TODO FIXME change to new async api | |
394 | * TODO FIXME I think there is a race condition here with the | |
395 | * async api : | |
396 | * store A reads in object foo, searchs for it, and finds nothing. | |
397 | * store B reads in object foo, searchs for it, finds nothing. | |
398 | * store A gets called back with nothing, so registers the object | |
399 | * store B gets called back with nothing, so registers the object, | |
400 | * which will conflict when the in core index gets around to scanning | |
401 | * store B. | |
402 | * | |
403 | * this suggests that rather than searching for duplicates, the | |
404 | * index rebuild should just assume its the most recent accurate | |
405 | * store entry and whoever indexes the stores handles duplicates. | |
406 | */ | |
407 | if (StoreEntry *e = Store::Root().get(key)) { | |
408 | ||
409 | if (e->lastref >= tmpe.lastref) { | |
410 | /* key already exists, old entry is newer */ | |
411 | /* keep old, ignore new */ | |
5db6bf73 | 412 | ++counts.dupcount; |
a46219b7 AR |
413 | |
414 | // For some stores, get() creates/unpacks a store entry. Signal | |
415 | // such stores that we will no longer use the get() result: | |
416 | e->lock(); | |
417 | e->unlock(); | |
418 | ||
e2851fe7 AR |
419 | return false; |
420 | } else { | |
421 | /* URL already exists, this swapfile not being used */ | |
422 | /* junk old, load new */ | |
423 | e->release(); /* release old entry */ | |
5db6bf73 | 424 | ++counts.dupcount; |
e2851fe7 AR |
425 | } |
426 | } | |
427 | ||
428 | return true; | |
429 | } |