Line | Count | Source |
1 | | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
2 | | * Copyright by The HDF Group. * |
3 | | * All rights reserved. * |
4 | | * * |
5 | | * This file is part of HDF5. The full HDF5 copyright notice, including * |
6 | | * terms governing use, modification, and redistribution, is contained in * |
7 | | * the LICENSE file, which can be found at the root of the source code * |
8 | | * distribution tree, or in https://www.hdfgroup.org/licenses. * |
9 | | * If you do not have access to either file, you may request a copy from * |
10 | | * help@hdfgroup.org. * |
11 | | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
12 | | |
13 | | /*------------------------------------------------------------------------- |
14 | | * |
15 | | * Created: H5Centry.c |
16 | | * |
17 | | * Purpose: Routines which operate on cache entries. |
18 | | * |
19 | | *------------------------------------------------------------------------- |
20 | | */ |
21 | | |
22 | | /****************/ |
23 | | /* Module Setup */ |
24 | | /****************/ |
25 | | |
26 | | #include "H5Cmodule.h" /* This source code file is part of the H5C module */ |
27 | | #define H5F_FRIEND /* suppress error about including H5Fpkg */ |
28 | | |
29 | | /***********/ |
30 | | /* Headers */ |
31 | | /***********/ |
32 | | #include "H5private.h" /* Generic Functions */ |
33 | | #include "H5Cpkg.h" /* Cache */ |
34 | | #include "H5Eprivate.h" /* Error handling */ |
35 | | #include "H5Fpkg.h" /* Files */ |
36 | | #include "H5MFprivate.h" /* File memory management */ |
37 | | #include "H5SLprivate.h" /* Skip Lists */ |
38 | | |
39 | | /****************/ |
40 | | /* Local Macros */ |
41 | | /****************/ |
42 | | |
43 | | /******************/ |
44 | | /* Local Typedefs */ |
45 | | /******************/ |
46 | | |
47 | | /********************/ |
48 | | /* Local Prototypes */ |
49 | | /********************/ |
50 | | static herr_t H5C__autoadjust__ageout(H5F_t *f, double hit_rate, enum H5C_resize_status *status_ptr, |
51 | | size_t *new_max_cache_size_ptr, bool write_permitted); |
52 | | static herr_t H5C__autoadjust__ageout__cycle_epoch_marker(H5C_t *cache_ptr); |
53 | | static herr_t H5C__autoadjust__ageout__evict_aged_out_entries(H5F_t *f, bool write_permitted); |
54 | | static herr_t H5C__autoadjust__ageout__insert_new_marker(H5C_t *cache_ptr); |
55 | | static herr_t H5C__flush_invalidate_ring(H5F_t *f, H5C_ring_t ring, unsigned flags); |
56 | | static herr_t H5C__serialize_ring(H5F_t *f, H5C_ring_t ring); |
57 | | |
58 | | /*********************/ |
59 | | /* Package Variables */ |
60 | | /*********************/ |
61 | | |
62 | | /*****************************/ |
63 | | /* Library Private Variables */ |
64 | | /*****************************/ |
65 | | |
66 | | /*******************/ |
67 | | /* Local Variables */ |
68 | | /*******************/ |
69 | | |
70 | | /*------------------------------------------------------------------------- |
71 | | * Function: H5C__auto_adjust_cache_size |
72 | | * |
73 | | * Purpose: Obtain the current full cache hit rate, and compare it |
74 | | * with the hit rate thresholds for modifying cache size. |
75 | | * If one of the thresholds has been crossed, adjusts the |
76 | | * size of the cache accordingly. |
77 | | * |
78 | | * The function then resets the full cache hit rate |
79 | | * statistics, and exits. |
80 | | * |
81 | | * Return: Non-negative on success/Negative on failure or if there was |
82 | | * an attempt to flush a protected item. |
83 | | * |
84 | | *------------------------------------------------------------------------- |
85 | | */ |
86 | | herr_t |
87 | | H5C__auto_adjust_cache_size(H5F_t *f, bool write_permitted) |
88 | 0 | { |
89 | 0 | H5C_t *cache_ptr = f->shared->cache; |
90 | 0 | bool reentrant_call = false; |
91 | 0 | bool inserted_epoch_marker = false; |
92 | 0 | size_t new_max_cache_size = 0; |
93 | 0 | size_t old_max_cache_size = 0; |
94 | 0 | size_t new_min_clean_size = 0; |
95 | 0 | size_t old_min_clean_size = 0; |
96 | 0 | double hit_rate; |
97 | 0 | enum H5C_resize_status status = in_spec; /* will change if needed */ |
98 | 0 | herr_t ret_value = SUCCEED; /* Return value */ |
99 | |
|
100 | 0 | FUNC_ENTER_PACKAGE |
101 | |
|
102 | 0 | assert(f); |
103 | 0 | assert(cache_ptr); |
104 | 0 | assert(cache_ptr->cache_accesses >= cache_ptr->resize_ctl.epoch_length); |
105 | 0 | assert(0.0 <= cache_ptr->resize_ctl.min_clean_fraction); |
106 | 0 | assert(cache_ptr->resize_ctl.min_clean_fraction <= 100.0); |
107 | | |
108 | | /* check to see if cache_ptr->resize_in_progress is true. If it, this |
109 | | * is a re-entrant call via a client callback called in the resize |
110 | | * process. To avoid an infinite recursion, set reentrant_call to |
111 | | * true, and goto done. |
112 | | */ |
113 | 0 | if (cache_ptr->resize_in_progress) { |
114 | 0 | reentrant_call = true; |
115 | 0 | HGOTO_DONE(SUCCEED); |
116 | 0 | } /* end if */ |
117 | | |
118 | 0 | cache_ptr->resize_in_progress = true; |
119 | |
|
120 | 0 | if (!cache_ptr->resize_enabled) |
121 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Auto cache resize disabled"); |
122 | | |
123 | 0 | assert((cache_ptr->resize_ctl.incr_mode != H5C_incr__off) || |
124 | 0 | (cache_ptr->resize_ctl.decr_mode != H5C_decr__off)); |
125 | |
|
126 | 0 | if (H5C_get_cache_hit_rate(cache_ptr, &hit_rate) != SUCCEED) |
127 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Can't get hit rate"); |
128 | | |
129 | 0 | assert((0.0 <= hit_rate) && (hit_rate <= 1.0)); |
130 | |
|
131 | 0 | switch (cache_ptr->resize_ctl.incr_mode) { |
132 | 0 | case H5C_incr__off: |
133 | 0 | if (cache_ptr->size_increase_possible) |
134 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "size_increase_possible but H5C_incr__off?!?!?"); |
135 | 0 | break; |
136 | | |
137 | 0 | case H5C_incr__threshold: |
138 | 0 | if (hit_rate < cache_ptr->resize_ctl.lower_hr_threshold) { |
139 | 0 | if (!cache_ptr->size_increase_possible) |
140 | 0 | status = increase_disabled; |
141 | 0 | else if (cache_ptr->max_cache_size >= cache_ptr->resize_ctl.max_size) { |
142 | 0 | assert(cache_ptr->max_cache_size == cache_ptr->resize_ctl.max_size); |
143 | 0 | status = at_max_size; |
144 | 0 | } |
145 | 0 | else if (!cache_ptr->cache_full) |
146 | 0 | status = not_full; |
147 | 0 | else { |
148 | 0 | new_max_cache_size = |
149 | 0 | (size_t)(((double)(cache_ptr->max_cache_size)) * cache_ptr->resize_ctl.increment); |
150 | | |
151 | | /* clip to max size if necessary */ |
152 | 0 | if (new_max_cache_size > cache_ptr->resize_ctl.max_size) |
153 | 0 | new_max_cache_size = cache_ptr->resize_ctl.max_size; |
154 | | |
155 | | /* clip to max increment if necessary */ |
156 | 0 | if (cache_ptr->resize_ctl.apply_max_increment && |
157 | 0 | ((cache_ptr->max_cache_size + cache_ptr->resize_ctl.max_increment) < |
158 | 0 | new_max_cache_size)) |
159 | 0 | new_max_cache_size = cache_ptr->max_cache_size + cache_ptr->resize_ctl.max_increment; |
160 | |
|
161 | 0 | status = increase; |
162 | 0 | } |
163 | 0 | } |
164 | 0 | break; |
165 | | |
166 | 0 | default: |
167 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "unknown incr_mode"); |
168 | 0 | } |
169 | | |
170 | | /* If the decr_mode is either age out or age out with threshold, we |
171 | | * must run the marker maintenance code, whether we run the size |
172 | | * reduction code or not. We do this in two places -- here we |
173 | | * insert a new marker if the number of active epoch markers is |
174 | | * is less than the current epochs before eviction, and after |
175 | | * the ageout call, we cycle the markers. |
176 | | * |
177 | | * However, we can't call the ageout code or cycle the markers |
178 | | * unless there was a full complement of markers in place on |
179 | | * entry. The inserted_epoch_marker flag is used to track this. |
180 | | */ |
181 | | |
182 | 0 | if (((cache_ptr->resize_ctl.decr_mode == H5C_decr__age_out) || |
183 | 0 | (cache_ptr->resize_ctl.decr_mode == H5C_decr__age_out_with_threshold)) && |
184 | 0 | (cache_ptr->epoch_markers_active < cache_ptr->resize_ctl.epochs_before_eviction)) { |
185 | |
|
186 | 0 | if (H5C__autoadjust__ageout__insert_new_marker(cache_ptr) < 0) |
187 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "can't insert new epoch marker"); |
188 | | |
189 | 0 | inserted_epoch_marker = true; |
190 | 0 | } |
191 | | |
192 | | /* don't run the cache size decrease code unless the cache size |
193 | | * increase code is disabled, or the size increase code sees no need |
194 | | * for action. In either case, status == in_spec at this point. |
195 | | */ |
196 | | |
197 | 0 | if (status == in_spec) { |
198 | 0 | switch (cache_ptr->resize_ctl.decr_mode) { |
199 | 0 | case H5C_decr__off: |
200 | 0 | break; |
201 | | |
202 | 0 | case H5C_decr__threshold: |
203 | 0 | if (hit_rate > cache_ptr->resize_ctl.upper_hr_threshold) { |
204 | 0 | if (!cache_ptr->size_decrease_possible) |
205 | 0 | status = decrease_disabled; |
206 | 0 | else if (cache_ptr->max_cache_size <= cache_ptr->resize_ctl.min_size) { |
207 | 0 | assert(cache_ptr->max_cache_size == cache_ptr->resize_ctl.min_size); |
208 | 0 | status = at_min_size; |
209 | 0 | } |
210 | 0 | else { |
211 | 0 | new_max_cache_size = |
212 | 0 | (size_t)(((double)(cache_ptr->max_cache_size)) * cache_ptr->resize_ctl.decrement); |
213 | | |
214 | | /* clip to min size if necessary */ |
215 | 0 | if (new_max_cache_size < cache_ptr->resize_ctl.min_size) |
216 | 0 | new_max_cache_size = cache_ptr->resize_ctl.min_size; |
217 | | |
218 | | /* clip to max decrement if necessary */ |
219 | 0 | if (cache_ptr->resize_ctl.apply_max_decrement && |
220 | 0 | ((cache_ptr->resize_ctl.max_decrement + new_max_cache_size) < |
221 | 0 | cache_ptr->max_cache_size)) |
222 | 0 | new_max_cache_size = |
223 | 0 | cache_ptr->max_cache_size - cache_ptr->resize_ctl.max_decrement; |
224 | |
|
225 | 0 | status = decrease; |
226 | 0 | } |
227 | 0 | } |
228 | 0 | break; |
229 | | |
230 | 0 | case H5C_decr__age_out_with_threshold: |
231 | 0 | case H5C_decr__age_out: |
232 | 0 | if (!inserted_epoch_marker) { |
233 | 0 | if (!cache_ptr->size_decrease_possible) |
234 | 0 | status = decrease_disabled; |
235 | 0 | else { |
236 | 0 | if (H5C__autoadjust__ageout(f, hit_rate, &status, &new_max_cache_size, |
237 | 0 | write_permitted) < 0) |
238 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "ageout code failed"); |
239 | 0 | } /* end else */ |
240 | 0 | } /* end if */ |
241 | 0 | break; |
242 | | |
243 | 0 | default: |
244 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "unknown incr_mode"); |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | | /* cycle the epoch markers here if appropriate */ |
249 | 0 | if (((cache_ptr->resize_ctl.decr_mode == H5C_decr__age_out) || |
250 | 0 | (cache_ptr->resize_ctl.decr_mode == H5C_decr__age_out_with_threshold)) && |
251 | 0 | !inserted_epoch_marker) |
252 | | /* move last epoch marker to the head of the LRU list */ |
253 | 0 | if (H5C__autoadjust__ageout__cycle_epoch_marker(cache_ptr) < 0) |
254 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "error cycling epoch marker"); |
255 | | |
256 | 0 | if ((status == increase) || (status == decrease)) { |
257 | 0 | old_max_cache_size = cache_ptr->max_cache_size; |
258 | 0 | old_min_clean_size = cache_ptr->min_clean_size; |
259 | |
|
260 | 0 | new_min_clean_size = |
261 | 0 | (size_t)((double)new_max_cache_size * (cache_ptr->resize_ctl.min_clean_fraction)); |
262 | | |
263 | | /* new_min_clean_size is of size_t, and thus must be non-negative. |
264 | | * Hence we have |
265 | | * |
266 | | * ( 0 <= new_min_clean_size ). |
267 | | * |
268 | | * by definition. |
269 | | */ |
270 | 0 | assert(new_min_clean_size <= new_max_cache_size); |
271 | 0 | assert(cache_ptr->resize_ctl.min_size <= new_max_cache_size); |
272 | 0 | assert(new_max_cache_size <= cache_ptr->resize_ctl.max_size); |
273 | |
|
274 | 0 | cache_ptr->max_cache_size = new_max_cache_size; |
275 | 0 | cache_ptr->min_clean_size = new_min_clean_size; |
276 | |
|
277 | 0 | if (status == increase) |
278 | 0 | cache_ptr->cache_full = false; |
279 | 0 | else if (status == decrease) |
280 | 0 | cache_ptr->size_decreased = true; |
281 | | |
282 | | /* update flash cache size increase fields as appropriate */ |
283 | 0 | if (cache_ptr->flash_size_increase_possible) { |
284 | 0 | switch (cache_ptr->resize_ctl.flash_incr_mode) { |
285 | 0 | case H5C_flash_incr__off: |
286 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, |
287 | 0 | "flash_size_increase_possible but H5C_flash_incr__off?!"); |
288 | 0 | break; |
289 | | |
290 | 0 | case H5C_flash_incr__add_space: |
291 | 0 | cache_ptr->flash_size_increase_threshold = |
292 | 0 | (size_t)(((double)(cache_ptr->max_cache_size)) * |
293 | 0 | (cache_ptr->resize_ctl.flash_threshold)); |
294 | 0 | break; |
295 | | |
296 | 0 | default: /* should be unreachable */ |
297 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Unknown flash_incr_mode?!?!?"); |
298 | 0 | break; |
299 | 0 | } |
300 | 0 | } |
301 | 0 | } |
302 | | |
303 | 0 | if (cache_ptr->resize_ctl.rpt_fcn != NULL) |
304 | 0 | (cache_ptr->resize_ctl.rpt_fcn)(cache_ptr, H5C__CURR_AUTO_RESIZE_RPT_FCN_VER, hit_rate, status, |
305 | 0 | old_max_cache_size, new_max_cache_size, old_min_clean_size, |
306 | 0 | new_min_clean_size); |
307 | |
|
308 | 0 | if (H5C_reset_cache_hit_rate_stats(cache_ptr) < 0) |
309 | | /* this should be impossible... */ |
310 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "H5C_reset_cache_hit_rate_stats failed"); |
311 | | |
312 | 0 | done: |
313 | | /* Sanity checks */ |
314 | 0 | assert(cache_ptr->resize_in_progress); |
315 | 0 | if (!reentrant_call) |
316 | 0 | cache_ptr->resize_in_progress = false; |
317 | 0 | assert((!reentrant_call) || (cache_ptr->resize_in_progress)); |
318 | |
|
319 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
320 | 0 | } /* H5C__auto_adjust_cache_size() */ |
321 | | |
322 | | /*------------------------------------------------------------------------- |
323 | | * Function: H5C__autoadjust__ageout |
324 | | * |
325 | | * Purpose: Implement the ageout automatic cache size decrement |
326 | | * algorithm. Note that while this code evicts aged out |
327 | | * entries, the code does not change the maximum cache size. |
328 | | * Instead, the function simply computes the new value (if |
329 | | * any change is indicated) and reports this value in |
330 | | * *new_max_cache_size_ptr. |
331 | | * |
332 | | * Return: Non-negative on success/Negative on failure or if there was |
333 | | * an attempt to flush a protected item. |
334 | | * |
335 | | *------------------------------------------------------------------------- |
336 | | */ |
337 | | static herr_t |
338 | | H5C__autoadjust__ageout(H5F_t *f, double hit_rate, enum H5C_resize_status *status_ptr, |
339 | | size_t *new_max_cache_size_ptr, bool write_permitted) |
340 | 0 | { |
341 | 0 | H5C_t *cache_ptr = f->shared->cache; |
342 | 0 | size_t test_size; |
343 | 0 | herr_t ret_value = SUCCEED; /* Return value */ |
344 | |
|
345 | 0 | FUNC_ENTER_PACKAGE |
346 | |
|
347 | 0 | assert(f); |
348 | 0 | assert(cache_ptr); |
349 | 0 | assert((status_ptr) && (*status_ptr == in_spec)); |
350 | 0 | assert((new_max_cache_size_ptr) && (*new_max_cache_size_ptr == 0)); |
351 | | |
352 | | /* remove excess epoch markers if any */ |
353 | 0 | if (cache_ptr->epoch_markers_active > cache_ptr->resize_ctl.epochs_before_eviction) |
354 | 0 | if (H5C__autoadjust__ageout__remove_excess_markers(cache_ptr) < 0) |
355 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "can't remove excess epoch markers"); |
356 | | |
357 | 0 | if ((cache_ptr->resize_ctl.decr_mode == H5C_decr__age_out) || |
358 | 0 | ((cache_ptr->resize_ctl.decr_mode == H5C_decr__age_out_with_threshold) && |
359 | 0 | (hit_rate >= cache_ptr->resize_ctl.upper_hr_threshold))) { |
360 | |
|
361 | 0 | if (cache_ptr->max_cache_size > cache_ptr->resize_ctl.min_size) { |
362 | | /* evict aged out cache entries if appropriate... */ |
363 | 0 | if (H5C__autoadjust__ageout__evict_aged_out_entries(f, write_permitted) < 0) |
364 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "error flushing aged out entries"); |
365 | | |
366 | | /* ... and then reduce cache size if appropriate */ |
367 | 0 | if (cache_ptr->index_size < cache_ptr->max_cache_size) { |
368 | 0 | if (cache_ptr->resize_ctl.apply_empty_reserve) { |
369 | 0 | test_size = |
370 | 0 | (size_t)(((double)cache_ptr->index_size) / (1 - cache_ptr->resize_ctl.empty_reserve)); |
371 | 0 | if (test_size < cache_ptr->max_cache_size) { |
372 | 0 | *status_ptr = decrease; |
373 | 0 | *new_max_cache_size_ptr = test_size; |
374 | 0 | } |
375 | 0 | } |
376 | 0 | else { |
377 | 0 | *status_ptr = decrease; |
378 | 0 | *new_max_cache_size_ptr = cache_ptr->index_size; |
379 | 0 | } |
380 | |
|
381 | 0 | if (*status_ptr == decrease) { |
382 | | /* clip to min size if necessary */ |
383 | 0 | if (*new_max_cache_size_ptr < cache_ptr->resize_ctl.min_size) |
384 | 0 | *new_max_cache_size_ptr = cache_ptr->resize_ctl.min_size; |
385 | | |
386 | | /* clip to max decrement if necessary */ |
387 | 0 | if ((cache_ptr->resize_ctl.apply_max_decrement) && |
388 | 0 | ((cache_ptr->resize_ctl.max_decrement + *new_max_cache_size_ptr) < |
389 | 0 | cache_ptr->max_cache_size)) |
390 | 0 | *new_max_cache_size_ptr = |
391 | 0 | cache_ptr->max_cache_size - cache_ptr->resize_ctl.max_decrement; |
392 | 0 | } |
393 | 0 | } |
394 | 0 | } |
395 | 0 | else |
396 | 0 | *status_ptr = at_min_size; |
397 | 0 | } |
398 | | |
399 | 0 | done: |
400 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
401 | 0 | } /* H5C__autoadjust__ageout() */ |
402 | | |
403 | | /*------------------------------------------------------------------------- |
404 | | * Function: H5C__autoadjust__ageout__cycle_epoch_marker |
405 | | * |
406 | | * Purpose: Remove the oldest epoch marker from the LRU list, |
407 | | * and reinsert it at the head of the LRU list. Also |
408 | | * remove the epoch marker's index from the head of the |
409 | | * ring buffer, and re-insert it at the tail of the ring |
410 | | * buffer. |
411 | | * |
412 | | * Return: SUCCEED on success/FAIL on failure. |
413 | | * |
414 | | *------------------------------------------------------------------------- |
415 | | */ |
416 | | static herr_t |
417 | | H5C__autoadjust__ageout__cycle_epoch_marker(H5C_t *cache_ptr) |
418 | 0 | { |
419 | 0 | int i; |
420 | 0 | herr_t ret_value = SUCCEED; /* Return value */ |
421 | |
|
422 | 0 | FUNC_ENTER_PACKAGE |
423 | |
|
424 | 0 | assert(cache_ptr); |
425 | |
|
426 | 0 | if (cache_ptr->epoch_markers_active <= 0) |
427 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "No active epoch markers on entry?!?!?"); |
428 | | |
429 | | /* remove the last marker from both the ring buffer and the LRU list */ |
430 | 0 | i = cache_ptr->epoch_marker_ringbuf[cache_ptr->epoch_marker_ringbuf_first]; |
431 | 0 | cache_ptr->epoch_marker_ringbuf_first = |
432 | 0 | (cache_ptr->epoch_marker_ringbuf_first + 1) % (H5C__MAX_EPOCH_MARKERS + 1); |
433 | 0 | if (cache_ptr->epoch_marker_ringbuf_size <= 0) |
434 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "ring buffer underflow"); |
435 | | |
436 | 0 | cache_ptr->epoch_marker_ringbuf_size -= 1; |
437 | 0 | if (cache_ptr->epoch_marker_active[i] != true) |
438 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "unused marker in LRU?!?"); |
439 | | |
440 | 0 | H5C__DLL_REMOVE((&((cache_ptr->epoch_markers)[i])), (cache_ptr)->LRU_head_ptr, (cache_ptr)->LRU_tail_ptr, |
441 | 0 | (cache_ptr)->LRU_list_len, (cache_ptr)->LRU_list_size, (FAIL)) |
442 | | |
443 | | /* now, re-insert it at the head of the LRU list, and at the tail of |
444 | | * the ring buffer. |
445 | | */ |
446 | 0 | assert(cache_ptr->epoch_markers[i].addr == (haddr_t)i); |
447 | 0 | assert(cache_ptr->epoch_markers[i].next == NULL); |
448 | 0 | assert(cache_ptr->epoch_markers[i].prev == NULL); |
449 | |
|
450 | 0 | cache_ptr->epoch_marker_ringbuf_last = |
451 | 0 | (cache_ptr->epoch_marker_ringbuf_last + 1) % (H5C__MAX_EPOCH_MARKERS + 1); |
452 | 0 | cache_ptr->epoch_marker_ringbuf[cache_ptr->epoch_marker_ringbuf_last] = i; |
453 | 0 | if (cache_ptr->epoch_marker_ringbuf_size >= H5C__MAX_EPOCH_MARKERS) |
454 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "ring buffer overflow"); |
455 | | |
456 | 0 | cache_ptr->epoch_marker_ringbuf_size += 1; |
457 | |
|
458 | 0 | H5C__DLL_PREPEND(&(cache_ptr->epoch_markers[i]), cache_ptr->LRU_head_ptr, cache_ptr->LRU_tail_ptr, |
459 | 0 | cache_ptr->LRU_list_len, cache_ptr->LRU_list_size, FAIL) |
460 | 0 | done: |
461 | |
|
462 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
463 | |
|
464 | 0 | } /* H5C__autoadjust__ageout__cycle_epoch_marker() */ |
465 | | |
466 | | /*------------------------------------------------------------------------- |
467 | | * Function: H5C__autoadjust__ageout__evict_aged_out_entries |
468 | | * |
469 | | * Purpose: Evict clean entries in the cache that haven't |
470 | | * been accessed for at least |
471 | | * cache_ptr->resize_ctl.epochs_before_eviction epochs, |
472 | | * and flush dirty entries that haven't been accessed for |
473 | | * that amount of time. |
474 | | * |
475 | | * Depending on configuration, the function will either |
476 | | * flush or evict all such entries, or all such entries it |
477 | | * encounters until it has freed the maximum amount of space |
478 | | * allowed under the maximum decrement. |
479 | | * |
480 | | * If we are running in parallel mode, writes may not be |
481 | | * permitted. If so, the function simply skips any dirty |
482 | | * entries it may encounter. |
483 | | * |
484 | | * The function makes no attempt to maintain the minimum |
485 | | * clean size, as there is no guarantee that the cache size |
486 | | * will be changed. |
487 | | * |
488 | | * If there is no cache size change, the minimum clean size |
489 | | * constraint will be met through a combination of clean |
490 | | * entries and free space in the cache. |
491 | | * |
492 | | * If there is a cache size reduction, the minimum clean size |
493 | | * will be re-calculated, and will be enforced the next time |
494 | | * we have to make space in the cache. |
495 | | * |
496 | | * Return: Non-negative on success/Negative on failure. |
497 | | * |
498 | | *------------------------------------------------------------------------- |
499 | | */ |
500 | | static herr_t |
501 | | H5C__autoadjust__ageout__evict_aged_out_entries(H5F_t *f, bool write_permitted) |
502 | 0 | { |
503 | 0 | H5C_t *cache_ptr = f->shared->cache; |
504 | 0 | size_t eviction_size_limit; |
505 | 0 | size_t bytes_evicted = 0; |
506 | 0 | bool prev_is_dirty = false; |
507 | 0 | bool restart_scan; |
508 | 0 | H5C_cache_entry_t *entry_ptr; |
509 | 0 | H5C_cache_entry_t *next_ptr; |
510 | 0 | H5C_cache_entry_t *prev_ptr; |
511 | 0 | herr_t ret_value = SUCCEED; /* Return value */ |
512 | |
|
513 | 0 | FUNC_ENTER_PACKAGE |
514 | |
|
515 | 0 | assert(f); |
516 | 0 | assert(cache_ptr); |
517 | | |
518 | | /* if there is a limit on the amount that the cache size can be decrease |
519 | | * in any one round of the cache size reduction algorithm, load that |
520 | | * limit into eviction_size_limit. Otherwise, set eviction_size_limit |
521 | | * to the equivalent of infinity. The current size of the index will |
522 | | * do nicely. |
523 | | */ |
524 | 0 | if (cache_ptr->resize_ctl.apply_max_decrement) |
525 | 0 | eviction_size_limit = cache_ptr->resize_ctl.max_decrement; |
526 | 0 | else |
527 | 0 | eviction_size_limit = cache_ptr->index_size; /* i.e. infinity */ |
528 | |
|
529 | 0 | if (write_permitted) { |
530 | 0 | restart_scan = false; |
531 | 0 | entry_ptr = cache_ptr->LRU_tail_ptr; |
532 | 0 | while (entry_ptr != NULL && entry_ptr->type->id != H5AC_EPOCH_MARKER_ID && |
533 | 0 | bytes_evicted < eviction_size_limit) { |
534 | 0 | bool skipping_entry = false; |
535 | |
|
536 | 0 | assert(!(entry_ptr->is_protected)); |
537 | 0 | assert(!(entry_ptr->is_read_only)); |
538 | 0 | assert((entry_ptr->ro_ref_count) == 0); |
539 | |
|
540 | 0 | next_ptr = entry_ptr->next; |
541 | 0 | prev_ptr = entry_ptr->prev; |
542 | |
|
543 | 0 | if (prev_ptr != NULL) |
544 | 0 | prev_is_dirty = prev_ptr->is_dirty; |
545 | |
|
546 | 0 | if (entry_ptr->is_dirty) { |
547 | 0 | assert(!entry_ptr->prefetched_dirty); |
548 | | |
549 | | /* dirty corked entry is skipped */ |
550 | 0 | if (entry_ptr->tag_info && entry_ptr->tag_info->corked) |
551 | 0 | skipping_entry = true; |
552 | 0 | else { |
553 | | /* reset entries_removed_counter and |
554 | | * last_entry_removed_ptr prior to the call to |
555 | | * H5C__flush_single_entry() so that we can spot |
556 | | * unexpected removals of entries from the cache, |
557 | | * and set the restart_scan flag if proceeding |
558 | | * would be likely to cause us to scan an entry |
559 | | * that is no longer in the cache. |
560 | | */ |
561 | 0 | cache_ptr->entries_removed_counter = 0; |
562 | 0 | cache_ptr->last_entry_removed_ptr = NULL; |
563 | |
|
564 | 0 | if (H5C__flush_single_entry(f, entry_ptr, H5C__NO_FLAGS_SET) < 0) |
565 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "unable to flush entry"); |
566 | | |
567 | 0 | if (cache_ptr->entries_removed_counter > 1 || |
568 | 0 | cache_ptr->last_entry_removed_ptr == prev_ptr) |
569 | 0 | restart_scan = true; |
570 | 0 | } /* end else */ |
571 | 0 | } /* end if */ |
572 | 0 | else if (!entry_ptr->prefetched_dirty) { |
573 | 0 | bytes_evicted += entry_ptr->size; |
574 | |
|
575 | 0 | if (H5C__flush_single_entry( |
576 | 0 | f, entry_ptr, H5C__FLUSH_INVALIDATE_FLAG | H5C__DEL_FROM_SLIST_ON_DESTROY_FLAG) < 0) |
577 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "unable to flush entry"); |
578 | 0 | } /* end else-if */ |
579 | 0 | else { |
580 | 0 | assert(!entry_ptr->is_dirty); |
581 | 0 | assert(entry_ptr->prefetched_dirty); |
582 | |
|
583 | 0 | skipping_entry = true; |
584 | 0 | } /* end else */ |
585 | | |
586 | 0 | if (prev_ptr != NULL) { |
587 | 0 | if (skipping_entry) |
588 | 0 | entry_ptr = prev_ptr; |
589 | 0 | else if (restart_scan || (prev_ptr->is_dirty != prev_is_dirty) || |
590 | 0 | (prev_ptr->next != next_ptr) || (prev_ptr->is_protected) || (prev_ptr->is_pinned)) { |
591 | | /* Something has happened to the LRU -- start over |
592 | | * from the tail. |
593 | | */ |
594 | 0 | restart_scan = false; |
595 | 0 | entry_ptr = cache_ptr->LRU_tail_ptr; |
596 | |
|
597 | 0 | H5C__UPDATE_STATS_FOR_LRU_SCAN_RESTART(cache_ptr); |
598 | 0 | } /* end else-if */ |
599 | 0 | else |
600 | 0 | entry_ptr = prev_ptr; |
601 | 0 | } /* end if */ |
602 | 0 | else |
603 | 0 | entry_ptr = NULL; |
604 | 0 | } /* end while */ |
605 | | |
606 | | /* for now at least, don't bother to maintain the minimum clean size, |
607 | | * as the cache should now be less than its maximum size. Due to |
608 | | * the vaguries of the cache size reduction algorithm, we may not |
609 | | * reduce the size of the cache. |
610 | | * |
611 | | * If we do, we will calculate a new minimum clean size, which will |
612 | | * be enforced the next time we try to make space in the cache. |
613 | | * |
614 | | * If we don't, no action is necessary, as we have just evicted and/or |
615 | | * or flushed a bunch of entries and therefore the sum of the clean |
616 | | * and free space in the cache must be greater than or equal to the |
617 | | * min clean space requirement (assuming that requirement was met on |
618 | | * entry). |
619 | | */ |
620 | 0 | } /* end if */ |
621 | 0 | else /* ! write_permitted */ { |
622 | | /* Since we are not allowed to write, all we can do is evict |
623 | | * any clean entries that we may encounter before we either |
624 | | * hit the eviction size limit, or encounter the epoch marker. |
625 | | * |
626 | | * If we are operating read only, this isn't an issue, as there |
627 | | * will not be any dirty entries. |
628 | | * |
629 | | * If we are operating in R/W mode, all the dirty entries we |
630 | | * skip will be flushed the next time we attempt to make space |
631 | | * when writes are permitted. This may have some local |
632 | | * performance implications, but it shouldn't cause any net |
633 | | * slowdown. |
634 | | */ |
635 | 0 | assert(H5C_MAINTAIN_CLEAN_AND_DIRTY_LRU_LISTS); |
636 | 0 | entry_ptr = cache_ptr->LRU_tail_ptr; |
637 | 0 | while (entry_ptr != NULL && ((entry_ptr->type)->id != H5AC_EPOCH_MARKER_ID) && |
638 | 0 | (bytes_evicted < eviction_size_limit)) { |
639 | 0 | assert(!(entry_ptr->is_protected)); |
640 | |
|
641 | 0 | prev_ptr = entry_ptr->prev; |
642 | |
|
643 | 0 | if (!(entry_ptr->is_dirty) && !(entry_ptr->prefetched_dirty)) |
644 | 0 | if (H5C__flush_single_entry( |
645 | 0 | f, entry_ptr, H5C__FLUSH_INVALIDATE_FLAG | H5C__DEL_FROM_SLIST_ON_DESTROY_FLAG) < 0) |
646 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "unable to flush clean entry"); |
647 | | |
648 | | /* just skip the entry if it is dirty, as we can't do |
649 | | * anything with it now since we can't write. |
650 | | * |
651 | | * Since all entries are clean, serialize() will not be called, |
652 | | * and thus we needn't test to see if the LRU has been changed |
653 | | * out from under us. |
654 | | */ |
655 | 0 | entry_ptr = prev_ptr; |
656 | 0 | } /* end while */ |
657 | 0 | } /* end else */ |
658 | | |
659 | 0 | if (cache_ptr->index_size < cache_ptr->max_cache_size) |
660 | 0 | cache_ptr->cache_full = false; |
661 | |
|
662 | 0 | done: |
663 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
664 | 0 | } /* H5C__autoadjust__ageout__evict_aged_out_entries() */ |
665 | | |
666 | | /*------------------------------------------------------------------------- |
667 | | * Function: H5C__autoadjust__ageout__insert_new_marker |
668 | | * |
669 | | * Purpose: Find an unused marker cache entry, mark it as used, and |
670 | | * insert it at the head of the LRU list. Also add the |
671 | | * marker's index in the epoch_markers array. |
672 | | * |
673 | | * Return: SUCCEED on success/FAIL on failure. |
674 | | * |
675 | | *------------------------------------------------------------------------- |
676 | | */ |
677 | | static herr_t |
678 | | H5C__autoadjust__ageout__insert_new_marker(H5C_t *cache_ptr) |
679 | 0 | { |
680 | 0 | int i; |
681 | 0 | herr_t ret_value = SUCCEED; /* Return value */ |
682 | |
|
683 | 0 | FUNC_ENTER_PACKAGE |
684 | |
|
685 | 0 | assert(cache_ptr); |
686 | |
|
687 | 0 | if (cache_ptr->epoch_markers_active >= cache_ptr->resize_ctl.epochs_before_eviction) |
688 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Already have a full complement of markers"); |
689 | | |
690 | | /* find an unused marker */ |
691 | 0 | i = 0; |
692 | 0 | while (i < H5C__MAX_EPOCH_MARKERS && (cache_ptr->epoch_marker_active)[i]) |
693 | 0 | i++; |
694 | 0 | if (i >= H5C__MAX_EPOCH_MARKERS) |
695 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Can't find unused marker"); |
696 | | |
697 | 0 | assert(((cache_ptr->epoch_markers)[i]).addr == (haddr_t)i); |
698 | 0 | assert(((cache_ptr->epoch_markers)[i]).next == NULL); |
699 | 0 | assert(((cache_ptr->epoch_markers)[i]).prev == NULL); |
700 | |
|
701 | 0 | (cache_ptr->epoch_marker_active)[i] = true; |
702 | |
|
703 | 0 | cache_ptr->epoch_marker_ringbuf_last = |
704 | 0 | (cache_ptr->epoch_marker_ringbuf_last + 1) % (H5C__MAX_EPOCH_MARKERS + 1); |
705 | 0 | (cache_ptr->epoch_marker_ringbuf)[cache_ptr->epoch_marker_ringbuf_last] = i; |
706 | 0 | if (cache_ptr->epoch_marker_ringbuf_size >= H5C__MAX_EPOCH_MARKERS) |
707 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "ring buffer overflow"); |
708 | | |
709 | 0 | cache_ptr->epoch_marker_ringbuf_size += 1; |
710 | |
|
711 | 0 | H5C__DLL_PREPEND(&(cache_ptr->epoch_markers[i]), cache_ptr->LRU_head_ptr, cache_ptr->LRU_tail_ptr, |
712 | 0 | cache_ptr->LRU_list_len, cache_ptr->LRU_list_size, FAIL) |
713 | |
|
714 | 0 | cache_ptr->epoch_markers_active += 1; |
715 | |
|
716 | 0 | done: |
717 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
718 | 0 | } /* H5C__autoadjust__ageout__insert_new_marker() */ |
719 | | |
720 | | /*------------------------------------------------------------------------- |
721 | | * Function: H5C__autoadjust__ageout__remove_all_markers |
722 | | * |
723 | | * Purpose: Remove all epoch markers from the LRU list and mark them |
724 | | * as inactive. |
725 | | * |
726 | | * Return: SUCCEED on success/FAIL on failure. |
727 | | * |
728 | | *------------------------------------------------------------------------- |
729 | | */ |
730 | | herr_t |
731 | | H5C__autoadjust__ageout__remove_all_markers(H5C_t *cache_ptr) |
732 | 0 | { |
733 | 0 | int ring_buf_index; |
734 | 0 | int i; |
735 | 0 | herr_t ret_value = SUCCEED; /* Return value */ |
736 | |
|
737 | 0 | FUNC_ENTER_PACKAGE |
738 | |
|
739 | 0 | assert(cache_ptr); |
740 | |
|
741 | 0 | while (cache_ptr->epoch_markers_active > 0) { |
742 | | /* get the index of the last epoch marker in the LRU list |
743 | | * and remove it from the ring buffer. |
744 | | */ |
745 | |
|
746 | 0 | ring_buf_index = cache_ptr->epoch_marker_ringbuf_first; |
747 | 0 | i = (cache_ptr->epoch_marker_ringbuf)[ring_buf_index]; |
748 | |
|
749 | 0 | cache_ptr->epoch_marker_ringbuf_first = |
750 | 0 | (cache_ptr->epoch_marker_ringbuf_first + 1) % (H5C__MAX_EPOCH_MARKERS + 1); |
751 | |
|
752 | 0 | if (cache_ptr->epoch_marker_ringbuf_size <= 0) |
753 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "ring buffer underflow"); |
754 | 0 | cache_ptr->epoch_marker_ringbuf_size -= 1; |
755 | |
|
756 | 0 | if (cache_ptr->epoch_marker_active[i] != true) |
757 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "unused marker in LRU?!?"); |
758 | | |
759 | | /* remove the epoch marker from the LRU list */ |
760 | 0 | H5C__DLL_REMOVE(&(cache_ptr->epoch_markers[i]), cache_ptr->LRU_head_ptr, cache_ptr->LRU_tail_ptr, |
761 | 0 | cache_ptr->LRU_list_len, cache_ptr->LRU_list_size, FAIL) |
762 | | |
763 | | /* mark the epoch marker as unused. */ |
764 | 0 | cache_ptr->epoch_marker_active[i] = false; |
765 | |
|
766 | 0 | assert(cache_ptr->epoch_markers[i].addr == (haddr_t)i); |
767 | 0 | assert(cache_ptr->epoch_markers[i].next == NULL); |
768 | 0 | assert(cache_ptr->epoch_markers[i].prev == NULL); |
769 | | |
770 | | /* decrement the number of active epoch markers */ |
771 | 0 | cache_ptr->epoch_markers_active -= 1; |
772 | |
|
773 | 0 | assert(cache_ptr->epoch_markers_active == cache_ptr->epoch_marker_ringbuf_size); |
774 | 0 | } |
775 | | |
776 | 0 | done: |
777 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
778 | 0 | } /* H5C__autoadjust__ageout__remove_all_markers() */ |
779 | | |
780 | | /*------------------------------------------------------------------------- |
781 | | * Function: H5C__autoadjust__ageout__remove_excess_markers |
782 | | * |
783 | | * Purpose: Remove epoch markers from the end of the LRU list and |
784 | | * mark them as inactive until the number of active markers |
785 | | * equals the current value of |
786 | | * cache_ptr->resize_ctl.epochs_before_eviction. |
787 | | * |
788 | | * Return: SUCCEED on success/FAIL on failure. |
789 | | * |
790 | | *------------------------------------------------------------------------- |
791 | | */ |
792 | | herr_t |
793 | | H5C__autoadjust__ageout__remove_excess_markers(H5C_t *cache_ptr) |
794 | 0 | { |
795 | 0 | int ring_buf_index; |
796 | 0 | int i; |
797 | 0 | herr_t ret_value = SUCCEED; /* Return value */ |
798 | |
|
799 | 0 | FUNC_ENTER_PACKAGE |
800 | |
|
801 | 0 | assert(cache_ptr); |
802 | |
|
803 | 0 | if (cache_ptr->epoch_markers_active <= cache_ptr->resize_ctl.epochs_before_eviction) |
804 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "no excess markers on entry"); |
805 | | |
806 | 0 | while (cache_ptr->epoch_markers_active > cache_ptr->resize_ctl.epochs_before_eviction) { |
807 | | /* get the index of the last epoch marker in the LRU list |
808 | | * and remove it from the ring buffer. |
809 | | */ |
810 | 0 | ring_buf_index = cache_ptr->epoch_marker_ringbuf_first; |
811 | 0 | i = (cache_ptr->epoch_marker_ringbuf)[ring_buf_index]; |
812 | |
|
813 | 0 | cache_ptr->epoch_marker_ringbuf_first = |
814 | 0 | (cache_ptr->epoch_marker_ringbuf_first + 1) % (H5C__MAX_EPOCH_MARKERS + 1); |
815 | |
|
816 | 0 | if (cache_ptr->epoch_marker_ringbuf_size <= 0) |
817 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "ring buffer underflow"); |
818 | 0 | cache_ptr->epoch_marker_ringbuf_size -= 1; |
819 | |
|
820 | 0 | if (cache_ptr->epoch_marker_active[i] != true) |
821 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "unused marker in LRU?!?"); |
822 | | |
823 | | /* remove the epoch marker from the LRU list */ |
824 | 0 | H5C__DLL_REMOVE(&(cache_ptr->epoch_markers[i]), cache_ptr->LRU_head_ptr, cache_ptr->LRU_tail_ptr, |
825 | 0 | cache_ptr->LRU_list_len, cache_ptr->LRU_list_size, FAIL) |
826 | | |
827 | | /* mark the epoch marker as unused. */ |
828 | 0 | cache_ptr->epoch_marker_active[i] = false; |
829 | |
|
830 | 0 | assert(cache_ptr->epoch_markers[i].addr == (haddr_t)i); |
831 | 0 | assert(cache_ptr->epoch_markers[i].next == NULL); |
832 | 0 | assert(cache_ptr->epoch_markers[i].prev == NULL); |
833 | | |
834 | | /* decrement the number of active epoch markers */ |
835 | 0 | cache_ptr->epoch_markers_active -= 1; |
836 | |
|
837 | 0 | assert(cache_ptr->epoch_markers_active == cache_ptr->epoch_marker_ringbuf_size); |
838 | 0 | } |
839 | | |
840 | 0 | done: |
841 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
842 | 0 | } /* H5C__autoadjust__ageout__remove_excess_markers() */ |
843 | | |
844 | | /*------------------------------------------------------------------------- |
845 | | * Function: H5C__flash_increase_cache_size |
846 | | * |
847 | | * Purpose: If there is not at least new_entry_size - old_entry_size |
848 | | * bytes of free space in the cache and the current |
849 | | * max_cache_size is less than cache_ptr->resize_ctl.max_size, |
850 | | * perform a flash increase in the cache size and then reset |
851 | | * the full cache hit rate statistics, and exit. |
852 | | * |
853 | | * Return: Non-negative on success/Negative on failure. |
854 | | * |
855 | | *------------------------------------------------------------------------- |
856 | | */ |
857 | | herr_t |
858 | | H5C__flash_increase_cache_size(H5C_t *cache_ptr, size_t old_entry_size, size_t new_entry_size) |
859 | 6 | { |
860 | 6 | size_t new_max_cache_size = 0; |
861 | 6 | size_t old_max_cache_size = 0; |
862 | 6 | size_t new_min_clean_size = 0; |
863 | 6 | size_t old_min_clean_size = 0; |
864 | 6 | size_t space_needed; |
865 | 6 | enum H5C_resize_status status = flash_increase; /* may change */ |
866 | 6 | double hit_rate; |
867 | 6 | herr_t ret_value = SUCCEED; /* Return value */ |
868 | | |
869 | 6 | FUNC_ENTER_PACKAGE |
870 | | |
871 | 6 | assert(cache_ptr); |
872 | 6 | assert(cache_ptr->flash_size_increase_possible); |
873 | 6 | assert(new_entry_size > cache_ptr->flash_size_increase_threshold); |
874 | 6 | assert(old_entry_size < new_entry_size); |
875 | | |
876 | 6 | if (old_entry_size >= new_entry_size) |
877 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "old_entry_size >= new_entry_size"); |
878 | | |
879 | 6 | space_needed = new_entry_size - old_entry_size; |
880 | 6 | if (((cache_ptr->index_size + space_needed) > cache_ptr->max_cache_size) && |
881 | 6 | (cache_ptr->max_cache_size < cache_ptr->resize_ctl.max_size)) { |
882 | 6 | switch (cache_ptr->resize_ctl.flash_incr_mode) { |
883 | 0 | case H5C_flash_incr__off: |
884 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, |
885 | 0 | "flash_size_increase_possible but H5C_flash_incr__off?!"); |
886 | 0 | break; |
887 | | |
888 | 6 | case H5C_flash_incr__add_space: |
889 | 6 | if (cache_ptr->index_size < cache_ptr->max_cache_size) { |
890 | 6 | assert((cache_ptr->max_cache_size - cache_ptr->index_size) < space_needed); |
891 | 6 | space_needed -= cache_ptr->max_cache_size - cache_ptr->index_size; |
892 | 6 | } |
893 | 6 | space_needed = (size_t)(((double)space_needed) * cache_ptr->resize_ctl.flash_multiple); |
894 | 6 | new_max_cache_size = cache_ptr->max_cache_size + space_needed; |
895 | 6 | break; |
896 | | |
897 | 0 | default: /* should be unreachable */ |
898 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Unknown flash_incr_mode?!?!?"); |
899 | 0 | break; |
900 | 6 | } |
901 | | |
902 | 6 | if (new_max_cache_size > cache_ptr->resize_ctl.max_size) |
903 | 6 | new_max_cache_size = cache_ptr->resize_ctl.max_size; |
904 | 6 | assert(new_max_cache_size > cache_ptr->max_cache_size); |
905 | | |
906 | 6 | new_min_clean_size = (size_t)((double)new_max_cache_size * cache_ptr->resize_ctl.min_clean_fraction); |
907 | 6 | assert(new_min_clean_size <= new_max_cache_size); |
908 | | |
909 | 6 | old_max_cache_size = cache_ptr->max_cache_size; |
910 | 6 | old_min_clean_size = cache_ptr->min_clean_size; |
911 | | |
912 | 6 | cache_ptr->max_cache_size = new_max_cache_size; |
913 | 6 | cache_ptr->min_clean_size = new_min_clean_size; |
914 | | |
915 | | /* update flash cache size increase fields as appropriate */ |
916 | 6 | assert(cache_ptr->flash_size_increase_possible); |
917 | | |
918 | 6 | switch (cache_ptr->resize_ctl.flash_incr_mode) { |
919 | 0 | case H5C_flash_incr__off: |
920 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, |
921 | 0 | "flash_size_increase_possible but H5C_flash_incr__off?!"); |
922 | 0 | break; |
923 | | |
924 | 6 | case H5C_flash_incr__add_space: |
925 | 6 | cache_ptr->flash_size_increase_threshold = |
926 | 6 | (size_t)((double)cache_ptr->max_cache_size * cache_ptr->resize_ctl.flash_threshold); |
927 | 6 | break; |
928 | | |
929 | 0 | default: /* should be unreachable */ |
930 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Unknown flash_incr_mode?!?!?"); |
931 | 0 | break; |
932 | 6 | } |
933 | | |
934 | | /* note that we don't cycle the epoch markers. We can |
935 | | * argue either way as to whether we should, but for now |
936 | | * we don't. |
937 | | */ |
938 | | |
939 | 6 | if (cache_ptr->resize_ctl.rpt_fcn != NULL) { |
940 | | /* get the hit rate for the reporting function. Should still |
941 | | * be good as we haven't reset the hit rate statistics. |
942 | | */ |
943 | 0 | if (H5C_get_cache_hit_rate(cache_ptr, &hit_rate) != SUCCEED) |
944 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Can't get hit rate"); |
945 | | |
946 | 0 | (cache_ptr->resize_ctl.rpt_fcn)(cache_ptr, H5C__CURR_AUTO_RESIZE_RPT_FCN_VER, hit_rate, status, |
947 | 0 | old_max_cache_size, new_max_cache_size, old_min_clean_size, |
948 | 0 | new_min_clean_size); |
949 | 0 | } |
950 | | |
951 | 6 | if (H5C_reset_cache_hit_rate_stats(cache_ptr) < 0) |
952 | | /* this should be impossible... */ |
953 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "H5C_reset_cache_hit_rate_stats failed"); |
954 | 6 | } |
955 | | |
956 | 6 | done: |
957 | 6 | FUNC_LEAVE_NOAPI(ret_value) |
958 | 6 | } /* H5C__flash_increase_cache_size() */ |
959 | | |
960 | | /*------------------------------------------------------------------------- |
961 | | * Function: H5C__flush_invalidate_cache |
962 | | * |
963 | | * Purpose: Flush and destroy the entries contained in the target |
964 | | * cache. |
965 | | * |
966 | | * If the cache contains protected entries, the function will |
967 | | * fail, as protected entries cannot be either flushed or |
968 | | * destroyed. However all unprotected entries should be |
969 | | * flushed and destroyed before the function returns failure. |
970 | | * |
971 | | * While pinned entries can usually be flushed, they cannot |
972 | | * be destroyed. However, they should be unpinned when all |
973 | | * the entries that reference them have been destroyed (thus |
974 | | * reduding the pinned entry's reference count to 0, allowing |
975 | | * it to be unpinned). |
976 | | * |
977 | | * If pinned entries are present, the function makes repeated |
978 | | * passes through the cache, flushing all dirty entries |
979 | | * (including the pinned dirty entries where permitted) and |
980 | | * destroying all unpinned entries. This process is repeated |
981 | | * until either the cache is empty, or the number of pinned |
982 | | * entries stops decreasing on each pass. |
983 | | * |
984 | | * Return: Non-negative on success/Negative on failure or if there was |
985 | | * a request to flush all items and something was protected. |
986 | | * |
987 | | *------------------------------------------------------------------------- |
988 | | */ |
989 | | herr_t |
990 | | H5C__flush_invalidate_cache(H5F_t *f, unsigned flags) |
991 | 15 | { |
992 | 15 | H5C_t *cache_ptr; |
993 | 15 | H5C_ring_t ring; |
994 | 15 | herr_t ret_value = SUCCEED; |
995 | | |
996 | 15 | FUNC_ENTER_PACKAGE |
997 | | |
998 | 15 | assert(f); |
999 | 15 | assert(f->shared); |
1000 | 15 | cache_ptr = f->shared->cache; |
1001 | 15 | assert(cache_ptr); |
1002 | 15 | assert(cache_ptr->slist_ptr); |
1003 | 15 | assert(cache_ptr->slist_enabled); |
1004 | | |
1005 | | #ifdef H5C_DO_SANITY_CHECKS |
1006 | | { |
1007 | | int32_t i; |
1008 | | uint32_t index_len = 0; |
1009 | | uint32_t slist_len = 0; |
1010 | | size_t index_size = (size_t)0; |
1011 | | size_t clean_index_size = (size_t)0; |
1012 | | size_t dirty_index_size = (size_t)0; |
1013 | | size_t slist_size = (size_t)0; |
1014 | | |
1015 | | assert(cache_ptr->index_ring_len[H5C_RING_UNDEFINED] == 0); |
1016 | | assert(cache_ptr->index_ring_size[H5C_RING_UNDEFINED] == (size_t)0); |
1017 | | assert(cache_ptr->clean_index_ring_size[H5C_RING_UNDEFINED] == (size_t)0); |
1018 | | assert(cache_ptr->dirty_index_ring_size[H5C_RING_UNDEFINED] == (size_t)0); |
1019 | | assert(cache_ptr->slist_ring_len[H5C_RING_UNDEFINED] == 0); |
1020 | | assert(cache_ptr->slist_ring_size[H5C_RING_UNDEFINED] == (size_t)0); |
1021 | | |
1022 | | for (i = H5C_RING_USER; i < H5C_RING_NTYPES; i++) { |
1023 | | index_len += cache_ptr->index_ring_len[i]; |
1024 | | index_size += cache_ptr->index_ring_size[i]; |
1025 | | clean_index_size += cache_ptr->clean_index_ring_size[i]; |
1026 | | dirty_index_size += cache_ptr->dirty_index_ring_size[i]; |
1027 | | |
1028 | | slist_len += cache_ptr->slist_ring_len[i]; |
1029 | | slist_size += cache_ptr->slist_ring_size[i]; |
1030 | | } /* end for */ |
1031 | | |
1032 | | assert(cache_ptr->index_len == index_len); |
1033 | | assert(cache_ptr->index_size == index_size); |
1034 | | assert(cache_ptr->clean_index_size == clean_index_size); |
1035 | | assert(cache_ptr->dirty_index_size == dirty_index_size); |
1036 | | assert(cache_ptr->slist_len == slist_len); |
1037 | | assert(cache_ptr->slist_size == slist_size); |
1038 | | } |
1039 | | #endif /* H5C_DO_SANITY_CHECKS */ |
1040 | | |
1041 | | /* remove ageout markers if present */ |
1042 | 15 | if (cache_ptr->epoch_markers_active > 0) |
1043 | 0 | if (H5C__autoadjust__ageout__remove_all_markers(cache_ptr) < 0) |
1044 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "error removing all epoch markers"); |
1045 | | |
1046 | | /* flush invalidate each ring, starting from the outermost ring and |
1047 | | * working inward. |
1048 | | */ |
1049 | 15 | ring = H5C_RING_USER; |
1050 | 78 | while (ring < H5C_RING_NTYPES) { |
1051 | 69 | if (H5C__flush_invalidate_ring(f, ring, flags) < 0) |
1052 | 6 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "flush invalidate ring failed"); |
1053 | 63 | ring++; |
1054 | 63 | } /* end while */ |
1055 | | |
1056 | | #ifndef NDEBUG |
1057 | | /* Invariants, after destroying all entries in the hash table */ |
1058 | | if (!(flags & H5C__EVICT_ALLOW_LAST_PINS_FLAG)) { |
1059 | | assert(cache_ptr->index_size == 0); |
1060 | | assert(cache_ptr->clean_index_size == 0); |
1061 | | assert(cache_ptr->pel_len == 0); |
1062 | | assert(cache_ptr->pel_size == 0); |
1063 | | } /* end if */ |
1064 | | else { |
1065 | | H5C_cache_entry_t *entry_ptr; /* Cache entry */ |
1066 | | unsigned u; /* Local index variable */ |
1067 | | |
1068 | | /* All rings except ring 4 should be empty now */ |
1069 | | /* (Ring 4 has the superblock) */ |
1070 | | for (u = H5C_RING_USER; u < H5C_RING_SB; u++) { |
1071 | | assert(cache_ptr->index_ring_len[u] == 0); |
1072 | | assert(cache_ptr->index_ring_size[u] == 0); |
1073 | | assert(cache_ptr->clean_index_ring_size[u] == 0); |
1074 | | } /* end for */ |
1075 | | |
1076 | | /* Check that any remaining pinned entries are in the superblock ring */ |
1077 | | entry_ptr = cache_ptr->pel_head_ptr; |
1078 | | while (entry_ptr) { |
1079 | | /* Check ring */ |
1080 | | assert(entry_ptr->ring == H5C_RING_SB); |
1081 | | |
1082 | | /* Advance to next entry in pinned entry list */ |
1083 | | entry_ptr = entry_ptr->next; |
1084 | | } /* end while */ |
1085 | | } /* end else */ |
1086 | | |
1087 | | assert(cache_ptr->dirty_index_size == 0); |
1088 | | assert(cache_ptr->slist_len == 0); |
1089 | | assert(cache_ptr->slist_size == 0); |
1090 | | assert(cache_ptr->pl_len == 0); |
1091 | | assert(cache_ptr->pl_size == 0); |
1092 | | assert(cache_ptr->LRU_list_len == 0); |
1093 | | assert(cache_ptr->LRU_list_size == 0); |
1094 | | #endif |
1095 | | |
1096 | 15 | done: |
1097 | 15 | FUNC_LEAVE_NOAPI(ret_value) |
1098 | 15 | } /* H5C__flush_invalidate_cache() */ |
1099 | | |
1100 | | /*------------------------------------------------------------------------- |
1101 | | * Function: H5C__flush_invalidate_ring |
1102 | | * |
1103 | | * Purpose: Flush and destroy the entries contained in the target |
1104 | | * cache and ring. |
1105 | | * |
1106 | | * If the ring contains protected entries, the function will |
1107 | | * fail, as protected entries cannot be either flushed or |
1108 | | * destroyed. However all unprotected entries should be |
1109 | | * flushed and destroyed before the function returns failure. |
1110 | | * |
1111 | | * While pinned entries can usually be flushed, they cannot |
1112 | | * be destroyed. However, they should be unpinned when all |
1113 | | * the entries that reference them have been destroyed (thus |
1114 | | * reduding the pinned entry's reference count to 0, allowing |
1115 | | * it to be unpinned). |
1116 | | * |
1117 | | * If pinned entries are present, the function makes repeated |
1118 | | * passes through the cache, flushing all dirty entries |
1119 | | * (including the pinned dirty entries where permitted) and |
1120 | | * destroying all unpinned entries. This process is repeated |
1121 | | * until either the cache is empty, or the number of pinned |
1122 | | * entries stops decreasing on each pass. |
1123 | | * |
1124 | | * If flush dependencies appear in the target ring, the |
1125 | | * function makes repeated passes through the cache flushing |
1126 | | * entries in flush dependency order. |
1127 | | * |
1128 | | * Return: Non-negative on success/Negative on failure or if there was |
1129 | | * a request to flush all items and something was protected. |
1130 | | * |
1131 | | *------------------------------------------------------------------------- |
1132 | | */ |
1133 | | static herr_t |
1134 | | H5C__flush_invalidate_ring(H5F_t *f, H5C_ring_t ring, unsigned flags) |
1135 | 69 | { |
1136 | 69 | H5C_t *cache_ptr; |
1137 | 69 | bool restart_slist_scan; |
1138 | 69 | uint32_t protected_entries = 0; |
1139 | 69 | int32_t i; |
1140 | 69 | uint32_t cur_ring_pel_len; |
1141 | 69 | uint32_t old_ring_pel_len; |
1142 | 69 | unsigned cooked_flags; |
1143 | 69 | unsigned evict_flags; |
1144 | 69 | H5SL_node_t *node_ptr = NULL; |
1145 | 69 | H5C_cache_entry_t *entry_ptr = NULL; |
1146 | 69 | H5C_cache_entry_t *next_entry_ptr = NULL; |
1147 | | #ifdef H5C_DO_SANITY_CHECKS |
1148 | | uint32_t initial_slist_len = 0; |
1149 | | size_t initial_slist_size = 0; |
1150 | | #endif /* H5C_DO_SANITY_CHECKS */ |
1151 | 69 | herr_t ret_value = SUCCEED; |
1152 | | |
1153 | 69 | FUNC_ENTER_PACKAGE |
1154 | | |
1155 | 69 | assert(f); |
1156 | 69 | assert(f->shared); |
1157 | 69 | cache_ptr = f->shared->cache; |
1158 | 69 | assert(cache_ptr); |
1159 | 69 | assert(cache_ptr->slist_enabled); |
1160 | 69 | assert(cache_ptr->slist_ptr); |
1161 | 69 | assert(ring > H5C_RING_UNDEFINED); |
1162 | 69 | assert(ring < H5C_RING_NTYPES); |
1163 | | |
1164 | 69 | assert(cache_ptr->epoch_markers_active == 0); |
1165 | | |
1166 | | /* Filter out the flags that are not relevant to the flush/invalidate. |
1167 | | */ |
1168 | 69 | cooked_flags = flags & H5C__FLUSH_CLEAR_ONLY_FLAG; |
1169 | 69 | evict_flags = flags & H5C__EVICT_ALLOW_LAST_PINS_FLAG; |
1170 | | |
1171 | | /* The flush procedure here is a bit strange. |
1172 | | * |
1173 | | * In the outer while loop we make at least one pass through the |
1174 | | * cache, and then repeat until either all the pinned entries in |
1175 | | * the ring unpin themselves, or until the number of pinned entries |
1176 | | * in the ring stops declining. In this later case, we scream and die. |
1177 | | * |
1178 | | * Since the fractal heap can dirty, resize, and/or move entries |
1179 | | * in is flush callback, it is possible that the cache will still |
1180 | | * contain dirty entries at this point. If so, we must make more |
1181 | | * passes through the skip list to allow it to empty. |
1182 | | * |
1183 | | * Further, since clean entries can be dirtied, resized, and/or moved |
1184 | | * as the result of a flush call back (either the entries own, or that |
1185 | | * for some other cache entry), we can no longer promise to flush |
1186 | | * the cache entries in increasing address order. |
1187 | | * |
1188 | | * Instead, we make a pass through |
1189 | | * the skip list, and then a pass through the "clean" entries, and |
1190 | | * then repeating as needed. Thus it is quite possible that an |
1191 | | * entry will be evicted from the cache only to be re-loaded later |
1192 | | * in the flush process. |
1193 | | * |
1194 | | * The bottom line is that entries will probably be flushed in close |
1195 | | * to increasing address order, but there are no guarantees. |
1196 | | */ |
1197 | | |
1198 | | /* compute the number of pinned entries in this ring */ |
1199 | 69 | entry_ptr = cache_ptr->pel_head_ptr; |
1200 | 69 | cur_ring_pel_len = 0; |
1201 | 69 | while (entry_ptr != NULL) { |
1202 | 0 | assert(entry_ptr->ring >= ring); |
1203 | 0 | if (entry_ptr->ring == ring) |
1204 | 0 | cur_ring_pel_len++; |
1205 | |
|
1206 | 0 | entry_ptr = entry_ptr->next; |
1207 | 0 | } /* end while */ |
1208 | 69 | old_ring_pel_len = cur_ring_pel_len; |
1209 | | |
1210 | 75 | while (cache_ptr->index_ring_len[ring] > 0) { |
1211 | | /* First, try to flush-destroy any dirty entries. Do this by |
1212 | | * making a scan through the slist. Note that new dirty entries |
1213 | | * may be created by the flush call back, thus we may need to |
1214 | | * restart the scan (see below). |
1215 | | */ |
1216 | | |
1217 | | #ifdef H5C_DO_SANITY_CHECKS |
1218 | | /* Depending on circumstances, H5C__flush_single_entry() will |
1219 | | * remove dirty entries from the slist as it flushes them. |
1220 | | * Thus for sanity checks we must make note of the initial |
1221 | | * slist length and size before we do any flushes. |
1222 | | */ |
1223 | | initial_slist_len = cache_ptr->slist_len; |
1224 | | initial_slist_size = cache_ptr->slist_size; |
1225 | | |
1226 | | /* There is also the possibility that entries will be |
1227 | | * dirtied, resized, moved, and/or removed from the cache |
1228 | | * as the result of calls to the flush callbacks. We use |
1229 | | * the slist_len_increase and slist_size_increase increase |
1230 | | * fields in struct H5C_t to track these changes for purpose |
1231 | | * of sanity checking. |
1232 | | * |
1233 | | * To this end, we must zero these fields before we start |
1234 | | * the pass through the slist. |
1235 | | */ |
1236 | | cache_ptr->slist_len_increase = 0; |
1237 | | cache_ptr->slist_size_increase = 0; |
1238 | | #endif /* H5C_DO_SANITY_CHECKS */ |
1239 | | |
1240 | | /* Set the cache_ptr->slist_changed to false. |
1241 | | * |
1242 | | * This flag is set to true by H5C__flush_single_entry if the slist |
1243 | | * is modified by a pre_serialize, serialize, or notify callback. |
1244 | | * |
1245 | | * H5C__flush_invalidate_ring() uses this flag to detect any |
1246 | | * modifications to the slist that might corrupt the scan of |
1247 | | * the slist -- and restart the scan in this event. |
1248 | | */ |
1249 | 12 | cache_ptr->slist_changed = false; |
1250 | | |
1251 | | /* this done, start the scan of the slist */ |
1252 | 12 | restart_slist_scan = true; |
1253 | 30 | while (restart_slist_scan || (node_ptr != NULL)) { |
1254 | 24 | if (restart_slist_scan) { |
1255 | 12 | restart_slist_scan = false; |
1256 | | |
1257 | | /* Start at beginning of skip list */ |
1258 | 12 | node_ptr = H5SL_first(cache_ptr->slist_ptr); |
1259 | 12 | if (node_ptr == NULL) |
1260 | | /* the slist is empty -- break out of inner loop */ |
1261 | 0 | break; |
1262 | | |
1263 | | /* Get cache entry for this node */ |
1264 | 12 | next_entry_ptr = (H5C_cache_entry_t *)H5SL_item(node_ptr); |
1265 | 12 | if (NULL == next_entry_ptr) |
1266 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "next_entry_ptr == NULL ?!?!"); |
1267 | | |
1268 | 12 | assert(next_entry_ptr->is_dirty); |
1269 | 12 | assert(next_entry_ptr->in_slist); |
1270 | 12 | assert(next_entry_ptr->ring >= ring); |
1271 | 12 | } /* end if */ |
1272 | | |
1273 | 24 | entry_ptr = next_entry_ptr; |
1274 | | |
1275 | | /* It is possible that entries will be dirtied, resized, |
1276 | | * flushed, or removed from the cache via the take ownership |
1277 | | * flag as the result of pre_serialize or serialized callbacks. |
1278 | | * |
1279 | | * This in turn can corrupt the scan through the slist. |
1280 | | * |
1281 | | * We test for slist modifications in the pre_serialize |
1282 | | * and serialize callbacks, and restart the scan of the |
1283 | | * slist if we find them. However, best we do some extra |
1284 | | * sanity checking just in case. |
1285 | | */ |
1286 | 24 | assert(entry_ptr != NULL); |
1287 | 24 | assert(entry_ptr->in_slist); |
1288 | 24 | assert(entry_ptr->is_dirty); |
1289 | 24 | assert(entry_ptr->ring >= ring); |
1290 | | |
1291 | | /* increment node pointer now, before we delete its target |
1292 | | * from the slist. |
1293 | | */ |
1294 | 24 | node_ptr = H5SL_next(node_ptr); |
1295 | 24 | if (node_ptr != NULL) { |
1296 | 12 | next_entry_ptr = (H5C_cache_entry_t *)H5SL_item(node_ptr); |
1297 | 12 | if (NULL == next_entry_ptr) |
1298 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "next_entry_ptr == NULL ?!?!"); |
1299 | | |
1300 | 12 | assert(next_entry_ptr->is_dirty); |
1301 | 12 | assert(next_entry_ptr->in_slist); |
1302 | 12 | assert(next_entry_ptr->ring >= ring); |
1303 | 12 | assert(entry_ptr != next_entry_ptr); |
1304 | 12 | } /* end if */ |
1305 | 12 | else |
1306 | 12 | next_entry_ptr = NULL; |
1307 | | |
1308 | | /* Note that we now remove nodes from the slist as we flush |
1309 | | * the associated entries, instead of leaving them there |
1310 | | * until we are done, and then destroying all nodes in |
1311 | | * the slist. |
1312 | | * |
1313 | | * While this optimization used to be easy, with the possibility |
1314 | | * of new entries being added to the slist in the midst of the |
1315 | | * flush, we must keep the slist in canonical form at all |
1316 | | * times. |
1317 | | */ |
1318 | 24 | if (((!entry_ptr->flush_me_last) || |
1319 | 24 | ((entry_ptr->flush_me_last) && (cache_ptr->num_last_entries >= cache_ptr->slist_len))) && |
1320 | 24 | (entry_ptr->flush_dep_nchildren == 0) && (entry_ptr->ring == ring)) { |
1321 | 6 | if (entry_ptr->is_protected) { |
1322 | | /* We have major problems -- but lets flush |
1323 | | * everything we can before we flag an error. |
1324 | | */ |
1325 | 0 | protected_entries++; |
1326 | 0 | } /* end if */ |
1327 | 6 | else if (entry_ptr->is_pinned) { |
1328 | 0 | if (H5C__flush_single_entry(f, entry_ptr, H5C__DURING_FLUSH_FLAG) < 0) |
1329 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "dirty pinned entry flush failed"); |
1330 | | |
1331 | 0 | if (cache_ptr->slist_changed) { |
1332 | | /* The slist has been modified by something |
1333 | | * other than the simple removal of the |
1334 | | * of the flushed entry after the flush. |
1335 | | * |
1336 | | * This has the potential to corrupt the |
1337 | | * scan through the slist, so restart it. |
1338 | | */ |
1339 | 0 | restart_slist_scan = true; |
1340 | 0 | cache_ptr->slist_changed = false; |
1341 | 0 | H5C__UPDATE_STATS_FOR_SLIST_SCAN_RESTART(cache_ptr); |
1342 | 0 | } /* end if */ |
1343 | 0 | } /* end else-if */ |
1344 | 6 | else { |
1345 | 6 | if (H5C__flush_single_entry(f, entry_ptr, |
1346 | 6 | (cooked_flags | H5C__DURING_FLUSH_FLAG | |
1347 | 6 | H5C__FLUSH_INVALIDATE_FLAG | |
1348 | 6 | H5C__DEL_FROM_SLIST_ON_DESTROY_FLAG)) < 0) |
1349 | 6 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "dirty entry flush destroy failed"); |
1350 | | |
1351 | 0 | if (cache_ptr->slist_changed) { |
1352 | | /* The slist has been modified by something |
1353 | | * other than the simple removal of the |
1354 | | * of the flushed entry after the flush. |
1355 | | * |
1356 | | * This has the potential to corrupt the |
1357 | | * scan through the slist, so restart it. |
1358 | | */ |
1359 | 0 | restart_slist_scan = true; |
1360 | 0 | cache_ptr->slist_changed = false; |
1361 | 0 | H5C__UPDATE_STATS_FOR_SLIST_SCAN_RESTART(cache_ptr); |
1362 | 0 | } /* end if */ |
1363 | 0 | } /* end else */ |
1364 | 6 | } /* end if */ |
1365 | 24 | } /* end while loop scanning skip list */ |
1366 | | |
1367 | | #ifdef H5C_DO_SANITY_CHECKS |
1368 | | /* It is possible that entries were added to the slist during |
1369 | | * the scan, either before or after scan pointer. The following |
1370 | | * asserts take this into account. |
1371 | | * |
1372 | | * Don't bother with the sanity checks if node_ptr != NULL, as |
1373 | | * in this case we broke out of the loop because it got changed |
1374 | | * out from under us. |
1375 | | */ |
1376 | | |
1377 | | if (node_ptr == NULL) { |
1378 | | assert(cache_ptr->slist_len == |
1379 | | (uint32_t)((int32_t)initial_slist_len + cache_ptr->slist_len_increase)); |
1380 | | assert(cache_ptr->slist_size == |
1381 | | (size_t)((ssize_t)initial_slist_size + cache_ptr->slist_size_increase)); |
1382 | | } /* end if */ |
1383 | | #endif /* H5C_DO_SANITY_CHECKS */ |
1384 | | |
1385 | | /* Since we are doing a destroy, we must make a pass through |
1386 | | * the hash table and try to flush - destroy all entries that |
1387 | | * remain. |
1388 | | * |
1389 | | * It used to be that all entries remaining in the cache at |
1390 | | * this point had to be clean, but with the fractal heap mods |
1391 | | * this may not be the case. If so, we will flush entries out |
1392 | | * in increasing address order. |
1393 | | * |
1394 | | * Writes to disk are possible here. |
1395 | | */ |
1396 | | |
1397 | | /* Reset the counters so that we can detect insertions, loads, |
1398 | | * and moves caused by the pre_serialize and serialize calls. |
1399 | | */ |
1400 | 6 | cache_ptr->entries_loaded_counter = 0; |
1401 | 6 | cache_ptr->entries_inserted_counter = 0; |
1402 | 6 | cache_ptr->entries_relocated_counter = 0; |
1403 | | |
1404 | 6 | next_entry_ptr = cache_ptr->il_head; |
1405 | 24 | while (next_entry_ptr != NULL) { |
1406 | 18 | entry_ptr = next_entry_ptr; |
1407 | 18 | assert(entry_ptr->ring >= ring); |
1408 | | |
1409 | 18 | next_entry_ptr = entry_ptr->il_next; |
1410 | | |
1411 | 18 | if (((!entry_ptr->flush_me_last) || |
1412 | 12 | (entry_ptr->flush_me_last && (cache_ptr->num_last_entries >= cache_ptr->slist_len))) && |
1413 | 18 | (entry_ptr->flush_dep_nchildren == 0) && (entry_ptr->ring == ring)) { |
1414 | | |
1415 | 6 | if (entry_ptr->is_protected) { |
1416 | | /* we have major problems -- but lets flush and |
1417 | | * destroy everything we can before we flag an |
1418 | | * error. |
1419 | | */ |
1420 | 0 | protected_entries++; |
1421 | |
|
1422 | 0 | if (!entry_ptr->in_slist) |
1423 | 0 | assert(!(entry_ptr->is_dirty)); |
1424 | 0 | } /* end if */ |
1425 | 6 | else if (!entry_ptr->is_pinned) { |
1426 | | /* if *entry_ptr is dirty, it is possible |
1427 | | * that one or more other entries may be |
1428 | | * either removed from the cache, loaded |
1429 | | * into the cache, or moved to a new location |
1430 | | * in the file as a side effect of the flush. |
1431 | | * |
1432 | | * It's also possible that removing a clean |
1433 | | * entry will remove the last child of a proxy |
1434 | | * entry, allowing it to be removed also and |
1435 | | * invalidating the next_entry_ptr. |
1436 | | * |
1437 | | * If either of these happen, and one of the target |
1438 | | * or proxy entries happens to be the next entry in |
1439 | | * the hash bucket, we could either find ourselves |
1440 | | * either scanning a non-existent entry, scanning |
1441 | | * through a different bucket, or skipping an entry. |
1442 | | * |
1443 | | * Neither of these are good, so restart the |
1444 | | * the scan at the head of the hash bucket |
1445 | | * after the flush if we detect that the next_entry_ptr |
1446 | | * becomes invalid. |
1447 | | * |
1448 | | * This is not as inefficient at it might seem, |
1449 | | * as hash buckets typically have at most two |
1450 | | * or three entries. |
1451 | | */ |
1452 | 6 | cache_ptr->entry_watched_for_removal = next_entry_ptr; |
1453 | 6 | if (H5C__flush_single_entry(f, entry_ptr, |
1454 | 6 | (cooked_flags | H5C__DURING_FLUSH_FLAG | |
1455 | 6 | H5C__FLUSH_INVALIDATE_FLAG | |
1456 | 6 | H5C__DEL_FROM_SLIST_ON_DESTROY_FLAG)) < 0) |
1457 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "Entry flush destroy failed"); |
1458 | | |
1459 | | /* Restart the index list scan if necessary. Must |
1460 | | * do this if the next entry is evicted, and also if |
1461 | | * one or more entries are inserted, loaded, or moved |
1462 | | * as these operations can result in part of the scan |
1463 | | * being skipped -- which can cause a spurious failure |
1464 | | * if this results in the size of the pinned entry |
1465 | | * failing to decline during the pass. |
1466 | | */ |
1467 | 6 | if (((NULL != next_entry_ptr) && (NULL == cache_ptr->entry_watched_for_removal)) || |
1468 | 6 | (cache_ptr->entries_loaded_counter > 0) || |
1469 | 6 | (cache_ptr->entries_inserted_counter > 0) || |
1470 | 6 | (cache_ptr->entries_relocated_counter > 0)) { |
1471 | |
|
1472 | 0 | next_entry_ptr = cache_ptr->il_head; |
1473 | |
|
1474 | 0 | cache_ptr->entries_loaded_counter = 0; |
1475 | 0 | cache_ptr->entries_inserted_counter = 0; |
1476 | 0 | cache_ptr->entries_relocated_counter = 0; |
1477 | |
|
1478 | 0 | H5C__UPDATE_STATS_FOR_INDEX_SCAN_RESTART(cache_ptr); |
1479 | 0 | } /* end if */ |
1480 | 6 | else |
1481 | 6 | cache_ptr->entry_watched_for_removal = NULL; |
1482 | 6 | } /* end if */ |
1483 | 6 | } /* end if */ |
1484 | 18 | } /* end for loop scanning hash table */ |
1485 | | |
1486 | | /* We can't do anything if entries are pinned. The |
1487 | | * hope is that the entries will be unpinned as the |
1488 | | * result of destroys of entries that reference them. |
1489 | | * |
1490 | | * We detect this by noting the change in the number |
1491 | | * of pinned entries from pass to pass. If it stops |
1492 | | * shrinking before it hits zero, we scream and die. |
1493 | | */ |
1494 | 6 | old_ring_pel_len = cur_ring_pel_len; |
1495 | 6 | entry_ptr = cache_ptr->pel_head_ptr; |
1496 | 6 | cur_ring_pel_len = 0; |
1497 | | |
1498 | 6 | while (entry_ptr != NULL) { |
1499 | 0 | assert(entry_ptr->ring >= ring); |
1500 | |
|
1501 | 0 | if (entry_ptr->ring == ring) |
1502 | 0 | cur_ring_pel_len++; |
1503 | |
|
1504 | 0 | entry_ptr = entry_ptr->next; |
1505 | 0 | } /* end while */ |
1506 | | |
1507 | | /* Check if the number of pinned entries in the ring is positive, and |
1508 | | * it is not declining. Scream and die if so. |
1509 | | */ |
1510 | 6 | if ((cur_ring_pel_len > 0) && (cur_ring_pel_len >= old_ring_pel_len)) { |
1511 | | /* Don't error if allowed to have pinned entries remaining */ |
1512 | 0 | if (evict_flags) |
1513 | 0 | HGOTO_DONE(true); |
1514 | | |
1515 | 0 | HGOTO_ERROR( |
1516 | 0 | H5E_CACHE, H5E_CANTFLUSH, FAIL, |
1517 | 0 | "Pinned entry count not decreasing, cur_ring_pel_len = %d, old_ring_pel_len = %d, ring = %d", |
1518 | 0 | (int)cur_ring_pel_len, (int)old_ring_pel_len, (int)ring); |
1519 | 0 | } /* end if */ |
1520 | | |
1521 | 6 | assert(protected_entries == cache_ptr->pl_len); |
1522 | | |
1523 | 6 | if ((protected_entries > 0) && (protected_entries == cache_ptr->index_len)) |
1524 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, |
1525 | 6 | "Only protected entries left in cache, protected_entries = %d", |
1526 | 6 | (int)protected_entries); |
1527 | 6 | } /* main while loop */ |
1528 | | |
1529 | | /* Invariants, after destroying all entries in the ring */ |
1530 | 297 | for (i = (int)H5C_RING_UNDEFINED; i <= (int)ring; i++) { |
1531 | 234 | assert(cache_ptr->index_ring_len[i] == 0); |
1532 | 234 | assert(cache_ptr->index_ring_size[i] == (size_t)0); |
1533 | 234 | assert(cache_ptr->clean_index_ring_size[i] == (size_t)0); |
1534 | 234 | assert(cache_ptr->dirty_index_ring_size[i] == (size_t)0); |
1535 | | |
1536 | 234 | assert(cache_ptr->slist_ring_len[i] == 0); |
1537 | 234 | assert(cache_ptr->slist_ring_size[i] == (size_t)0); |
1538 | 234 | } /* end for */ |
1539 | | |
1540 | 63 | assert(protected_entries <= cache_ptr->pl_len); |
1541 | | |
1542 | 63 | if (protected_entries > 0) |
1543 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "Cache has protected entries"); |
1544 | 63 | else if (cur_ring_pel_len > 0) |
1545 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "Can't unpin all pinned entries in ring"); |
1546 | | |
1547 | 69 | done: |
1548 | 69 | FUNC_LEAVE_NOAPI(ret_value) |
1549 | 69 | } /* H5C__flush_invalidate_ring() */ |
1550 | | |
1551 | | /*------------------------------------------------------------------------- |
1552 | | * Function: H5C__flush_ring |
1553 | | * |
1554 | | * Purpose: Flush the entries contained in the specified cache and |
1555 | | * ring. All entries in rings outside the specified ring |
1556 | | * must have been flushed on entry. |
1557 | | * |
1558 | | * If the cache contains protected entries in the specified |
1559 | | * ring, the function will fail, as protected entries cannot |
1560 | | * be flushed. However all unprotected entries in the target |
1561 | | * ring should be flushed before the function returns failure. |
1562 | | * |
1563 | | * If flush dependencies appear in the target ring, the |
1564 | | * function makes repeated passes through the slist flushing |
1565 | | * entries in flush dependency order. |
1566 | | * |
1567 | | * Return: Non-negative on success/Negative on failure or if there was |
1568 | | * a request to flush all items and something was protected. |
1569 | | * |
1570 | | *------------------------------------------------------------------------- |
1571 | | */ |
1572 | | herr_t |
1573 | | H5C__flush_ring(H5F_t *f, H5C_ring_t ring, unsigned flags) |
1574 | 120 | { |
1575 | 120 | H5C_t *cache_ptr = f->shared->cache; |
1576 | 120 | bool flushed_entries_last_pass; |
1577 | 120 | bool ignore_protected; |
1578 | 120 | bool tried_to_flush_protected_entry = false; |
1579 | 120 | bool restart_slist_scan; |
1580 | 120 | uint32_t protected_entries = 0; |
1581 | 120 | H5SL_node_t *node_ptr = NULL; |
1582 | 120 | H5C_cache_entry_t *entry_ptr = NULL; |
1583 | 120 | H5C_cache_entry_t *next_entry_ptr = NULL; |
1584 | | #ifdef H5C_DO_SANITY_CHECKS |
1585 | | uint32_t initial_slist_len = 0; |
1586 | | size_t initial_slist_size = 0; |
1587 | | #endif /* H5C_DO_SANITY_CHECKS */ |
1588 | 120 | int i; |
1589 | 120 | herr_t ret_value = SUCCEED; |
1590 | | |
1591 | 120 | FUNC_ENTER_PACKAGE |
1592 | | |
1593 | 120 | assert(cache_ptr); |
1594 | 120 | assert(cache_ptr->slist_enabled); |
1595 | 120 | assert(cache_ptr->slist_ptr); |
1596 | 120 | assert((flags & H5C__FLUSH_INVALIDATE_FLAG) == 0); |
1597 | 120 | assert(ring > H5C_RING_UNDEFINED); |
1598 | 120 | assert(ring < H5C_RING_NTYPES); |
1599 | | |
1600 | | #ifdef H5C_DO_EXTREME_SANITY_CHECKS |
1601 | | if (H5C__validate_protected_entry_list(cache_ptr) < 0 || H5C__validate_pinned_entry_list(cache_ptr) < 0 || |
1602 | | H5C__validate_lru_list(cache_ptr) < 0) |
1603 | | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "an extreme sanity check failed on entry"); |
1604 | | #endif /* H5C_DO_EXTREME_SANITY_CHECKS */ |
1605 | | |
1606 | 120 | ignore_protected = ((flags & H5C__FLUSH_IGNORE_PROTECTED_FLAG) != 0); |
1607 | | |
1608 | 480 | for (i = (int)H5C_RING_UNDEFINED; i < (int)ring; i++) |
1609 | 360 | assert(cache_ptr->slist_ring_len[i] == 0); |
1610 | | |
1611 | 120 | assert(cache_ptr->flush_in_progress); |
1612 | | |
1613 | | /* When we are only flushing marked entries, the slist will usually |
1614 | | * still contain entries when we have flushed everything we should. |
1615 | | * Thus we track whether we have flushed any entries in the last |
1616 | | * pass, and terminate if we haven't. |
1617 | | */ |
1618 | 120 | flushed_entries_last_pass = true; |
1619 | | |
1620 | | /* Set the cache_ptr->slist_changed to false. |
1621 | | * |
1622 | | * This flag is set to true by H5C__flush_single_entry if the |
1623 | | * slist is modified by a pre_serialize, serialize, or notify callback. |
1624 | | * H5C_flush_cache uses this flag to detect any modifications |
1625 | | * to the slist that might corrupt the scan of the slist -- and |
1626 | | * restart the scan in this event. |
1627 | | */ |
1628 | 120 | cache_ptr->slist_changed = false; |
1629 | | |
1630 | 126 | while ((cache_ptr->slist_ring_len[ring] > 0) && (protected_entries == 0) && (flushed_entries_last_pass)) { |
1631 | 6 | flushed_entries_last_pass = false; |
1632 | | |
1633 | | #ifdef H5C_DO_SANITY_CHECKS |
1634 | | /* For sanity checking, try to verify that the skip list has |
1635 | | * the expected size and number of entries at the end of each |
1636 | | * internal while loop (see below). |
1637 | | * |
1638 | | * Doing this get a bit tricky, as depending on flags, we may |
1639 | | * or may not flush all the entries in the slist. |
1640 | | * |
1641 | | * To make things more entertaining, with the advent of the |
1642 | | * fractal heap, the entry serialize callback can cause entries |
1643 | | * to be dirtied, resized, and/or moved. Also, the |
1644 | | * pre_serialize callback can result in an entry being |
1645 | | * removed from the cache via the take ownership flag. |
1646 | | * |
1647 | | * To deal with this, we first make note of the initial |
1648 | | * skip list length and size: |
1649 | | */ |
1650 | | initial_slist_len = cache_ptr->slist_len; |
1651 | | initial_slist_size = cache_ptr->slist_size; |
1652 | | |
1653 | | /* As mentioned above, there is the possibility that |
1654 | | * entries will be dirtied, resized, flushed, or removed |
1655 | | * from the cache via the take ownership flag during |
1656 | | * our pass through the skip list. To capture the number |
1657 | | * of entries added, and the skip list size delta, |
1658 | | * zero the slist_len_increase and slist_size_increase of |
1659 | | * the cache's instance of H5C_t. These fields will be |
1660 | | * updated elsewhere to account for slist insertions and/or |
1661 | | * dirty entry size changes. |
1662 | | */ |
1663 | | cache_ptr->slist_len_increase = 0; |
1664 | | cache_ptr->slist_size_increase = 0; |
1665 | | |
1666 | | /* at the end of the loop, use these values to compute the |
1667 | | * expected slist length and size and compare this with the |
1668 | | * value recorded in the cache's instance of H5C_t. |
1669 | | */ |
1670 | | #endif /* H5C_DO_SANITY_CHECKS */ |
1671 | | |
1672 | 6 | restart_slist_scan = true; |
1673 | 12 | while ((restart_slist_scan) || (node_ptr != NULL)) { |
1674 | 6 | if (restart_slist_scan) { |
1675 | 6 | restart_slist_scan = false; |
1676 | | |
1677 | | /* Start at beginning of skip list */ |
1678 | 6 | node_ptr = H5SL_first(cache_ptr->slist_ptr); |
1679 | 6 | if (node_ptr == NULL) |
1680 | | /* the slist is empty -- break out of inner loop */ |
1681 | 0 | break; |
1682 | | |
1683 | | /* Get cache entry for this node */ |
1684 | 6 | next_entry_ptr = (H5C_cache_entry_t *)H5SL_item(node_ptr); |
1685 | 6 | if (NULL == next_entry_ptr) |
1686 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "next_entry_ptr == NULL ?!?!"); |
1687 | | |
1688 | 6 | assert(next_entry_ptr->is_dirty); |
1689 | 6 | assert(next_entry_ptr->in_slist); |
1690 | 6 | } /* end if */ |
1691 | | |
1692 | 6 | entry_ptr = next_entry_ptr; |
1693 | | |
1694 | | /* With the advent of the fractal heap, the free space |
1695 | | * manager, and the version 3 cache, it is possible |
1696 | | * that the pre-serialize or serialize callback will |
1697 | | * dirty, resize, or take ownership of other entries |
1698 | | * in the cache. |
1699 | | * |
1700 | | * To deal with this, there is code to detect any |
1701 | | * change in the skip list not directly under the control |
1702 | | * of this function. If such modifications are detected, |
1703 | | * we must re-start the scan of the skip list to avoid |
1704 | | * the possibility that the target of the next_entry_ptr |
1705 | | * may have been flushed or deleted from the cache. |
1706 | | * |
1707 | | * To verify that all such possibilities have been dealt |
1708 | | * with, we do a bit of extra sanity checking on |
1709 | | * entry_ptr. |
1710 | | */ |
1711 | 6 | assert(entry_ptr->in_slist); |
1712 | 6 | assert(entry_ptr->is_dirty); |
1713 | 6 | assert(entry_ptr->ring >= ring); |
1714 | | |
1715 | | /* Advance node pointer now, before we delete its target |
1716 | | * from the slist. |
1717 | | */ |
1718 | 6 | node_ptr = H5SL_next(node_ptr); |
1719 | 6 | if (node_ptr != NULL) { |
1720 | 0 | next_entry_ptr = (H5C_cache_entry_t *)H5SL_item(node_ptr); |
1721 | 0 | if (NULL == next_entry_ptr) |
1722 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "next_entry_ptr == NULL ?!?!"); |
1723 | | |
1724 | 0 | assert(next_entry_ptr->is_dirty); |
1725 | 0 | assert(next_entry_ptr->in_slist); |
1726 | 0 | assert(next_entry_ptr->ring >= ring); |
1727 | 0 | assert(entry_ptr != next_entry_ptr); |
1728 | 0 | } /* end if */ |
1729 | 6 | else |
1730 | 6 | next_entry_ptr = NULL; |
1731 | | |
1732 | 6 | if (((!entry_ptr->flush_me_last) || |
1733 | 6 | ((entry_ptr->flush_me_last) && cache_ptr->num_last_entries >= cache_ptr->slist_len)) && |
1734 | 6 | ((entry_ptr->flush_dep_nchildren == 0) || (entry_ptr->flush_dep_ndirty_children == 0)) && |
1735 | 6 | (entry_ptr->ring == ring)) { |
1736 | | |
1737 | 6 | assert(entry_ptr->flush_dep_nunser_children == 0); |
1738 | | |
1739 | 6 | if (entry_ptr->is_protected) { |
1740 | | /* we probably have major problems -- but lets |
1741 | | * flush everything we can before we decide |
1742 | | * whether to flag an error. |
1743 | | */ |
1744 | 0 | tried_to_flush_protected_entry = true; |
1745 | 0 | protected_entries++; |
1746 | 0 | } /* end if */ |
1747 | 6 | else { |
1748 | 6 | if (H5C__flush_single_entry(f, entry_ptr, (flags | H5C__DURING_FLUSH_FLAG)) < 0) |
1749 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "Can't flush entry"); |
1750 | | |
1751 | 6 | if (cache_ptr->slist_changed) { |
1752 | | /* The slist has been modified by something |
1753 | | * other than the simple removal of the |
1754 | | * of the flushed entry after the flush. |
1755 | | * |
1756 | | * This has the potential to corrupt the |
1757 | | * scan through the slist, so restart it. |
1758 | | */ |
1759 | 0 | restart_slist_scan = true; |
1760 | 0 | cache_ptr->slist_changed = false; |
1761 | 0 | H5C__UPDATE_STATS_FOR_SLIST_SCAN_RESTART(cache_ptr); |
1762 | 0 | } /* end if */ |
1763 | | |
1764 | 6 | flushed_entries_last_pass = true; |
1765 | 6 | } /* end else */ |
1766 | 6 | } /* end if */ |
1767 | 6 | } /* while ( ( restart_slist_scan ) || ( node_ptr != NULL ) ) */ |
1768 | | |
1769 | | #ifdef H5C_DO_SANITY_CHECKS |
1770 | | /* Verify that the slist size and length are as expected. */ |
1771 | | assert((uint32_t)((int32_t)initial_slist_len + cache_ptr->slist_len_increase) == |
1772 | | cache_ptr->slist_len); |
1773 | | assert((size_t)((ssize_t)initial_slist_size + cache_ptr->slist_size_increase) == |
1774 | | cache_ptr->slist_size); |
1775 | | #endif /* H5C_DO_SANITY_CHECKS */ |
1776 | 6 | } /* while */ |
1777 | | |
1778 | 120 | assert(protected_entries <= cache_ptr->pl_len); |
1779 | | |
1780 | 120 | if (((cache_ptr->pl_len > 0) && !ignore_protected) || tried_to_flush_protected_entry) |
1781 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "cache has protected items"); |
1782 | | |
1783 | | #ifdef H5C_DO_SANITY_CHECKS |
1784 | | assert(cache_ptr->slist_ring_len[ring] == 0); |
1785 | | assert(cache_ptr->slist_ring_size[ring] == 0); |
1786 | | #endif /* H5C_DO_SANITY_CHECKS */ |
1787 | | |
1788 | 120 | done: |
1789 | 120 | FUNC_LEAVE_NOAPI(ret_value) |
1790 | 120 | } /* H5C__flush_ring() */ |
1791 | | |
1792 | | /*------------------------------------------------------------------------- |
1793 | | * Function: H5C__make_space_in_cache |
1794 | | * |
1795 | | * Purpose: Attempt to evict cache entries until the index_size |
1796 | | * is at least needed_space below max_cache_size. |
1797 | | * |
1798 | | * In passing, also attempt to bring cLRU_list_size to a |
1799 | | * value greater than min_clean_size. |
1800 | | * |
1801 | | * Depending on circumstances, both of these goals may |
1802 | | * be impossible, as in parallel mode, we must avoid generating |
1803 | | * a write as part of a read (to avoid deadlock in collective |
1804 | | * I/O), and in all cases, it is possible (though hopefully |
1805 | | * highly unlikely) that the protected list may exceed the |
1806 | | * maximum size of the cache. |
1807 | | * |
1808 | | * Thus the function simply does its best, returning success |
1809 | | * unless an error is encountered. |
1810 | | * |
1811 | | * Observe that this function cannot occasion a read. |
1812 | | * |
1813 | | * Return: Non-negative on success/Negative on failure. |
1814 | | * |
1815 | | *------------------------------------------------------------------------- |
1816 | | */ |
1817 | | herr_t |
1818 | | H5C__make_space_in_cache(H5F_t *f, size_t space_needed, bool write_permitted) |
1819 | 12 | { |
1820 | 12 | H5C_t *cache_ptr = f->shared->cache; |
1821 | | #if H5C_COLLECT_CACHE_STATS |
1822 | | int32_t clean_entries_skipped = 0; |
1823 | | int32_t dirty_pf_entries_skipped = 0; |
1824 | | int32_t total_entries_scanned = 0; |
1825 | | #endif /* H5C_COLLECT_CACHE_STATS */ |
1826 | 12 | uint32_t entries_examined = 0; |
1827 | 12 | uint32_t initial_list_len; |
1828 | 12 | size_t empty_space; |
1829 | 12 | bool reentrant_call = false; |
1830 | 12 | bool prev_is_dirty = false; |
1831 | 12 | bool didnt_flush_entry = false; |
1832 | 12 | bool restart_scan; |
1833 | 12 | H5C_cache_entry_t *entry_ptr; |
1834 | 12 | H5C_cache_entry_t *prev_ptr; |
1835 | 12 | H5C_cache_entry_t *next_ptr; |
1836 | | #ifndef NDEBUG |
1837 | | uint32_t num_corked_entries = 0; |
1838 | | #endif |
1839 | 12 | herr_t ret_value = SUCCEED; /* Return value */ |
1840 | | |
1841 | 12 | FUNC_ENTER_PACKAGE |
1842 | | |
1843 | | /* Sanity checks */ |
1844 | 12 | assert(f); |
1845 | 12 | assert(cache_ptr); |
1846 | 12 | assert(cache_ptr->index_size == (cache_ptr->clean_index_size + cache_ptr->dirty_index_size)); |
1847 | | |
1848 | | /* check to see if cache_ptr->msic_in_progress is true. If it, this |
1849 | | * is a re-entrant call via a client callback called in the make |
1850 | | * space in cache process. To avoid an infinite recursion, set |
1851 | | * reentrant_call to true, and goto done. |
1852 | | */ |
1853 | 12 | if (cache_ptr->msic_in_progress) { |
1854 | 0 | reentrant_call = true; |
1855 | 0 | HGOTO_DONE(SUCCEED); |
1856 | 0 | } /* end if */ |
1857 | | |
1858 | 12 | cache_ptr->msic_in_progress = true; |
1859 | | |
1860 | 12 | if (write_permitted) { |
1861 | 12 | restart_scan = false; |
1862 | 12 | initial_list_len = cache_ptr->LRU_list_len; |
1863 | 12 | entry_ptr = cache_ptr->LRU_tail_ptr; |
1864 | | |
1865 | 12 | if (cache_ptr->index_size >= cache_ptr->max_cache_size) |
1866 | 6 | empty_space = 0; |
1867 | 6 | else |
1868 | 6 | empty_space = cache_ptr->max_cache_size - cache_ptr->index_size; |
1869 | | |
1870 | 12 | while ((((cache_ptr->index_size + space_needed) > cache_ptr->max_cache_size) || |
1871 | 0 | ((empty_space + cache_ptr->clean_index_size) < (cache_ptr->min_clean_size))) && |
1872 | 12 | (entries_examined <= (2 * initial_list_len)) && (entry_ptr != NULL)) { |
1873 | 0 | assert(!(entry_ptr->is_protected)); |
1874 | 0 | assert(!(entry_ptr->is_read_only)); |
1875 | 0 | assert((entry_ptr->ro_ref_count) == 0); |
1876 | |
|
1877 | 0 | next_ptr = entry_ptr->next; |
1878 | 0 | prev_ptr = entry_ptr->prev; |
1879 | |
|
1880 | 0 | if (prev_ptr != NULL) |
1881 | 0 | prev_is_dirty = prev_ptr->is_dirty; |
1882 | |
|
1883 | 0 | if (entry_ptr->is_dirty && (entry_ptr->tag_info && entry_ptr->tag_info->corked)) { |
1884 | | /* Skip "dirty" corked entries. */ |
1885 | | #ifndef NDEBUG |
1886 | | ++num_corked_entries; |
1887 | | #endif |
1888 | 0 | didnt_flush_entry = true; |
1889 | 0 | } |
1890 | 0 | else if ((entry_ptr->type->id != H5AC_EPOCH_MARKER_ID) && !entry_ptr->flush_in_progress && |
1891 | 0 | !entry_ptr->prefetched_dirty) { |
1892 | 0 | didnt_flush_entry = false; |
1893 | 0 | if (entry_ptr->is_dirty) { |
1894 | | #if H5C_COLLECT_CACHE_STATS |
1895 | | if ((cache_ptr->index_size + space_needed) > cache_ptr->max_cache_size) |
1896 | | cache_ptr->entries_scanned_to_make_space++; |
1897 | | #endif /* H5C_COLLECT_CACHE_STATS */ |
1898 | | |
1899 | | /* reset entries_removed_counter and |
1900 | | * last_entry_removed_ptr prior to the call to |
1901 | | * H5C__flush_single_entry() so that we can spot |
1902 | | * unexpected removals of entries from the cache, |
1903 | | * and set the restart_scan flag if proceeding |
1904 | | * would be likely to cause us to scan an entry |
1905 | | * that is no longer in the cache. |
1906 | | */ |
1907 | 0 | cache_ptr->entries_removed_counter = 0; |
1908 | 0 | cache_ptr->last_entry_removed_ptr = NULL; |
1909 | |
|
1910 | 0 | if (H5C__flush_single_entry(f, entry_ptr, H5C__NO_FLAGS_SET) < 0) |
1911 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "unable to flush entry"); |
1912 | | |
1913 | 0 | if ((cache_ptr->entries_removed_counter > 1) || |
1914 | 0 | (cache_ptr->last_entry_removed_ptr == prev_ptr)) |
1915 | | |
1916 | 0 | restart_scan = true; |
1917 | 0 | } |
1918 | 0 | else if ((cache_ptr->index_size + space_needed) > cache_ptr->max_cache_size |
1919 | | #ifdef H5_HAVE_PARALLEL |
1920 | | && !(entry_ptr->coll_access) |
1921 | | #endif /* H5_HAVE_PARALLEL */ |
1922 | 0 | ) { |
1923 | | #if H5C_COLLECT_CACHE_STATS |
1924 | | cache_ptr->entries_scanned_to_make_space++; |
1925 | | #endif /* H5C_COLLECT_CACHE_STATS */ |
1926 | |
|
1927 | 0 | if (H5C__flush_single_entry(f, entry_ptr, |
1928 | 0 | H5C__FLUSH_INVALIDATE_FLAG | |
1929 | 0 | H5C__DEL_FROM_SLIST_ON_DESTROY_FLAG) < 0) |
1930 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "unable to flush entry"); |
1931 | 0 | } |
1932 | 0 | else { |
1933 | | /* We have enough space so don't flush clean entry. */ |
1934 | | #if H5C_COLLECT_CACHE_STATS |
1935 | | clean_entries_skipped++; |
1936 | | #endif /* H5C_COLLECT_CACHE_STATS */ |
1937 | 0 | didnt_flush_entry = true; |
1938 | 0 | } |
1939 | |
|
1940 | | #if H5C_COLLECT_CACHE_STATS |
1941 | | total_entries_scanned++; |
1942 | | #endif /* H5C_COLLECT_CACHE_STATS */ |
1943 | 0 | } |
1944 | 0 | else { |
1945 | | |
1946 | | /* Skip epoch markers, entries that are in the process |
1947 | | * of being flushed, and entries marked as prefetched_dirty |
1948 | | * (occurs in the R/O case only). |
1949 | | */ |
1950 | 0 | didnt_flush_entry = true; |
1951 | |
|
1952 | | #if H5C_COLLECT_CACHE_STATS |
1953 | | if (entry_ptr->prefetched_dirty) |
1954 | | dirty_pf_entries_skipped++; |
1955 | | #endif /* H5C_COLLECT_CACHE_STATS */ |
1956 | 0 | } |
1957 | | |
1958 | 0 | if (prev_ptr != NULL) { |
1959 | 0 | if (didnt_flush_entry) |
1960 | | /* epoch markers don't get flushed, and we don't touch |
1961 | | * entries that are in the process of being flushed. |
1962 | | * Hence no need for sanity checks, as we haven't |
1963 | | * flushed anything. Thus just set entry_ptr to prev_ptr |
1964 | | * and go on. |
1965 | | */ |
1966 | 0 | entry_ptr = prev_ptr; |
1967 | 0 | else if (restart_scan || prev_ptr->is_dirty != prev_is_dirty || prev_ptr->next != next_ptr || |
1968 | 0 | prev_ptr->is_protected || prev_ptr->is_pinned) { |
1969 | | /* something has happened to the LRU -- start over |
1970 | | * from the tail. |
1971 | | */ |
1972 | 0 | restart_scan = false; |
1973 | 0 | entry_ptr = cache_ptr->LRU_tail_ptr; |
1974 | 0 | H5C__UPDATE_STATS_FOR_LRU_SCAN_RESTART(cache_ptr); |
1975 | 0 | } |
1976 | 0 | else |
1977 | 0 | entry_ptr = prev_ptr; |
1978 | 0 | } |
1979 | 0 | else |
1980 | 0 | entry_ptr = NULL; |
1981 | |
|
1982 | 0 | entries_examined++; |
1983 | |
|
1984 | 0 | if (cache_ptr->index_size >= cache_ptr->max_cache_size) |
1985 | 0 | empty_space = 0; |
1986 | 0 | else |
1987 | 0 | empty_space = cache_ptr->max_cache_size - cache_ptr->index_size; |
1988 | |
|
1989 | 0 | assert(cache_ptr->index_size == (cache_ptr->clean_index_size + cache_ptr->dirty_index_size)); |
1990 | 0 | } |
1991 | | |
1992 | | #if H5C_COLLECT_CACHE_STATS |
1993 | | cache_ptr->calls_to_msic++; |
1994 | | |
1995 | | cache_ptr->total_entries_skipped_in_msic += clean_entries_skipped; |
1996 | | cache_ptr->total_dirty_pf_entries_skipped_in_msic += dirty_pf_entries_skipped; |
1997 | | cache_ptr->total_entries_scanned_in_msic += total_entries_scanned; |
1998 | | |
1999 | | if (clean_entries_skipped > cache_ptr->max_entries_skipped_in_msic) |
2000 | | cache_ptr->max_entries_skipped_in_msic = clean_entries_skipped; |
2001 | | |
2002 | | if (dirty_pf_entries_skipped > cache_ptr->max_dirty_pf_entries_skipped_in_msic) |
2003 | | cache_ptr->max_dirty_pf_entries_skipped_in_msic = dirty_pf_entries_skipped; |
2004 | | |
2005 | | if (total_entries_scanned > cache_ptr->max_entries_scanned_in_msic) |
2006 | | cache_ptr->max_entries_scanned_in_msic = total_entries_scanned; |
2007 | | #endif /* H5C_COLLECT_CACHE_STATS */ |
2008 | | |
2009 | | /* NEED: work on a better assert for corked entries */ |
2010 | 12 | assert((entries_examined > (2 * initial_list_len)) || |
2011 | 12 | ((cache_ptr->pl_size + cache_ptr->pel_size + cache_ptr->min_clean_size) > |
2012 | 12 | cache_ptr->max_cache_size) || |
2013 | 12 | ((cache_ptr->clean_index_size + empty_space) >= cache_ptr->min_clean_size) || |
2014 | 12 | ((num_corked_entries))); |
2015 | | #if H5C_MAINTAIN_CLEAN_AND_DIRTY_LRU_LISTS |
2016 | | |
2017 | | assert((entries_examined > (2 * initial_list_len)) || |
2018 | | (cache_ptr->cLRU_list_size <= cache_ptr->clean_index_size)); |
2019 | | assert((entries_examined > (2 * initial_list_len)) || |
2020 | | (cache_ptr->dLRU_list_size <= cache_ptr->dirty_index_size)); |
2021 | | |
2022 | | #endif /* H5C_MAINTAIN_CLEAN_AND_DIRTY_LRU_LISTS */ |
2023 | 12 | } |
2024 | 0 | else { |
2025 | 0 | assert(H5C_MAINTAIN_CLEAN_AND_DIRTY_LRU_LISTS); |
2026 | |
|
2027 | | #if H5C_MAINTAIN_CLEAN_AND_DIRTY_LRU_LISTS |
2028 | | initial_list_len = cache_ptr->cLRU_list_len; |
2029 | | entry_ptr = cache_ptr->cLRU_tail_ptr; |
2030 | | |
2031 | | while (((cache_ptr->index_size + space_needed) > cache_ptr->max_cache_size) && |
2032 | | (entries_examined <= initial_list_len) && (entry_ptr != NULL)) { |
2033 | | assert(!(entry_ptr->is_protected)); |
2034 | | assert(!(entry_ptr->is_read_only)); |
2035 | | assert((entry_ptr->ro_ref_count) == 0); |
2036 | | assert(!(entry_ptr->is_dirty)); |
2037 | | |
2038 | | prev_ptr = entry_ptr->aux_prev; |
2039 | | |
2040 | | if (!entry_ptr->prefetched_dirty |
2041 | | #ifdef H5_HAVE_PARALLEL |
2042 | | && !entry_ptr->coll_access |
2043 | | #endif /* H5_HAVE_PARALLEL */ |
2044 | | ) { |
2045 | | if (H5C__flush_single_entry( |
2046 | | f, entry_ptr, H5C__FLUSH_INVALIDATE_FLAG | H5C__DEL_FROM_SLIST_ON_DESTROY_FLAG) < 0) |
2047 | | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "unable to flush entry"); |
2048 | | } /* end if */ |
2049 | | |
2050 | | /* we are scanning the clean LRU, so the serialize function |
2051 | | * will not be called on any entry -- thus there is no |
2052 | | * concern about the list being modified out from under |
2053 | | * this function. |
2054 | | */ |
2055 | | |
2056 | | entry_ptr = prev_ptr; |
2057 | | entries_examined++; |
2058 | | } |
2059 | | #endif /* H5C_MAINTAIN_CLEAN_AND_DIRTY_LRU_LISTS */ |
2060 | 0 | } |
2061 | | |
2062 | 12 | done: |
2063 | | /* Sanity checks */ |
2064 | 12 | assert(cache_ptr->msic_in_progress); |
2065 | 12 | if (!reentrant_call) |
2066 | 12 | cache_ptr->msic_in_progress = false; |
2067 | 12 | assert((!reentrant_call) || (cache_ptr->msic_in_progress)); |
2068 | | |
2069 | 12 | FUNC_LEAVE_NOAPI(ret_value) |
2070 | 12 | } /* H5C__make_space_in_cache() */ |
2071 | | |
2072 | | /*------------------------------------------------------------------------- |
2073 | | * Function: H5C__serialize_cache |
2074 | | * |
2075 | | * Purpose: Serialize (i.e. construct an on disk image) for all entries |
2076 | | * in the metadata cache including clean entries. |
2077 | | * |
2078 | | * Note that flush dependencies and "flush me last" flags |
2079 | | * must be observed in the serialization process. |
2080 | | * |
2081 | | * Note also that entries may be loaded, flushed, evicted, |
2082 | | * expunged, relocated, resized, or removed from the cache |
2083 | | * during this process, just as these actions may occur during |
2084 | | * a regular flush. |
2085 | | * |
2086 | | * However, we are given that the cache will contain no protected |
2087 | | * entries on entry to this routine (although entries may be |
2088 | | * briefly protected and then unprotected during the serialize |
2089 | | * process). |
2090 | | * |
2091 | | * The objective of this routine is serialize all entries and |
2092 | | * to force all entries into their actual locations on disk. |
2093 | | * |
2094 | | * The initial need for this routine is to settle all entries |
2095 | | * in the cache prior to construction of the metadata cache |
2096 | | * image so that the size of the cache image can be calculated. |
2097 | | * |
2098 | | * Return: Non-negative on success/Negative on failure or if there was |
2099 | | * a request to flush all items and something was protected. |
2100 | | * |
2101 | | *------------------------------------------------------------------------- |
2102 | | */ |
2103 | | herr_t |
2104 | | H5C__serialize_cache(H5F_t *f) |
2105 | 0 | { |
2106 | | #ifdef H5C_DO_SANITY_CHECKS |
2107 | | int i; |
2108 | | uint32_t index_len = 0; |
2109 | | size_t index_size = (size_t)0; |
2110 | | size_t clean_index_size = (size_t)0; |
2111 | | size_t dirty_index_size = (size_t)0; |
2112 | | size_t slist_size = (size_t)0; |
2113 | | uint32_t slist_len = 0; |
2114 | | #endif /* H5C_DO_SANITY_CHECKS */ |
2115 | 0 | H5C_ring_t ring; |
2116 | 0 | H5C_t *cache_ptr; |
2117 | 0 | herr_t ret_value = SUCCEED; |
2118 | |
|
2119 | 0 | FUNC_ENTER_PACKAGE |
2120 | | |
2121 | | /* Sanity checks */ |
2122 | 0 | assert(f); |
2123 | 0 | assert(f->shared); |
2124 | 0 | cache_ptr = f->shared->cache; |
2125 | 0 | assert(cache_ptr); |
2126 | 0 | assert(cache_ptr->slist_ptr); |
2127 | |
|
2128 | | #ifdef H5C_DO_SANITY_CHECKS |
2129 | | assert(cache_ptr->index_ring_len[H5C_RING_UNDEFINED] == 0); |
2130 | | assert(cache_ptr->index_ring_size[H5C_RING_UNDEFINED] == (size_t)0); |
2131 | | assert(cache_ptr->clean_index_ring_size[H5C_RING_UNDEFINED] == (size_t)0); |
2132 | | assert(cache_ptr->dirty_index_ring_size[H5C_RING_UNDEFINED] == (size_t)0); |
2133 | | assert(cache_ptr->slist_ring_len[H5C_RING_UNDEFINED] == 0); |
2134 | | assert(cache_ptr->slist_ring_size[H5C_RING_UNDEFINED] == (size_t)0); |
2135 | | |
2136 | | for (i = H5C_RING_USER; i < H5C_RING_NTYPES; i++) { |
2137 | | index_len += cache_ptr->index_ring_len[i]; |
2138 | | index_size += cache_ptr->index_ring_size[i]; |
2139 | | clean_index_size += cache_ptr->clean_index_ring_size[i]; |
2140 | | dirty_index_size += cache_ptr->dirty_index_ring_size[i]; |
2141 | | |
2142 | | slist_len += cache_ptr->slist_ring_len[i]; |
2143 | | slist_size += cache_ptr->slist_ring_size[i]; |
2144 | | } /* end for */ |
2145 | | |
2146 | | assert(cache_ptr->index_len == index_len); |
2147 | | assert(cache_ptr->index_size == index_size); |
2148 | | assert(cache_ptr->clean_index_size == clean_index_size); |
2149 | | assert(cache_ptr->dirty_index_size == dirty_index_size); |
2150 | | assert(cache_ptr->slist_len == slist_len); |
2151 | | assert(cache_ptr->slist_size == slist_size); |
2152 | | #endif /* H5C_DO_SANITY_CHECKS */ |
2153 | |
|
2154 | | #ifdef H5C_DO_EXTREME_SANITY_CHECKS |
2155 | | if (H5C__validate_protected_entry_list(cache_ptr) < 0 || H5C__validate_pinned_entry_list(cache_ptr) < 0 || |
2156 | | H5C__validate_lru_list(cache_ptr) < 0) |
2157 | | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "an extreme sanity check failed on entry"); |
2158 | | #endif /* H5C_DO_EXTREME_SANITY_CHECKS */ |
2159 | |
|
2160 | | #ifndef NDEBUG |
2161 | | /* if this is a debug build, set the serialization_count field of |
2162 | | * each entry in the cache to zero before we start the serialization. |
2163 | | * This allows us to detect the case in which any entry is serialized |
2164 | | * more than once (a performance issues), and more importantly, the |
2165 | | * case is which any flush dependency parent is serializes more than |
2166 | | * once (a correctness issue). |
2167 | | */ |
2168 | | { |
2169 | | H5C_cache_entry_t *scan_ptr = NULL; |
2170 | | |
2171 | | scan_ptr = cache_ptr->il_head; |
2172 | | while (scan_ptr != NULL) { |
2173 | | scan_ptr->serialization_count = 0; |
2174 | | scan_ptr = scan_ptr->il_next; |
2175 | | } /* end while */ |
2176 | | } /* end block */ |
2177 | | #endif |
2178 | | |
2179 | | /* set cache_ptr->serialization_in_progress to true, and back |
2180 | | * to false at the end of the function. Must maintain this flag |
2181 | | * to support H5C_get_serialization_in_progress(), which is in |
2182 | | * turn required to support sanity checking in some cache |
2183 | | * clients. |
2184 | | */ |
2185 | 0 | assert(!cache_ptr->serialization_in_progress); |
2186 | 0 | cache_ptr->serialization_in_progress = true; |
2187 | | |
2188 | | /* Serialize each ring, starting from the outermost ring and |
2189 | | * working inward. |
2190 | | */ |
2191 | 0 | ring = H5C_RING_USER; |
2192 | 0 | while (ring < H5C_RING_NTYPES) { |
2193 | 0 | assert(cache_ptr->close_warning_received); |
2194 | 0 | switch (ring) { |
2195 | 0 | case H5C_RING_USER: |
2196 | 0 | break; |
2197 | | |
2198 | 0 | case H5C_RING_RDFSM: |
2199 | | /* Settle raw data FSM */ |
2200 | 0 | if (!cache_ptr->rdfsm_settled) |
2201 | 0 | if (H5MF_settle_raw_data_fsm(f, &cache_ptr->rdfsm_settled) < 0) |
2202 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "RD FSM settle failed"); |
2203 | 0 | break; |
2204 | | |
2205 | 0 | case H5C_RING_MDFSM: |
2206 | | /* Settle metadata FSM */ |
2207 | 0 | if (!cache_ptr->mdfsm_settled) |
2208 | 0 | if (H5MF_settle_meta_data_fsm(f, &cache_ptr->mdfsm_settled) < 0) |
2209 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "MD FSM settle failed"); |
2210 | 0 | break; |
2211 | | |
2212 | 0 | case H5C_RING_SBE: |
2213 | 0 | case H5C_RING_SB: |
2214 | 0 | break; |
2215 | | |
2216 | 0 | default: |
2217 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, "Unknown ring?!?!"); |
2218 | 0 | break; |
2219 | 0 | } /* end switch */ |
2220 | | |
2221 | 0 | if (H5C__serialize_ring(f, ring) < 0) |
2222 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTSERIALIZE, FAIL, "serialize ring failed"); |
2223 | | |
2224 | 0 | ring++; |
2225 | 0 | } /* end while */ |
2226 | | |
2227 | | #ifndef NDEBUG |
2228 | | /* Verify that no entry has been serialized more than once. |
2229 | | * FD parents with multiple serializations should have been caught |
2230 | | * elsewhere, so no specific check for them here. |
2231 | | */ |
2232 | | { |
2233 | | H5C_cache_entry_t *scan_ptr = NULL; |
2234 | | |
2235 | | scan_ptr = cache_ptr->il_head; |
2236 | | while (scan_ptr != NULL) { |
2237 | | assert(scan_ptr->serialization_count <= 1); |
2238 | | |
2239 | | scan_ptr = scan_ptr->il_next; |
2240 | | } /* end while */ |
2241 | | } /* end block */ |
2242 | | #endif |
2243 | | |
2244 | 0 | done: |
2245 | 0 | cache_ptr->serialization_in_progress = false; |
2246 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
2247 | 0 | } /* H5C__serialize_cache() */ |
2248 | | |
2249 | | /*------------------------------------------------------------------------- |
2250 | | * Function: H5C__serialize_ring |
2251 | | * |
2252 | | * Purpose: Serialize the entries contained in the specified cache and |
2253 | | * ring. All entries in rings outside the specified ring |
2254 | | * must have been serialized on entry. |
2255 | | * |
2256 | | * If the cache contains protected entries in the specified |
2257 | | * ring, the function will fail, as protected entries cannot |
2258 | | * be serialized. However all unprotected entries in the |
2259 | | * target ring should be serialized before the function |
2260 | | * returns failure. |
2261 | | * |
2262 | | * If flush dependencies appear in the target ring, the |
2263 | | * function makes repeated passes through the index list |
2264 | | * serializing entries in flush dependency order. |
2265 | | * |
2266 | | * All entries outside the H5C_RING_SBE are marked for |
2267 | | * inclusion in the cache image. Entries in H5C_RING_SBE |
2268 | | * and below are marked for exclusion from the image. |
2269 | | * |
2270 | | * Return: Non-negative on success/Negative on failure or if there was |
2271 | | * a request to flush all items and something was protected. |
2272 | | * |
2273 | | *------------------------------------------------------------------------- |
2274 | | */ |
2275 | | static herr_t |
2276 | | H5C__serialize_ring(H5F_t *f, H5C_ring_t ring) |
2277 | 0 | { |
2278 | 0 | bool done = false; |
2279 | 0 | H5C_t *cache_ptr; |
2280 | 0 | H5C_cache_entry_t *entry_ptr; |
2281 | 0 | herr_t ret_value = SUCCEED; |
2282 | |
|
2283 | 0 | FUNC_ENTER_PACKAGE |
2284 | | |
2285 | | /* Sanity checks */ |
2286 | 0 | assert(f); |
2287 | 0 | assert(f->shared); |
2288 | 0 | cache_ptr = f->shared->cache; |
2289 | 0 | assert(cache_ptr); |
2290 | 0 | assert(ring > H5C_RING_UNDEFINED); |
2291 | 0 | assert(ring < H5C_RING_NTYPES); |
2292 | |
|
2293 | 0 | assert(cache_ptr->serialization_in_progress); |
2294 | | |
2295 | | /* The objective here is to serialize all entries in the cache ring |
2296 | | * in flush dependency order. |
2297 | | * |
2298 | | * The basic algorithm is to scan the cache index list looking for |
2299 | | * unserialized entries that are either not in a flush dependency |
2300 | | * relationship, or which have no unserialized children. Any such |
2301 | | * entry is serialized and its flush dependency parents (if any) are |
2302 | | * informed -- allowing them to decrement their userialized child counts. |
2303 | | * |
2304 | | * However, this algorithm is complicated by the ability |
2305 | | * of client serialization callbacks to perform operations on |
2306 | | * on the cache which can result in the insertion, deletion, |
2307 | | * relocation, resize, dirty, flush, eviction, or removal (via the |
2308 | | * take ownership flag) of entries. Changes in the flush dependency |
2309 | | * structure are also possible. |
2310 | | * |
2311 | | * On the other hand, the algorithm is simplified by the fact that |
2312 | | * we are serializing, not flushing. Thus, as long as all entries |
2313 | | * are serialized correctly, it doesn't matter if we have to go back |
2314 | | * and serialize an entry a second time. |
2315 | | * |
2316 | | * These possible actions result in the following modifications to |
2317 | | * the basic algorithm: |
2318 | | * |
2319 | | * 1) In the event of an entry expunge, eviction or removal, we must |
2320 | | * restart the scan as it is possible that the next entry in our |
2321 | | * scan is no longer in the cache. Were we to examine this entry, |
2322 | | * we would be accessing deallocated memory. |
2323 | | * |
2324 | | * 2) A resize, dirty, or insertion of an entry may result in the |
2325 | | * the increment of a flush dependency parent's dirty and/or |
2326 | | * unserialized child count. In the context of serializing the |
2327 | | * the cache, this is a non-issue, as even if we have already |
2328 | | * serialized the parent, it will be marked dirty and its image |
2329 | | * marked out of date if appropriate when the child is serialized. |
2330 | | * |
2331 | | * However, this is a major issue for a flush, as were this to happen |
2332 | | * in a flush, it would violate the invariant that the flush dependency |
2333 | | * feature is intended to enforce. As the metadata cache has no |
2334 | | * control over the behavior of cache clients, it has no way of |
2335 | | * preventing this behaviour. However, it should detect it if at all |
2336 | | * possible. |
2337 | | * |
2338 | | * Do this by maintaining a count of the number of times each entry is |
2339 | | * serialized during a cache serialization. If any flush dependency |
2340 | | * parent is serialized more than once, throw an assertion failure. |
2341 | | * |
2342 | | * 3) An entry relocation will typically change the location of the |
2343 | | * entry in the index list. This shouldn't cause problems as we |
2344 | | * will scan the index list until we make a complete pass without |
2345 | | * finding anything to serialize -- making relocations of either |
2346 | | * the current or next entries irrelevant. |
2347 | | * |
2348 | | * Note that since a relocation may result in our skipping part of |
2349 | | * the index list, we must always do at least one more pass through |
2350 | | * the index list after an entry relocation. |
2351 | | * |
2352 | | * 4) Changes in the flush dependency structure are possible on |
2353 | | * entry insertion, load, expunge, evict, or remove. Destruction |
2354 | | * of a flush dependency has no effect, as it can only relax the |
2355 | | * flush dependencies. Creation of a flush dependency can create |
2356 | | * an unserialized child of a flush dependency parent where all |
2357 | | * flush dependency children were previously serialized. Should |
2358 | | * this child dirty the flush dependency parent when it is serialized, |
2359 | | * the parent will be re-serialized. |
2360 | | * |
2361 | | * Per the discussion of 2) above, this is a non issue for cache |
2362 | | * serialization, and a major problem for cache flush. Using the |
2363 | | * same detection mechanism, throw an assertion failure if this |
2364 | | * condition appears. |
2365 | | * |
2366 | | * Observe that either eviction or removal of entries as a result of |
2367 | | * a serialization is not a problem as long as the flush dependency |
2368 | | * tree does not change beyond the removal of a leaf. |
2369 | | */ |
2370 | 0 | while (!done) { |
2371 | | /* Reset the counters so that we can detect insertions, loads, |
2372 | | * moves, and flush dependency height changes caused by the pre_serialize |
2373 | | * and serialize callbacks. |
2374 | | */ |
2375 | 0 | cache_ptr->entries_loaded_counter = 0; |
2376 | 0 | cache_ptr->entries_inserted_counter = 0; |
2377 | 0 | cache_ptr->entries_relocated_counter = 0; |
2378 | |
|
2379 | 0 | done = true; /* set to false if any activity in inner loop */ |
2380 | 0 | entry_ptr = cache_ptr->il_head; |
2381 | 0 | while (entry_ptr != NULL) { |
2382 | | /* Verify that either the entry is already serialized, or |
2383 | | * that it is assigned to either the target or an inner |
2384 | | * ring. |
2385 | | */ |
2386 | 0 | assert((entry_ptr->ring >= ring) || (entry_ptr->image_up_to_date)); |
2387 | | |
2388 | | /* Skip flush me last entries or inner ring entries */ |
2389 | 0 | if (!entry_ptr->flush_me_last && entry_ptr->ring == ring) { |
2390 | | |
2391 | | /* if we encounter an unserialized entry in the current |
2392 | | * ring that is not marked flush me last, we are not done. |
2393 | | */ |
2394 | 0 | if (!entry_ptr->image_up_to_date) |
2395 | 0 | done = false; |
2396 | | |
2397 | | /* Serialize the entry if its image is not up to date |
2398 | | * and it has no unserialized flush dependency children. |
2399 | | */ |
2400 | 0 | if (!entry_ptr->image_up_to_date && entry_ptr->flush_dep_nunser_children == 0) { |
2401 | 0 | assert(entry_ptr->serialization_count == 0); |
2402 | | |
2403 | | /* Serialize the entry */ |
2404 | 0 | if (H5C__serialize_single_entry(f, cache_ptr, entry_ptr) < 0) |
2405 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTSERIALIZE, FAIL, "entry serialization failed"); |
2406 | | |
2407 | 0 | assert(entry_ptr->flush_dep_nunser_children == 0); |
2408 | 0 | assert(entry_ptr->serialization_count == 0); |
2409 | |
|
2410 | | #ifndef NDEBUG |
2411 | | /* Increment serialization counter (to detect multiple serializations) */ |
2412 | | entry_ptr->serialization_count++; |
2413 | | #endif |
2414 | 0 | } /* end if */ |
2415 | 0 | } /* end if */ |
2416 | | |
2417 | | /* Check for the cache being perturbed during the entry serialize */ |
2418 | 0 | if ((cache_ptr->entries_loaded_counter > 0) || (cache_ptr->entries_inserted_counter > 0) || |
2419 | 0 | (cache_ptr->entries_relocated_counter > 0)) { |
2420 | |
|
2421 | | #if H5C_COLLECT_CACHE_STATS |
2422 | | H5C__UPDATE_STATS_FOR_INDEX_SCAN_RESTART(cache_ptr); |
2423 | | #endif /* H5C_COLLECT_CACHE_STATS */ |
2424 | | |
2425 | | /* Reset the counters */ |
2426 | 0 | cache_ptr->entries_loaded_counter = 0; |
2427 | 0 | cache_ptr->entries_inserted_counter = 0; |
2428 | 0 | cache_ptr->entries_relocated_counter = 0; |
2429 | | |
2430 | | /* Restart scan */ |
2431 | 0 | entry_ptr = cache_ptr->il_head; |
2432 | 0 | } /* end if */ |
2433 | 0 | else |
2434 | | /* Advance to next entry */ |
2435 | 0 | entry_ptr = entry_ptr->il_next; |
2436 | 0 | } /* while ( entry_ptr != NULL ) */ |
2437 | 0 | } /* while ( ! done ) */ |
2438 | | |
2439 | | /* Reset the counters so that we can detect insertions, loads, |
2440 | | * moves, and flush dependency height changes caused by the pre_serialize |
2441 | | * and serialize callbacks. |
2442 | | */ |
2443 | 0 | cache_ptr->entries_loaded_counter = 0; |
2444 | 0 | cache_ptr->entries_inserted_counter = 0; |
2445 | 0 | cache_ptr->entries_relocated_counter = 0; |
2446 | | |
2447 | | /* At this point, all entries not marked "flush me last" and in |
2448 | | * the current ring or outside it should be serialized and have up |
2449 | | * to date images. Scan the index list again to serialize the |
2450 | | * "flush me last" entries (if they are in the current ring) and to |
2451 | | * verify that all other entries have up to date images. |
2452 | | */ |
2453 | 0 | entry_ptr = cache_ptr->il_head; |
2454 | 0 | while (entry_ptr != NULL) { |
2455 | 0 | assert(entry_ptr->ring > H5C_RING_UNDEFINED); |
2456 | 0 | assert(entry_ptr->ring < H5C_RING_NTYPES); |
2457 | 0 | assert((entry_ptr->ring >= ring) || (entry_ptr->image_up_to_date)); |
2458 | |
|
2459 | 0 | if (entry_ptr->ring == ring) { |
2460 | 0 | if (entry_ptr->flush_me_last) { |
2461 | 0 | if (!entry_ptr->image_up_to_date) { |
2462 | 0 | assert(entry_ptr->serialization_count == 0); |
2463 | 0 | assert(entry_ptr->flush_dep_nunser_children == 0); |
2464 | | |
2465 | | /* Serialize the entry */ |
2466 | 0 | if (H5C__serialize_single_entry(f, cache_ptr, entry_ptr) < 0) |
2467 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_CANTSERIALIZE, FAIL, "entry serialization failed"); |
2468 | | |
2469 | | /* Check for the cache changing */ |
2470 | 0 | if ((cache_ptr->entries_loaded_counter > 0) || |
2471 | 0 | (cache_ptr->entries_inserted_counter > 0) || |
2472 | 0 | (cache_ptr->entries_relocated_counter > 0)) |
2473 | 0 | HGOTO_ERROR(H5E_CACHE, H5E_SYSTEM, FAIL, |
2474 | 0 | "flush_me_last entry serialization triggered restart"); |
2475 | | |
2476 | 0 | assert(entry_ptr->flush_dep_nunser_children == 0); |
2477 | 0 | assert(entry_ptr->serialization_count == 0); |
2478 | | #ifndef NDEBUG |
2479 | | /* Increment serialization counter (to detect multiple serializations) */ |
2480 | | entry_ptr->serialization_count++; |
2481 | | #endif |
2482 | 0 | } /* end if */ |
2483 | 0 | } /* end if */ |
2484 | 0 | else { |
2485 | 0 | assert(entry_ptr->image_up_to_date); |
2486 | 0 | assert(entry_ptr->serialization_count <= 1); |
2487 | 0 | assert(entry_ptr->flush_dep_nunser_children == 0); |
2488 | 0 | } /* end else */ |
2489 | 0 | } /* if ( entry_ptr->ring == ring ) */ |
2490 | | |
2491 | 0 | entry_ptr = entry_ptr->il_next; |
2492 | 0 | } /* while ( entry_ptr != NULL ) */ |
2493 | | |
2494 | 0 | done: |
2495 | 0 | assert(cache_ptr->serialization_in_progress); |
2496 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
2497 | 0 | } /* H5C__serialize_ring() */ |