]> git.ipfire.org Git - thirdparty/squid.git/blob - src/store_dir.cc
Merged from trunk 13172.
[thirdparty/squid.git] / src / store_dir.cc
1
2 /*
3 * DEBUG: section 47 Store Directory Routines
4 * AUTHOR: Duane Wessels
5 *
6 * SQUID Web Proxy Cache http://www.squid-cache.org/
7 * ----------------------------------------------------------
8 *
9 * Squid is the result of efforts by numerous individuals from
10 * the Internet community; see the CONTRIBUTORS file for full
11 * details. Many organizations have provided support for Squid's
12 * development; see the SPONSORS file for full details. Squid is
13 * Copyrighted (C) 2001 by the Regents of the University of
14 * California; see the COPYRIGHT file for full details. Squid
15 * incorporates software developed and/or copyrighted by other
16 * sources; see the CREDITS file for full details.
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
31 *
32 */
33
34 #include "squid.h"
35 #include "globals.h"
36 #include "mem_node.h"
37 #include "MemObject.h"
38 #include "MemStore.h"
39 #include "profiler/Profiler.h"
40 #include "SquidConfig.h"
41 #include "SquidMath.h"
42 #include "SquidTime.h"
43 #include "Store.h"
44 #include "store_key_md5.h"
45 #include "StoreHashIndex.h"
46 #include "swap_log_op.h"
47 #include "SwapDir.h"
48 #include "tools.h"
49 #include "Transients.h"
50
51 #if HAVE_STATVFS
52 #if HAVE_SYS_STATVFS_H
53 #include <sys/statvfs.h>
54 #endif
55 #endif /* HAVE_STATVFS */
56 /* statfs() needs <sys/param.h> and <sys/mount.h> on BSD systems */
57 #if HAVE_SYS_PARAM_H
58 #include <sys/param.h>
59 #endif
60 #if HAVE_LIMITS_H
61 #include <limits.h>
62 #endif
63 #if HAVE_SYS_MOUNT_H
64 #include <sys/mount.h>
65 #endif
66 /* Windows and Linux use sys/vfs.h */
67 #if HAVE_SYS_VFS_H
68 #include <sys/vfs.h>
69 #endif
70 #if HAVE_SYS_WAIT_H
71 #include <sys/wait.h>
72 #endif
73 #if HAVE_ERRNO_H
74 #include <errno.h>
75 #endif
76
77 static STDIRSELECT storeDirSelectSwapDirRoundRobin;
78 static STDIRSELECT storeDirSelectSwapDirLeastLoad;
79
80 /*
81 * store_dirs_rebuilding is initialized to _1_ as a hack so that
82 * storeDirWriteCleanLogs() doesn't try to do anything unless _all_
83 * cache_dirs have been read. For example, without this hack, Squid
84 * will try to write clean log files if -kparse fails (becasue it
85 * calls fatal()).
86 */
87 int StoreController::store_dirs_rebuilding = 1;
88
89 StoreController::StoreController() : swapDir (new StoreHashIndex())
90 , memStore(NULL), transients(NULL)
91 {}
92
93 StoreController::~StoreController()
94 {
95 delete memStore;
96 delete transients;
97 }
98
99 /*
100 * This function pointer is set according to 'store_dir_select_algorithm'
101 * in squid.conf.
102 */
103 STDIRSELECT *storeDirSelectSwapDir = storeDirSelectSwapDirLeastLoad;
104
105 void
106 StoreController::init()
107 {
108 if (Config.memShared && IamWorkerProcess()) {
109 memStore = new MemStore;
110 memStore->init();
111 }
112
113 swapDir->init();
114
115 if (0 == strcasecmp(Config.store_dir_select_algorithm, "round-robin")) {
116 storeDirSelectSwapDir = storeDirSelectSwapDirRoundRobin;
117 debugs(47, DBG_IMPORTANT, "Using Round Robin store dir selection");
118 } else {
119 storeDirSelectSwapDir = storeDirSelectSwapDirLeastLoad;
120 debugs(47, DBG_IMPORTANT, "Using Least Load store dir selection");
121 }
122
123 if (UsingSmp() && IamWorkerProcess() && Config.onoff.collapsed_forwarding) {
124 transients = new Transients;
125 transients->init();
126 }
127 }
128
129 void
130 StoreController::createOneStore(Store &aStore)
131 {
132 /*
133 * On Windows, fork() is not available.
134 * The following is a workaround for create store directories sequentially
135 * when running on native Windows port.
136 */
137 #if !_SQUID_WINDOWS_
138
139 if (fork())
140 return;
141
142 #endif
143
144 aStore.create();
145
146 #if !_SQUID_WINDOWS_
147
148 exit(0);
149
150 #endif
151 }
152
153 void
154 StoreController::create()
155 {
156 swapDir->create();
157
158 #if !_SQUID_WINDOWS_
159
160 pid_t pid;
161
162 do {
163 int status;
164 #if _SQUID_NEXT_
165
166 pid = wait3(&status, WNOHANG, NULL);
167 #else
168
169 pid = waitpid(-1, &status, 0);
170 #endif
171
172 } while (pid > 0 || (pid < 0 && errno == EINTR));
173
174 #endif
175 }
176
177 /**
178 * Determine whether the given directory can handle this object
179 * size
180 *
181 * Note: if the object size is -1, then the only swapdirs that
182 * will return true here are ones that have min and max unset,
183 * ie any-sized-object swapdirs. This is a good thing.
184 */
185 bool
186 SwapDir::objectSizeIsAcceptable(int64_t objsize) const
187 {
188 // If the swapdir has no range limits, then it definitely can
189 if (min_objsize <= 0 && max_objsize == -1)
190 return true;
191
192 /*
193 * If the object size is -1 and the storedir has limits we
194 * can't store it there.
195 */
196 if (objsize == -1)
197 return false;
198
199 // Else, make sure that the object size will fit.
200 if (max_objsize == -1 && min_objsize <= objsize)
201 return true;
202 else
203 return min_objsize <= objsize && max_objsize > objsize;
204 }
205
206 /*
207 * This new selection scheme simply does round-robin on all SwapDirs.
208 * A SwapDir is skipped if it is over the max_size (100%) limit, or
209 * overloaded.
210 */
211 static int
212 storeDirSelectSwapDirRoundRobin(const StoreEntry * e)
213 {
214 static int dirn = 0;
215 int i;
216 int load;
217 RefCount<SwapDir> sd;
218
219 // e->objectLen() is negative at this point when we are still STORE_PENDING
220 ssize_t objsize = e->mem_obj->expectedReplySize();
221 if (objsize != -1)
222 objsize += e->mem_obj->swap_hdr_sz;
223
224 for (i = 0; i < Config.cacheSwap.n_configured; ++i) {
225 if (++dirn >= Config.cacheSwap.n_configured)
226 dirn = 0;
227
228 sd = dynamic_cast<SwapDir *>(INDEXSD(dirn));
229
230 if (!sd->canStore(*e, objsize, load))
231 continue;
232
233 if (load < 0 || load > 1000) {
234 continue;
235 }
236
237 return dirn;
238 }
239
240 return -1;
241 }
242
243 /*
244 * Spread load across all of the store directories
245 *
246 * Note: We should modify this later on to prefer sticking objects
247 * in the *tightest fit* swapdir to conserve space, along with the
248 * actual swapdir usage. But for now, this hack will do while
249 * testing, so you should order your swapdirs in the config file
250 * from smallest max-size= to largest max-size=.
251 *
252 * We also have to choose nleast == nconf since we need to consider
253 * ALL swapdirs, regardless of state. Again, this is a hack while
254 * we sort out the real usefulness of this algorithm.
255 */
256 static int
257 storeDirSelectSwapDirLeastLoad(const StoreEntry * e)
258 {
259 int64_t most_free = 0;
260 ssize_t least_objsize = -1;
261 int least_load = INT_MAX;
262 int load;
263 int dirn = -1;
264 int i;
265 RefCount<SwapDir> SD;
266
267 // e->objectLen() is negative at this point when we are still STORE_PENDING
268 ssize_t objsize = e->mem_obj->expectedReplySize();
269
270 if (objsize != -1)
271 objsize += e->mem_obj->swap_hdr_sz;
272
273 for (i = 0; i < Config.cacheSwap.n_configured; ++i) {
274 SD = dynamic_cast<SwapDir *>(INDEXSD(i));
275 SD->flags.selected = false;
276
277 if (!SD->canStore(*e, objsize, load))
278 continue;
279
280 if (load < 0 || load > 1000)
281 continue;
282
283 if (load > least_load)
284 continue;
285
286 const int64_t cur_free = SD->maxSize() - SD->currentSize();
287
288 /* If the load is equal, then look in more details */
289 if (load == least_load) {
290 /* closest max-size fit */
291
292 if (least_objsize != -1)
293 if (SD->maxObjectSize() > least_objsize)
294 continue;
295
296 /* most free */
297 if (cur_free < most_free)
298 continue;
299 }
300
301 least_load = load;
302 least_objsize = SD->maxObjectSize();
303 most_free = cur_free;
304 dirn = i;
305 }
306
307 if (dirn >= 0)
308 dynamic_cast<SwapDir *>(INDEXSD(dirn))->flags.selected = true;
309
310 return dirn;
311 }
312
313 /*
314 * An entry written to the swap log MUST have the following
315 * properties.
316 * 1. It MUST be a public key. It does no good to log
317 * a public ADD, change the key, then log a private
318 * DEL. So we need to log a DEL before we change a
319 * key from public to private.
320 * 2. It MUST have a valid (> -1) swap_filen.
321 */
322 void
323 storeDirSwapLog(const StoreEntry * e, int op)
324 {
325 assert (e);
326 assert(!EBIT_TEST(e->flags, KEY_PRIVATE));
327 assert(e->swap_filen >= 0);
328 /*
329 * icons and such; don't write them to the swap log
330 */
331
332 if (EBIT_TEST(e->flags, ENTRY_SPECIAL))
333 return;
334
335 assert(op > SWAP_LOG_NOP && op < SWAP_LOG_MAX);
336
337 debugs(20, 3, "storeDirSwapLog: " <<
338 swap_log_op_str[op] << " " <<
339 e->getMD5Text() << " " <<
340 e->swap_dirn << " " <<
341 std::hex << std::uppercase << std::setfill('0') << std::setw(8) << e->swap_filen);
342
343 dynamic_cast<SwapDir *>(INDEXSD(e->swap_dirn))->logEntry(*e, op);
344 }
345
346 void
347 StoreController::getStats(StoreInfoStats &stats) const
348 {
349 if (memStore)
350 memStore->getStats(stats);
351 else {
352 // move this code to a non-shared memory cache class when we have it
353 stats.mem.shared = false;
354 stats.mem.capacity = Config.memMaxSize;
355 stats.mem.size = mem_node::StoreMemSize();
356 stats.mem.count = hot_obj_count;
357 }
358
359 swapDir->getStats(stats);
360
361 // low-level info not specific to memory or disk cache
362 stats.store_entry_count = StoreEntry::inUseCount();
363 stats.mem_object_count = MemObject::inUseCount();
364 }
365
366 void
367 StoreController::stat(StoreEntry &output) const
368 {
369 storeAppendPrintf(&output, "Store Directory Statistics:\n");
370 storeAppendPrintf(&output, "Store Entries : %lu\n",
371 (unsigned long int)StoreEntry::inUseCount());
372 storeAppendPrintf(&output, "Maximum Swap Size : %" PRIu64 " KB\n",
373 maxSize() >> 10);
374 storeAppendPrintf(&output, "Current Store Swap Size: %.2f KB\n",
375 currentSize() / 1024.0);
376 storeAppendPrintf(&output, "Current Capacity : %.2f%% used, %.2f%% free\n",
377 Math::doublePercent(currentSize(), maxSize()),
378 Math::doublePercent((maxSize() - currentSize()), maxSize()));
379
380 if (memStore)
381 memStore->stat(output);
382
383 /* now the swapDir */
384 swapDir->stat(output);
385 }
386
387 /* if needed, this could be taught to cache the result */
388 uint64_t
389 StoreController::maxSize() const
390 {
391 /* TODO: include memory cache ? */
392 return swapDir->maxSize();
393 }
394
395 uint64_t
396 StoreController::minSize() const
397 {
398 /* TODO: include memory cache ? */
399 return swapDir->minSize();
400 }
401
402 uint64_t
403 StoreController::currentSize() const
404 {
405 return swapDir->currentSize();
406 }
407
408 uint64_t
409 StoreController::currentCount() const
410 {
411 return swapDir->currentCount();
412 }
413
414 int64_t
415 StoreController::maxObjectSize() const
416 {
417 return swapDir->maxObjectSize();
418 }
419
420 void
421 SwapDir::diskFull()
422 {
423 if (currentSize() >= maxSize())
424 return;
425
426 max_size = currentSize();
427
428 debugs(20, DBG_IMPORTANT, "WARNING: Shrinking cache_dir #" << index << " to " << currentSize() / 1024.0 << " KB");
429 }
430
431 void
432 storeDirOpenSwapLogs(void)
433 {
434 for (int dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn)
435 dynamic_cast<SwapDir *>(INDEXSD(dirn))->openLog();
436 }
437
438 void
439 storeDirCloseSwapLogs(void)
440 {
441 for (int dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn)
442 dynamic_cast<SwapDir *>(INDEXSD(dirn))->closeLog();
443 }
444
445 /*
446 * storeDirWriteCleanLogs
447 *
448 * Writes a "clean" swap log file from in-memory metadata.
449 * This is a rewrite of the original function to troll each
450 * StoreDir and write the logs, and flush at the end of
451 * the run. Thanks goes to Eric Stern, since this solution
452 * came out of his COSS code.
453 */
454 int
455 storeDirWriteCleanLogs(int reopen)
456 {
457 const StoreEntry *e = NULL;
458 int n = 0;
459
460 struct timeval start;
461 double dt;
462 RefCount<SwapDir> sd;
463 int dirn;
464 int notdone = 1;
465
466 if (StoreController::store_dirs_rebuilding) {
467 debugs(20, DBG_IMPORTANT, "Not currently OK to rewrite swap log.");
468 debugs(20, DBG_IMPORTANT, "storeDirWriteCleanLogs: Operation aborted.");
469 return 0;
470 }
471
472 debugs(20, DBG_IMPORTANT, "storeDirWriteCleanLogs: Starting...");
473 getCurrentTime();
474 start = current_time;
475
476 for (dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn) {
477 sd = dynamic_cast<SwapDir *>(INDEXSD(dirn));
478
479 if (sd->writeCleanStart() < 0) {
480 debugs(20, DBG_IMPORTANT, "log.clean.start() failed for dir #" << sd->index);
481 continue;
482 }
483 }
484
485 /*
486 * This may look inefficient as CPU wise it is more efficient to do this
487 * sequentially, but I/O wise the parallellism helps as it allows more
488 * hdd spindles to be active.
489 */
490 while (notdone) {
491 notdone = 0;
492
493 for (dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn) {
494 sd = dynamic_cast<SwapDir *>(INDEXSD(dirn));
495
496 if (NULL == sd->cleanLog)
497 continue;
498
499 e = sd->cleanLog->nextEntry();
500
501 if (!e)
502 continue;
503
504 notdone = 1;
505
506 if (!sd->canLog(*e))
507 continue;
508
509 sd->cleanLog->write(*e);
510
511 if ((++n & 0xFFFF) == 0) {
512 getCurrentTime();
513 debugs(20, DBG_IMPORTANT, " " << std::setw(7) << n <<
514 " entries written so far.");
515 }
516 }
517 }
518
519 /* Flush */
520 for (dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn)
521 dynamic_cast<SwapDir *>(INDEXSD(dirn))->writeCleanDone();
522
523 if (reopen)
524 storeDirOpenSwapLogs();
525
526 getCurrentTime();
527
528 dt = tvSubDsec(start, current_time);
529
530 debugs(20, DBG_IMPORTANT, " Finished. Wrote " << n << " entries.");
531 debugs(20, DBG_IMPORTANT, " Took "<< std::setw(3)<< std::setprecision(2) << dt <<
532 " seconds ("<< std::setw(6) << ((double) n / (dt > 0.0 ? dt : 1.0)) << " entries/sec).");
533
534 return n;
535 }
536
537 StoreSearch *
538 StoreController::search(String const url, HttpRequest *request)
539 {
540 /* cheat, for now you can't search the memory hot cache */
541 return swapDir->search(url, request);
542 }
543
544 StorePointer
545 StoreHashIndex::store(int const x) const
546 {
547 return INDEXSD(x);
548 }
549
550 SwapDir &
551 StoreHashIndex::dir(const int i) const
552 {
553 SwapDir *sd = dynamic_cast<SwapDir*>(INDEXSD(i));
554 assert(sd);
555 return *sd;
556 }
557
558 void
559 StoreController::sync(void)
560 {
561 if (memStore)
562 memStore->sync();
563 swapDir->sync();
564 }
565
566 /*
567 * handle callbacks all avaliable fs'es
568 */
569 int
570 StoreController::callback()
571 {
572 /* This will likely double count. Thats ok. */
573 PROF_start(storeDirCallback);
574
575 /* mem cache callbacks ? */
576 int result = swapDir->callback();
577
578 PROF_stop(storeDirCallback);
579
580 return result;
581 }
582
583 int
584 storeDirGetBlkSize(const char *path, int *blksize)
585 {
586 #if HAVE_STATVFS
587
588 struct statvfs sfs;
589
590 if (statvfs(path, &sfs)) {
591 debugs(50, DBG_IMPORTANT, "" << path << ": " << xstrerror());
592 *blksize = 2048;
593 return 1;
594 }
595
596 *blksize = (int) sfs.f_frsize;
597 #else
598
599 struct statfs sfs;
600
601 if (statfs(path, &sfs)) {
602 debugs(50, DBG_IMPORTANT, "" << path << ": " << xstrerror());
603 *blksize = 2048;
604 return 1;
605 }
606
607 *blksize = (int) sfs.f_bsize;
608 #endif
609 /*
610 * Sanity check; make sure we have a meaningful value.
611 */
612
613 if (*blksize < 512)
614 *blksize = 2048;
615
616 return 0;
617 }
618
619 #define fsbtoblk(num, fsbs, bs) \
620 (((fsbs) != 0 && (fsbs) < (bs)) ? \
621 (num) / ((bs) / (fsbs)) : (num) * ((fsbs) / (bs)))
622 int
623 storeDirGetUFSStats(const char *path, int *totl_kb, int *free_kb, int *totl_in, int *free_in)
624 {
625 #if HAVE_STATVFS
626
627 struct statvfs sfs;
628
629 if (statvfs(path, &sfs)) {
630 debugs(50, DBG_IMPORTANT, "" << path << ": " << xstrerror());
631 return 1;
632 }
633
634 *totl_kb = (int) fsbtoblk(sfs.f_blocks, sfs.f_frsize, 1024);
635 *free_kb = (int) fsbtoblk(sfs.f_bfree, sfs.f_frsize, 1024);
636 *totl_in = (int) sfs.f_files;
637 *free_in = (int) sfs.f_ffree;
638 #else
639
640 struct statfs sfs;
641
642 if (statfs(path, &sfs)) {
643 debugs(50, DBG_IMPORTANT, "" << path << ": " << xstrerror());
644 return 1;
645 }
646
647 *totl_kb = (int) fsbtoblk(sfs.f_blocks, sfs.f_bsize, 1024);
648 *free_kb = (int) fsbtoblk(sfs.f_bfree, sfs.f_bsize, 1024);
649 *totl_in = (int) sfs.f_files;
650 *free_in = (int) sfs.f_ffree;
651 #endif
652
653 return 0;
654 }
655
656 void
657 allocate_new_swapdir(SquidConfig::_cacheSwap * swap)
658 {
659 if (swap->swapDirs == NULL) {
660 swap->n_allocated = 4;
661 swap->swapDirs = static_cast<SwapDir::Pointer *>(xcalloc(swap->n_allocated, sizeof(SwapDir::Pointer)));
662 }
663
664 if (swap->n_allocated == swap->n_configured) {
665 swap->n_allocated <<= 1;
666 SwapDir::Pointer *const tmp = static_cast<SwapDir::Pointer *>(xcalloc(swap->n_allocated, sizeof(SwapDir::Pointer)));
667 memcpy(tmp, swap->swapDirs, swap->n_configured * sizeof(SwapDir *));
668 xfree(swap->swapDirs);
669 swap->swapDirs = tmp;
670 }
671 }
672
673 void
674 free_cachedir(SquidConfig::_cacheSwap * swap)
675 {
676 int i;
677 /* DON'T FREE THESE FOR RECONFIGURE */
678
679 if (reconfiguring)
680 return;
681
682 for (i = 0; i < swap->n_configured; ++i) {
683 /* TODO XXX this lets the swapdir free resources asynchronously
684 * swap->swapDirs[i]->deactivate();
685 * but there may be such a means already.
686 * RBC 20041225
687 */
688 swap->swapDirs[i] = NULL;
689 }
690
691 safe_free(swap->swapDirs);
692 swap->swapDirs = NULL;
693 swap->n_allocated = 0;
694 swap->n_configured = 0;
695 }
696
697 /* this should be a virtual method on StoreEntry,
698 * i.e. e->referenced()
699 * so that the entry can notify the creating Store
700 */
701 void
702 StoreController::reference(StoreEntry &e)
703 {
704 // special entries do not belong to any specific Store, but are IN_MEMORY
705 if (EBIT_TEST(e.flags, ENTRY_SPECIAL))
706 return;
707
708 /* Notify the fs that we're referencing this object again */
709
710 if (e.swap_dirn > -1)
711 swapDir->reference(e);
712
713 // Notify the memory cache that we're referencing this object again
714 if (memStore && e.mem_status == IN_MEMORY)
715 memStore->reference(e);
716
717 // TODO: move this code to a non-shared memory cache class when we have it
718 if (e.mem_obj) {
719 if (mem_policy->Referenced)
720 mem_policy->Referenced(mem_policy, &e, &e.mem_obj->repl);
721 }
722 }
723
724 bool
725 StoreController::dereference(StoreEntry &e, bool wantsLocalMemory)
726 {
727 // special entries do not belong to any specific Store, but are IN_MEMORY
728 if (EBIT_TEST(e.flags, ENTRY_SPECIAL))
729 return true;
730
731 bool keepInStoreTable = false; // keep only if somebody needs it there
732
733 /* Notify the fs that we're not referencing this object any more */
734
735 if (e.swap_filen > -1)
736 keepInStoreTable = swapDir->dereference(e, wantsLocalMemory) || keepInStoreTable;
737
738 // Notify the memory cache that we're not referencing this object any more
739 if (memStore && e.mem_status == IN_MEMORY)
740 keepInStoreTable = memStore->dereference(e, wantsLocalMemory) || keepInStoreTable;
741
742 // TODO: move this code to a non-shared memory cache class when we have it
743 if (e.mem_obj) {
744 if (mem_policy->Dereferenced)
745 mem_policy->Dereferenced(mem_policy, &e, &e.mem_obj->repl);
746 // non-shared memory cache relies on store_table
747 if (!memStore)
748 keepInStoreTable = wantsLocalMemory || keepInStoreTable;
749 }
750
751 return keepInStoreTable;
752 }
753
754 StoreEntry *
755 StoreController::get(const cache_key *key)
756 {
757 if (StoreEntry *e = find(key)) {
758 // this is not very precise: some get()s are not initiated by clients
759 e->touch();
760 return e;
761 }
762 return NULL;
763 }
764
765 /// Internal method to implements the guts of the Store::get() API:
766 /// returns an in-transit or cached object with a given key, if any.
767 StoreEntry *
768 StoreController::find(const cache_key *key)
769 {
770 if (StoreEntry *e = swapDir->get(key)) {
771 // TODO: ignore and maybe handleIdleEntry() unlocked intransit entries
772 // because their backing store slot may be gone already.
773 debugs(20, 3, HERE << "got in-transit entry: " << *e);
774 return e;
775 }
776
777 // Must search transients before caches because we must sync those we find.
778 if (transients) {
779 if (StoreEntry *e = transients->get(key)) {
780 debugs(20, 3, "got shared in-transit entry: " << *e);
781 bool inSync = false;
782 const bool found = anchorCollapsed(*e, inSync);
783 if (!found || inSync)
784 return e;
785 assert(!e->locked()); // ensure release will destroyStoreEntry()
786 e->release(); // do not let others into the same trap
787 return NULL;
788 }
789 }
790
791 if (memStore) {
792 if (StoreEntry *e = memStore->get(key)) {
793 debugs(20, 3, HERE << "got mem-cached entry: " << *e);
794 return e;
795 }
796 }
797
798 // TODO: this disk iteration is misplaced; move to StoreHashIndex when
799 // the global store_table is no longer used for in-transit objects.
800 if (const int cacheDirs = Config.cacheSwap.n_configured) {
801 // ask each cache_dir until the entry is found; use static starting
802 // point to avoid asking the same subset of disks more often
803 // TODO: coordinate with put() to be able to guess the right disk often
804 static int idx = 0;
805 for (int n = 0; n < cacheDirs; ++n) {
806 idx = (idx + 1) % cacheDirs;
807 SwapDir *sd = dynamic_cast<SwapDir*>(INDEXSD(idx));
808 if (!sd->active())
809 continue;
810
811 if (StoreEntry *e = sd->get(key)) {
812 debugs(20, 3, HERE << "cache_dir " << idx <<
813 " got cached entry: " << *e);
814 return e;
815 }
816 }
817 }
818
819 debugs(20, 4, HERE << "none of " << Config.cacheSwap.n_configured <<
820 " cache_dirs have " << storeKeyText(key));
821
822 return NULL;
823 }
824
825 void
826 StoreController::get(String const key, STOREGETCLIENT aCallback, void *aCallbackData)
827 {
828 fatal("not implemented");
829 }
830
831 /// updates the collapsed entry with the corresponding on-disk entry, if any
832 /// In other words, the SwapDir::anchorCollapsed() API applied to all disks.
833 bool
834 StoreController::anchorCollapsedOnDisk(StoreEntry &collapsed, bool &inSync)
835 {
836 // TODO: move this loop to StoreHashIndex, just like the one in get().
837 if (const int cacheDirs = Config.cacheSwap.n_configured) {
838 // ask each cache_dir until the entry is found; use static starting
839 // point to avoid asking the same subset of disks more often
840 // TODO: coordinate with put() to be able to guess the right disk often
841 static int idx = 0;
842 for (int n = 0; n < cacheDirs; ++n) {
843 idx = (idx + 1) % cacheDirs;
844 SwapDir *sd = dynamic_cast<SwapDir*>(INDEXSD(idx));
845 if (!sd->active())
846 continue;
847
848 if (sd->anchorCollapsed(collapsed, inSync)) {
849 debugs(20, 3, "cache_dir " << idx << " anchors " << collapsed);
850 return true;
851 }
852 }
853 }
854
855 debugs(20, 4, "none of " << Config.cacheSwap.n_configured <<
856 " cache_dirs have " << collapsed);
857 return false;
858 }
859
860 void StoreController::markForUnlink(StoreEntry &e)
861 {
862 if (transients && e.mem_obj && e.mem_obj->xitTable.index >= 0)
863 transients->markForUnlink(e);
864 if (memStore && e.mem_obj && e.mem_obj->memCache.index >= 0)
865 memStore->markForUnlink(e);
866 if (e.swap_filen >= 0)
867 e.store()->markForUnlink(e);
868 }
869
870 // move this into [non-shared] memory cache class when we have one
871 /// whether e should be kept in local RAM for possible future caching
872 bool
873 StoreController::keepForLocalMemoryCache(const StoreEntry &e) const
874 {
875 if (!e.memoryCachable())
876 return false;
877
878 // does the current and expected size obey memory caching limits?
879 assert(e.mem_obj);
880 const int64_t loadedSize = e.mem_obj->endOffset();
881 const int64_t expectedSize = e.mem_obj->expectedReplySize(); // may be < 0
882 const int64_t ramSize = max(loadedSize, expectedSize);
883 const int64_t ramLimit = min(
884 static_cast<int64_t>(Config.memMaxSize),
885 static_cast<int64_t>(Config.Store.maxInMemObjSize));
886 return ramSize <= ramLimit;
887 }
888
889 void
890 StoreController::memoryOut(StoreEntry &e, const bool preserveSwappable)
891 {
892 bool keepInLocalMemory = false;
893 if (memStore)
894 memStore->write(e); // leave keepInLocalMemory false
895 else
896 keepInLocalMemory = keepForLocalMemoryCache(e);
897
898 debugs(20, 7, HERE << "keepInLocalMemory: " << keepInLocalMemory);
899
900 if (!keepInLocalMemory)
901 e.trimMemory(preserveSwappable);
902 }
903
904 void
905 StoreController::memoryUnlink(StoreEntry &e)
906 {
907 if (memStore)
908 memStore->unlink(e);
909 else // TODO: move into [non-shared] memory cache class when we have one
910 e.destroyMemObject();
911 }
912
913 void
914 StoreController::memoryDisconnect(StoreEntry &e)
915 {
916 if (memStore)
917 memStore->disconnect(e);
918 // else nothing to do for non-shared memory cache
919 }
920
921 void
922 StoreController::transientsAbandon(StoreEntry &e)
923 {
924 if (transients) {
925 assert(e.mem_obj);
926 if (e.mem_obj->xitTable.index >= 0)
927 transients->abandon(e);
928 }
929 }
930
931 void
932 StoreController::transientsCompleteWriting(StoreEntry &e)
933 {
934 if (transients) {
935 assert(e.mem_obj);
936 if (e.mem_obj->xitTable.index >= 0)
937 transients->completeWriting(e);
938 }
939 }
940
941 int
942 StoreController::transientReaders(const StoreEntry &e) const
943 {
944 return (transients && e.mem_obj && e.mem_obj->xitTable.index >= 0) ?
945 transients->readers(e) : 0;
946 }
947
948 void
949 StoreController::transientsDisconnect(MemObject &mem_obj)
950 {
951 if (transients)
952 transients->disconnect(mem_obj);
953 }
954
955 void
956 StoreController::handleIdleEntry(StoreEntry &e)
957 {
958 bool keepInLocalMemory = false;
959
960 if (EBIT_TEST(e.flags, ENTRY_SPECIAL)) {
961 // Icons (and cache digests?) should stay in store_table until we
962 // have a dedicated storage for them (that would not purge them).
963 // They are not managed [well] by any specific Store handled below.
964 keepInLocalMemory = true;
965 } else if (memStore) {
966 // leave keepInLocalMemory false; memStore maintains its own cache
967 } else {
968 keepInLocalMemory = keepForLocalMemoryCache(e) && // in good shape and
969 // the local memory cache is not overflowing
970 (mem_node::InUseCount() <= store_pages_max);
971 }
972
973 // An idle, unlocked entry that only belongs to a SwapDir which controls
974 // its own index, should not stay in the global store_table.
975 if (!dereference(e, keepInLocalMemory)) {
976 debugs(20, 5, HERE << "destroying unlocked entry: " << &e << ' ' << e);
977 destroyStoreEntry(static_cast<hash_link*>(&e));
978 return;
979 }
980
981 debugs(20, 5, HERE << "keepInLocalMemory: " << keepInLocalMemory);
982
983 // TODO: move this into [non-shared] memory cache class when we have one
984 if (keepInLocalMemory) {
985 e.setMemStatus(IN_MEMORY);
986 e.mem_obj->unlinkRequest();
987 } else {
988 e.purgeMem(); // may free e
989 }
990 }
991
992 void
993 StoreController::allowCollapsing(StoreEntry *e, const RequestFlags &reqFlags,
994 const HttpRequestMethod &reqMethod)
995 {
996 e->makePublic(); // this is needed for both local and SMP collapsing
997 if (transients)
998 transients->startWriting(e, reqFlags, reqMethod);
999 debugs(20, 3, "may " << (transients && e->mem_obj->xitTable.index >= 0 ?
1000 "SMP-" : "locally-") << "collapse " << *e);
1001 }
1002
1003 void
1004 StoreController::syncCollapsed(const sfileno xitIndex)
1005 {
1006 assert(transients);
1007
1008 StoreEntry *collapsed = transients->findCollapsed(xitIndex);
1009 if (!collapsed) { // the entry is no longer locally active, ignore update
1010 debugs(20, 7, "not SMP-syncing not-transient " << xitIndex);
1011 return;
1012 }
1013 assert(collapsed->mem_obj);
1014 assert(collapsed->mem_obj->smpCollapsed);
1015
1016 debugs(20, 7, "syncing " << *collapsed);
1017
1018 bool abandoned = transients->abandoned(*collapsed);
1019 bool found = false;
1020 bool inSync = false;
1021 if (memStore && collapsed->mem_obj->memCache.io == MemObject::ioDone) {
1022 found = true;
1023 inSync = true;
1024 debugs(20, 7, "fully mem-loaded " << *collapsed);
1025 } else if (memStore && collapsed->mem_obj->memCache.index >= 0) {
1026 found = true;
1027 inSync = memStore->updateCollapsed(*collapsed);
1028 } else if (collapsed->swap_filen >= 0) {
1029 found = true;
1030 inSync = collapsed->store()->updateCollapsed(*collapsed);
1031 } else {
1032 found = anchorCollapsed(*collapsed, inSync);
1033 }
1034
1035 if (abandoned && collapsed->store_status == STORE_PENDING) {
1036 debugs(20, 3, "aborting abandoned but STORE_PENDING " << *collapsed);
1037 collapsed->abort();
1038 return;
1039 }
1040
1041 if (inSync) {
1042 debugs(20, 5, "synced " << *collapsed);
1043 collapsed->invokeHandlers();
1044 } else if (found) { // unrecoverable problem syncing this entry
1045 debugs(20, 3, "aborting unsyncable " << *collapsed);
1046 collapsed->abort();
1047 } else { // the entry is still not in one of the caches
1048 debugs(20, 7, "waiting " << *collapsed);
1049 }
1050 }
1051
1052 /// Called for in-transit entries that are not yet anchored to a cache.
1053 /// For cached entries, return true after synchronizing them with their cache
1054 /// (making inSync true on success). For not-yet-cached entries, return false.
1055 bool
1056 StoreController::anchorCollapsed(StoreEntry &collapsed, bool &inSync)
1057 {
1058 // this method is designed to work with collapsed transients only
1059 assert(collapsed.mem_obj);
1060 assert(collapsed.mem_obj->xitTable.index >= 0);
1061 assert(collapsed.mem_obj->smpCollapsed);
1062
1063 debugs(20, 7, "anchoring " << collapsed);
1064
1065 bool found = false;
1066 if (memStore)
1067 found = memStore->anchorCollapsed(collapsed, inSync);
1068 else if (Config.cacheSwap.n_configured)
1069 found = anchorCollapsedOnDisk(collapsed, inSync);
1070
1071 if (found) {
1072 if (inSync)
1073 debugs(20, 7, "anchored " << collapsed);
1074 else
1075 debugs(20, 5, "failed to anchor " << collapsed);
1076 } else {
1077 debugs(20, 7, "skipping not yet cached " << collapsed);
1078 }
1079
1080 return found;
1081 }
1082
1083 StoreHashIndex::StoreHashIndex()
1084 {
1085 if (store_table)
1086 abort();
1087 assert (store_table == NULL);
1088 }
1089
1090 StoreHashIndex::~StoreHashIndex()
1091 {
1092 if (store_table) {
1093 hashFreeItems(store_table, destroyStoreEntry);
1094 hashFreeMemory(store_table);
1095 store_table = NULL;
1096 }
1097 }
1098
1099 int
1100 StoreHashIndex::callback()
1101 {
1102 int result = 0;
1103 int j;
1104 static int ndir = 0;
1105
1106 do {
1107 j = 0;
1108
1109 for (int i = 0; i < Config.cacheSwap.n_configured; ++i) {
1110 if (ndir >= Config.cacheSwap.n_configured)
1111 ndir = ndir % Config.cacheSwap.n_configured;
1112
1113 int temp_result = store(ndir)->callback();
1114
1115 ++ndir;
1116
1117 j += temp_result;
1118
1119 result += temp_result;
1120
1121 if (j > 100)
1122 fatal ("too much io\n");
1123 }
1124 } while (j > 0);
1125
1126 ++ndir;
1127
1128 return result;
1129 }
1130
1131 void
1132 StoreHashIndex::create()
1133 {
1134 if (Config.cacheSwap.n_configured == 0) {
1135 debugs(0, DBG_PARSE_NOTE(DBG_CRITICAL), "No cache_dir stores are configured.");
1136 }
1137
1138 for (int i = 0; i < Config.cacheSwap.n_configured; ++i) {
1139 if (dir(i).active())
1140 store(i)->create();
1141 }
1142 }
1143
1144 /* Lookup an object in the cache.
1145 * return just a reference to object, don't start swapping in yet. */
1146 StoreEntry *
1147 StoreHashIndex::get(const cache_key *key)
1148 {
1149 PROF_start(storeGet);
1150 debugs(20, 3, "storeGet: looking up " << storeKeyText(key));
1151 StoreEntry *p = static_cast<StoreEntry *>(hash_lookup(store_table, key));
1152 PROF_stop(storeGet);
1153 return p;
1154 }
1155
1156 void
1157 StoreHashIndex::get(String const key, STOREGETCLIENT aCallback, void *aCallbackData)
1158 {
1159 fatal("not implemented");
1160 }
1161
1162 void
1163 StoreHashIndex::init()
1164 {
1165 if (Config.Store.objectsPerBucket <= 0)
1166 fatal("'store_objects_per_bucket' should be larger than 0.");
1167
1168 if (Config.Store.avgObjectSize <= 0)
1169 fatal("'store_avg_object_size' should be larger than 0.");
1170
1171 /* Calculate size of hash table (maximum currently 64k buckets). */
1172 /* this is very bogus, its specific to the any Store maintaining an
1173 * in-core index, not global */
1174 size_t buckets = (Store::Root().maxSize() + Config.memMaxSize) / Config.Store.avgObjectSize;
1175 debugs(20, DBG_IMPORTANT, "Swap maxSize " << (Store::Root().maxSize() >> 10) <<
1176 " + " << ( Config.memMaxSize >> 10) << " KB, estimated " << buckets << " objects");
1177 buckets /= Config.Store.objectsPerBucket;
1178 debugs(20, DBG_IMPORTANT, "Target number of buckets: " << buckets);
1179 /* ideally the full scan period should be configurable, for the
1180 * moment it remains at approximately 24 hours. */
1181 store_hash_buckets = storeKeyHashBuckets(buckets);
1182 debugs(20, DBG_IMPORTANT, "Using " << store_hash_buckets << " Store buckets");
1183 debugs(20, DBG_IMPORTANT, "Max Mem size: " << ( Config.memMaxSize >> 10) << " KB" <<
1184 (Config.memShared ? " [shared]" : ""));
1185 debugs(20, DBG_IMPORTANT, "Max Swap size: " << (Store::Root().maxSize() >> 10) << " KB");
1186
1187 store_table = hash_create(storeKeyHashCmp,
1188 store_hash_buckets, storeKeyHashHash);
1189
1190 for (int i = 0; i < Config.cacheSwap.n_configured; ++i) {
1191 /* this starts a search of the store dirs, loading their
1192 * index. under the new Store api this should be
1193 * driven by the StoreHashIndex, not by each store.
1194 *
1195 * That is, the HashIndex should perform a search of each dir it is
1196 * indexing to do the hash insertions. The search is then able to
1197 * decide 'from-memory', or 'from-clean-log' or 'from-dirty-log' or
1198 * 'from-no-log'.
1199 *
1200 * Step 1: make the store rebuilds use a search internally
1201 * Step 2: change the search logic to use the four modes described
1202 * above
1203 * Step 3: have the hash index walk the searches itself.
1204 */
1205 if (dir(i).active())
1206 store(i)->init();
1207 }
1208 }
1209
1210 uint64_t
1211 StoreHashIndex::maxSize() const
1212 {
1213 uint64_t result = 0;
1214
1215 for (int i = 0; i < Config.cacheSwap.n_configured; ++i) {
1216 if (dir(i).doReportStat())
1217 result += store(i)->maxSize();
1218 }
1219
1220 return result;
1221 }
1222
1223 uint64_t
1224 StoreHashIndex::minSize() const
1225 {
1226 uint64_t result = 0;
1227
1228 for (int i = 0; i < Config.cacheSwap.n_configured; ++i) {
1229 if (dir(i).doReportStat())
1230 result += store(i)->minSize();
1231 }
1232
1233 return result;
1234 }
1235
1236 uint64_t
1237 StoreHashIndex::currentSize() const
1238 {
1239 uint64_t result = 0;
1240
1241 for (int i = 0; i < Config.cacheSwap.n_configured; ++i) {
1242 if (dir(i).doReportStat())
1243 result += store(i)->currentSize();
1244 }
1245
1246 return result;
1247 }
1248
1249 uint64_t
1250 StoreHashIndex::currentCount() const
1251 {
1252 uint64_t result = 0;
1253
1254 for (int i = 0; i < Config.cacheSwap.n_configured; ++i) {
1255 if (dir(i).doReportStat())
1256 result += store(i)->currentCount();
1257 }
1258
1259 return result;
1260 }
1261
1262 int64_t
1263 StoreHashIndex::maxObjectSize() const
1264 {
1265 int64_t result = -1;
1266
1267 for (int i = 0; i < Config.cacheSwap.n_configured; ++i) {
1268 if (dir(i).active() && store(i)->maxObjectSize() > result)
1269 result = store(i)->maxObjectSize();
1270 }
1271
1272 return result;
1273 }
1274
1275 void
1276 StoreHashIndex::getStats(StoreInfoStats &stats) const
1277 {
1278 // accumulate per-disk cache stats
1279 for (int i = 0; i < Config.cacheSwap.n_configured; ++i) {
1280 StoreInfoStats dirStats;
1281 store(i)->getStats(dirStats);
1282 stats += dirStats;
1283 }
1284
1285 // common to all disks
1286 stats.swap.open_disk_fd = store_open_disk_fd;
1287
1288 // memory cache stats are collected in StoreController::getStats(), for now
1289 }
1290
1291 void
1292 StoreHashIndex::stat(StoreEntry & output) const
1293 {
1294 int i;
1295
1296 /* Now go through each store, calling its stat routine */
1297
1298 for (i = 0; i < Config.cacheSwap.n_configured; ++i) {
1299 storeAppendPrintf(&output, "\n");
1300 store(i)->stat(output);
1301 }
1302 }
1303
1304 void
1305 StoreHashIndex::reference(StoreEntry &e)
1306 {
1307 e.store()->reference(e);
1308 }
1309
1310 bool
1311 StoreHashIndex::dereference(StoreEntry &e, bool wantsLocalMemory)
1312 {
1313 return e.store()->dereference(e, wantsLocalMemory);
1314 }
1315
1316 void
1317 StoreHashIndex::maintain()
1318 {
1319 int i;
1320 /* walk each fs */
1321
1322 for (i = 0; i < Config.cacheSwap.n_configured; ++i) {
1323 /* XXX FixMe: This should be done "in parallell" on the different
1324 * cache_dirs, not one at a time.
1325 */
1326 /* call the maintain function .. */
1327 store(i)->maintain();
1328 }
1329 }
1330
1331 void
1332 StoreHashIndex::sync()
1333 {
1334 for (int i = 0; i < Config.cacheSwap.n_configured; ++i)
1335 store(i)->sync();
1336 }
1337
1338 StoreSearch *
1339 StoreHashIndex::search(String const url, HttpRequest *)
1340 {
1341 if (url.size())
1342 fatal ("Cannot search by url yet\n");
1343
1344 return new StoreSearchHashIndex (this);
1345 }
1346
1347 CBDATA_CLASS_INIT(StoreSearchHashIndex);
1348
1349 StoreSearchHashIndex::StoreSearchHashIndex(RefCount<StoreHashIndex> aSwapDir) :
1350 sd(aSwapDir),
1351 callback(NULL),
1352 cbdata(NULL),
1353 _done(false),
1354 bucket(0)
1355 {}
1356
1357 /* do not link
1358 StoreSearchHashIndex::StoreSearchHashIndex(StoreSearchHashIndex const &);
1359 */
1360
1361 StoreSearchHashIndex::~StoreSearchHashIndex()
1362 {}
1363
1364 void
1365 StoreSearchHashIndex::next(void (aCallback)(void *), void *aCallbackData)
1366 {
1367 next();
1368 aCallback (aCallbackData);
1369 }
1370
1371 bool
1372 StoreSearchHashIndex::next()
1373 {
1374 if (entries.size())
1375 entries.pop_back();
1376
1377 while (!isDone() && !entries.size())
1378 copyBucket();
1379
1380 return currentItem() != NULL;
1381 }
1382
1383 bool
1384 StoreSearchHashIndex::error() const
1385 {
1386 return false;
1387 }
1388
1389 bool
1390 StoreSearchHashIndex::isDone() const
1391 {
1392 return bucket >= store_hash_buckets || _done;
1393 }
1394
1395 StoreEntry *
1396 StoreSearchHashIndex::currentItem()
1397 {
1398 if (!entries.size())
1399 return NULL;
1400
1401 return entries.back();
1402 }
1403
1404 void
1405 StoreSearchHashIndex::copyBucket()
1406 {
1407 /* probably need to lock the store entries...
1408 * we copy them all to prevent races on the links. */
1409 debugs(47, 3, "StoreSearchHashIndex::copyBucket #" << bucket);
1410 assert (!entries.size());
1411 hash_link *link_ptr = NULL;
1412 hash_link *link_next = NULL;
1413 link_next = hash_get_bucket(store_table, bucket);
1414
1415 while (NULL != (link_ptr = link_next)) {
1416 link_next = link_ptr->next;
1417 StoreEntry *e = (StoreEntry *) link_ptr;
1418
1419 entries.push_back(e);
1420 }
1421
1422 ++bucket;
1423 debugs(47,3, "got entries: " << entries.size());
1424 }