]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blame - libxfs/cache.c
Fix realtime inode reference counting buglet in mkfs proto file code.
[thirdparty/xfsprogs-dev.git] / libxfs / cache.c
CommitLineData
e80aa729
NS
1/*
2 * Copyright (c) 2006 Silicon Graphics, Inc.
3 * All Rights Reserved.
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it would be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write the Free Software Foundation,
16 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <unistd.h>
23#include <pthread.h>
24
25#include <xfs/platform_defs.h>
26#include <xfs/list.h>
27#include <xfs/cache.h>
28
29#define CACHE_DEBUG 1
b6281496 30#define HASH_CACHE_RATIO 8
e80aa729 31
1c4110bd
NS
32static unsigned int cache_generic_bulkrelse(struct cache *, struct list_head *);
33
e80aa729
NS
34struct cache *
35cache_init(
36 unsigned int hashsize,
37 struct cache_operations *cache_operations)
38{
39 struct cache * cache;
40 unsigned int i, maxcount;
41
b6281496 42 maxcount = hashsize * HASH_CACHE_RATIO;
e80aa729
NS
43
44 if (!(cache = malloc(sizeof(struct cache))))
45 return NULL;
46 if (!(cache->c_hash = calloc(hashsize, sizeof(struct cache_hash)))) {
47 free(cache);
48 return NULL;
49 }
50
51 cache->c_count = 0;
9f38f08d
MV
52 cache->c_max = 0;
53 cache->c_hits = 0;
54 cache->c_misses = 0;
e80aa729
NS
55 cache->c_maxcount = maxcount;
56 cache->c_hashsize = hashsize;
57 cache->hash = cache_operations->hash;
58 cache->alloc = cache_operations->alloc;
59 cache->relse = cache_operations->relse;
60 cache->compare = cache_operations->compare;
1c4110bd
NS
61 cache->bulkrelse = cache_operations->bulkrelse ?
62 cache_operations->bulkrelse : cache_generic_bulkrelse;
e80aa729
NS
63 pthread_mutex_init(&cache->c_mutex, NULL);
64
65 for (i = 0; i < hashsize; i++) {
66 list_head_init(&cache->c_hash[i].ch_list);
b6281496 67 cache->c_hash[i].ch_count = 0;
e80aa729
NS
68 pthread_mutex_init(&cache->c_hash[i].ch_mutex, NULL);
69 }
70 return cache;
71}
72
73void
74cache_walk(
75 struct cache * cache,
76 cache_walk_t visit)
77{
78 struct cache_hash * hash;
79 struct list_head * head;
80 struct list_head * pos;
81 unsigned int i;
82
83 for (i = 0; i < cache->c_hashsize; i++) {
84 hash = &cache->c_hash[i];
85 head = &hash->ch_list;
86 pthread_mutex_lock(&hash->ch_mutex);
87 for (pos = head->next; pos != head; pos = pos->next)
88 visit((struct cache_node *)pos);
89 pthread_mutex_unlock(&hash->ch_mutex);
90 }
91}
92
93#ifdef CACHE_DEBUG
94static void
95cache_zero_check(
96 struct cache_node * node)
97{
98 if (node->cn_count > 0) {
99 fprintf(stderr, "%s: refcount is %u, not zero (node=%p)\n",
100 __FUNCTION__, node->cn_count, node);
101 /* abort(); */
102 }
103}
104#define cache_destroy_check(c) cache_walk((c), cache_zero_check)
105#else
106#define cache_destroy_check(c) do { } while (0)
107#endif
108
109void
110cache_destroy(
111 struct cache * cache)
112{
113 unsigned int i;
114
115 cache_destroy_check(cache);
116 for (i = 0; i < cache->c_hashsize; i++) {
117 list_head_destroy(&cache->c_hash[i].ch_list);
118 pthread_mutex_destroy(&cache->c_hash[i].ch_mutex);
119 }
120 pthread_mutex_destroy(&cache->c_mutex);
121 free(cache->c_hash);
122 free(cache);
123}
124
125static int
126cache_shake_node(
127 struct cache * cache,
128 cache_key_t key,
129 struct cache_node * node)
130{
131 struct list_head * head;
132 struct list_head * pos;
133 struct list_head * n;
134 struct cache_hash * hash;
135 int count = -1;
136
137 hash = cache->c_hash + cache->hash(key, cache->c_hashsize);
138 head = &hash->ch_list;
139 pthread_mutex_lock(&hash->ch_mutex);
140 for (pos = head->next, n = pos->next;
141 pos != head;
142 pos = n, n = pos->next) {
143 if ((struct cache_node *)pos != node)
144 continue;
145 pthread_mutex_lock(&node->cn_mutex);
146 count = node->cn_count;
147 pthread_mutex_unlock(&node->cn_mutex);
148 if (count != 0)
149 break;
150 pthread_mutex_destroy(&node->cn_mutex);
151 list_del_init(&node->cn_list);
b6281496 152 hash->ch_count--;
e80aa729
NS
153 cache->relse(node);
154 break;
155 }
156 pthread_mutex_unlock(&hash->ch_mutex);
157 return count;
158}
159
160/*
161 * We've hit the limit on cache size, so we need to start reclaiming
162 * nodes we've used. This reclaims from the one given hash bucket
163 * only. Returns the number of freed up nodes, its left to the
164 * caller to updates the global counter of used nodes for the cache.
165 * The hash chain lock is held for the hash list argument, must be
166 * dropped before returning.
167 * We walk backwards through the hash (remembering we keep recently
168 * used nodes toward the front) until we hit an in-use node. We'll
169 * stop there if its a low priority call but keep going if its not.
170 */
171static unsigned int
172cache_shake_hash(
173 struct cache * cache,
174 struct cache_hash * hash,
175 unsigned int priority)
176{
177 struct list_head temp;
178 struct list_head * head;
179 struct list_head * pos;
180 struct list_head * n;
181 struct cache_node * node;
182 unsigned int inuse = 0;
e80aa729
NS
183
184 list_head_init(&temp);
185 head = &hash->ch_list;
186 for (pos = head->prev, n = pos->prev;
187 pos != head;
188 pos = n, n = pos->prev) {
189 node = (struct cache_node *)pos;
190 pthread_mutex_lock(&node->cn_mutex);
b6281496
MV
191 if (!(inuse = (node->cn_count > 0))) {
192 hash->ch_count--;
e80aa729 193 list_move(&node->cn_list, &temp);
b6281496 194 }
e80aa729
NS
195 pthread_mutex_unlock(&node->cn_mutex);
196 if (inuse && !priority)
197 break;
198 }
199 pthread_mutex_unlock(&hash->ch_mutex);
1c4110bd
NS
200 return cache->bulkrelse(cache, &temp);
201}
202
203/*
204 * Generic implementation of bulk release, which just iterates over
205 * the list calling the single node relse routine for each node.
206 */
207static unsigned int
208cache_generic_bulkrelse(
209 struct cache * cache,
210 struct list_head * list)
211{
212 struct cache_node * node;
213 unsigned int count = 0;
214
215 while (!list_empty(list)) {
216 node = (struct cache_node *)list->next;
e80aa729
NS
217 pthread_mutex_destroy(&node->cn_mutex);
218 list_del_init(&node->cn_list);
219 cache->relse(node);
220 count++;
221 }
222 return count;
223}
224
225/*
226 * We've hit the limit on cache size, so we need to start reclaiming
227 * nodes we've used. Start by shaking this hash chain only, unless
228 * the shake priority has been increased already.
229 * The hash chain lock is held for the hash list argument, must be
230 * dropped before returning.
231 * Returns new priority at end of the call (in case we call again).
232 */
233static unsigned int
234cache_shake(
235 struct cache * cache,
236 struct cache_hash * hash,
237 unsigned int priority)
238{
239 unsigned int count;
240 unsigned int i;
241
242 if (!priority) { /* do just one */
243 count = cache_shake_hash(cache, hash, priority);
244 } else { /* use a bigger hammer */
245 pthread_mutex_unlock(&hash->ch_mutex);
246 for (count = 0, i = 0; i < cache->c_hashsize; i++) {
247 hash = &cache->c_hash[i];
248 pthread_mutex_lock(&hash->ch_mutex);
249 count += cache_shake_hash(cache, hash, priority - 1);
250 }
251 }
252 if (count) {
253 pthread_mutex_lock(&cache->c_mutex);
254 cache->c_count -= count;
255 pthread_mutex_unlock(&cache->c_mutex);
256 }
257 return ++priority;
258}
259
260/*
261 * Allocate a new hash node (updating atomic counter in the process),
262 * unless doing so will push us over the maximum cache size.
263 */
264struct cache_node *
265cache_node_allocate(
266 struct cache * cache,
267 struct cache_hash * hashlist)
268{
269 unsigned int nodesfree;
270 struct cache_node * node;
271
272 pthread_mutex_lock(&cache->c_mutex);
9f38f08d 273 if ((nodesfree = (cache->c_count < cache->c_maxcount))) {
e80aa729 274 cache->c_count++;
9f38f08d
MV
275 if (cache->c_count > cache->c_max)
276 cache->c_max = cache->c_count;
277 }
278 cache->c_misses++;
e80aa729
NS
279 pthread_mutex_unlock(&cache->c_mutex);
280 if (!nodesfree)
281 return NULL;
282 if (!(node = cache->alloc())) { /* uh-oh */
283 pthread_mutex_lock(&cache->c_mutex);
284 cache->c_count--;
285 pthread_mutex_unlock(&cache->c_mutex);
286 return NULL;
287 }
288 pthread_mutex_init(&node->cn_mutex, NULL);
289 list_head_init(&node->cn_list);
290 node->cn_count = 1;
291 return node;
292}
293
294/*
295 * Lookup in the cache hash table. With any luck we'll get a cache
296 * hit, in which case this will all be over quickly and painlessly.
297 * Otherwise, we allocate a new node, taking care not to expand the
298 * cache beyond the requested maximum size (shrink it if it would).
299 * Returns one if hit in cache, otherwise zero. A node is _always_
300 * returned, however.
301 */
302int
303cache_node_get(
304 struct cache * cache,
305 cache_key_t key,
306 struct cache_node ** nodep)
307{
308 struct cache_node * node = NULL;
309 struct cache_hash * hash;
310 struct list_head * head;
311 struct list_head * pos;
312 int priority = 0;
313 int allocated = 0;
314
315 hash = cache->c_hash + cache->hash(key, cache->c_hashsize);
316 head = &hash->ch_list;
317
318 restart:
319 pthread_mutex_lock(&hash->ch_mutex);
320 for (pos = head->next; pos != head; pos = pos->next) {
321 node = (struct cache_node *)pos;
322 if (cache->compare(node, key) == 0)
323 continue;
324 pthread_mutex_lock(&node->cn_mutex);
325 node->cn_count++;
326 pthread_mutex_unlock(&node->cn_mutex);
9f38f08d
MV
327 pthread_mutex_lock(&cache->c_mutex);
328 cache->c_hits++;
329 pthread_mutex_unlock(&cache->c_mutex);
e80aa729
NS
330 break;
331 }
332 if (pos == head) {
333 node = cache_node_allocate(cache, hash);
334 if (!node) {
335 priority = cache_shake(cache, hash, priority);
336 goto restart;
337 }
338 allocated = 1;
b6281496 339 hash->ch_count++; /* new entry */
e80aa729
NS
340 }
341 /* looked at it, move to hash list head */
342 list_move(&node->cn_list, &hash->ch_list);
343 pthread_mutex_unlock(&hash->ch_mutex);
344 *nodep = node;
345 return allocated;
346}
347
348void
349cache_node_put(
350 struct cache_node * node)
351{
352 pthread_mutex_lock(&node->cn_mutex);
353#ifdef CACHE_DEBUG
354 if (node->cn_count < 1) {
355 fprintf(stderr, "%s: node put on refcount %u (node=%p)\n",
356 __FUNCTION__, node->cn_count, node);
357 /* abort(); */
358 }
359#endif
360 node->cn_count--;
361 pthread_mutex_unlock(&node->cn_mutex);
362}
363
364/*
365 * Purge a specific node from the cache. Reference count must be zero.
366 */
367int
368cache_node_purge(
369 struct cache * cache,
370 cache_key_t key,
371 struct cache_node * node)
372{
373 int refcount;
374
375 refcount = cache_shake_node(cache, key, node);
376 if (refcount == 0) {
377 pthread_mutex_lock(&cache->c_mutex);
378 cache->c_count--;
379 pthread_mutex_unlock(&cache->c_mutex);
380 }
381#ifdef CACHE_DEBUG
382 if (refcount >= 1) {
383 fprintf(stderr, "%s: refcount was %u, not zero (node=%p)\n",
384 __FUNCTION__, refcount, node);
385 /* abort(); */
386 }
387 if (refcount == -1) {
388 fprintf(stderr, "%s: purge node not found! (node=%p)\n",
389 __FUNCTION__, node);
390 /* abort(); */
391 }
392#endif
393 return (refcount == 0);
394}
395
396/*
397 * Purge all nodes from the cache. All reference counts must be zero.
398 */
399void
400cache_purge(
401 struct cache * cache)
402{
403 struct cache_hash * hash;
404
405 hash = &cache->c_hash[0];
406 pthread_mutex_lock(&hash->ch_mutex);
407 cache_shake(cache, hash, (unsigned int)-1);
408#ifdef CACHE_DEBUG
409 if (cache->c_count != 0) {
410 fprintf(stderr, "%s: shake on cache %p left %u nodes!?\n",
411 __FUNCTION__, cache, cache->c_count);
412 /* abort(); */
413 }
414#endif
415}
9f38f08d 416
b6281496 417#define HASH_REPORT (3*HASH_CACHE_RATIO)
9f38f08d 418void
b6281496 419cache_report(FILE *fp, const char *name, struct cache * cache)
9f38f08d 420{
b6281496
MV
421 int i;
422 unsigned long count, index, total;
423 unsigned long hash_bucket_lengths[HASH_REPORT+2];
424
425 if ((cache->c_hits+cache->c_misses) == 0)
426 return;
427
428 /* report cache summary */
429 fprintf(fp, "%s: %p\n"
9f38f08d
MV
430 "Max supported entries = %u\n"
431 "Max utilized entries = %u\n"
432 "Active entries = %u\n"
433 "Hash table size = %u\n"
434 "Hits = %llu\n"
435 "Misses = %llu\n"
436 "Hit ratio = %5.2f\n",
437 name, cache,
438 cache->c_maxcount,
439 cache->c_max,
440 cache->c_count,
441 cache->c_hashsize,
442 cache->c_hits,
443 cache->c_misses,
444 (double) (cache->c_hits*100/(cache->c_hits+cache->c_misses))
445 );
b6281496
MV
446
447 /* report hash bucket lengths */
448 bzero(hash_bucket_lengths, sizeof(hash_bucket_lengths));
449
450 for (i = 0; i < cache->c_hashsize; i++) {
451 count = cache->c_hash[i].ch_count;
452 if (count > HASH_REPORT)
453 index = HASH_REPORT + 1;
454 else
455 index = count;
456 hash_bucket_lengths[index]++;
457 }
458
459 total = 0;
460 for (i = 0; i < HASH_REPORT+1; i++) {
461 total += i*hash_bucket_lengths[i];
462 if (hash_bucket_lengths[i] == 0)
463 continue;
464 fprintf(fp, "Hash buckets with %2d entries %5ld (%3ld%%)\n",
465 i, hash_bucket_lengths[i], (i*hash_bucket_lengths[i]*100)/cache->c_count);
466 }
467 if (hash_bucket_lengths[i]) /* last report bucket is the overflow bucket */
468 fprintf(fp, "Hash buckets with >%2d entries %5ld (%3ld%%)\n",
469 i-1, hash_bucket_lengths[i], ((cache->c_count-total)*100)/cache->c_count);
9f38f08d 470}