1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2  * Copyright by The HDF Group.                                               *
3  * Copyright by the Board of Trustees of the University of Illinois.         *
4  * All rights reserved.                                                      *
5  *                                                                           *
6  * This file is part of HDF5.  The full HDF5 copyright notice, including     *
7  * terms governing use, modification, and redistribution, is contained in    *
8  * the COPYING file, which can be found at the root of the source code       *
9  * distribution tree, or in https://support.hdfgroup.org/ftp/HDF5/releases.  *
10  * If you do not have access to either file, you may request a copy from     *
11  * help@hdfgroup.org.                                                        *
12  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
13 
14 /*-------------------------------------------------------------------------
15  *
16  * Created:             H5Defc.c
17  *                      December 13, 2010
18  *                      Neil Fortner <nfortne2@hdfgroup.org>
19  *
20  * Purpose:             External file caching routines - implements a
21  *                      cache of external files to minimize the number of
22  *                      file opens and closes.
23  *
24  *-------------------------------------------------------------------------
25  */
26 
27 #include "H5Fmodule.h"          /* This source code file is part of the H5F module */
28 
29 
30 /* Packages needed by this file... */
31 #include "H5private.h"          /* Generic Functions                    */
32 #include "H5Eprivate.h"         /* Error handling                       */
33 #include "H5Fpkg.h"             /* File access                          */
34 #include "H5MMprivate.h"        /* Memory management                    */
35 #include "H5Pprivate.h"         /* Property lists                       */
36 
37 /* Special values for the "tag" field below */
38 #define H5F_EFC_TAG_DEFAULT     -1
39 #define H5F_EFC_TAG_LOCK        -2
40 #define H5F_EFC_TAG_CLOSE       -3
41 #define H5F_EFC_TAG_DONTCLOSE   -4
42 
43 /* Structure for each entry in a file's external file cache */
44 typedef struct H5F_efc_ent_t {
45     char                *name;          /* Name of the file */
46     H5F_t               *file;          /* File object */
47     struct H5F_efc_ent_t *LRU_next;     /* Next item in LRU list */
48     struct H5F_efc_ent_t *LRU_prev;     /* Previous item in LRU list */
49     unsigned            nopen;          /* Number of times this file is currently opened by an EFC client */
50 } H5F_efc_ent_t;
51 
52 /* Structure for a shared file struct's external file cache */
53 struct H5F_efc_t {
54     H5SL_t              *slist;         /* Skip list of cached external files */
55     H5F_efc_ent_t       *LRU_head;      /* Head of LRU list.  This is the least recently used file */
56     H5F_efc_ent_t       *LRU_tail;      /* Tail of LRU list.  This is the most recently used file */
57     unsigned            nfiles;         /* Size of the external file cache */
58     unsigned            max_nfiles;     /* Maximum size of the external file cache */
59     unsigned            nrefs;          /* Number of times this file appears in another file's EFC */
60     int                 tag;            /* Temporary variable used by H5F__efc_try_close() */
61     H5F_file_t          *tmp_next;      /* Next file in temporary list used by H5F__efc_try_close() */
62 };
63 
64 /* Private prototypes */
65 static herr_t H5F__efc_release_real(H5F_efc_t *efc);
66 static herr_t H5F__efc_remove_ent(H5F_efc_t *efc, H5F_efc_ent_t *ent);
67 static void H5F__efc_try_close_tag1(H5F_file_t *sf, H5F_file_t **tail);
68 static void H5F__efc_try_close_tag2(H5F_file_t *sf, H5F_file_t **tail);
69 
70 /* Free lists */
71 H5FL_DEFINE_STATIC(H5F_efc_ent_t);
72 H5FL_DEFINE_STATIC(H5F_efc_t);
73 
74 
75 /*-------------------------------------------------------------------------
76  * Function:    H5F__efc_create
77  *
78  * Purpose:     Allocate and initialize a new external file cache object,
79  *              which can the be used to cache open external files.
80  *              the object must be freed with H5F__efc_destroy.
81  *
82  * Return:      Pointer to new external file cache object on success
83  *              NULL on failure
84  *
85  * Programmer:  Neil Fortner
86  *              Tuesday, December 14, 2010
87  *
88  *-------------------------------------------------------------------------
89  */
90 H5F_efc_t *
H5F__efc_create(unsigned max_nfiles)91 H5F__efc_create(unsigned max_nfiles)
92 {
93     H5F_efc_t   *efc = NULL;            /* EFC object */
94     H5F_efc_t   *ret_value = NULL;      /* Return value */
95 
96     FUNC_ENTER_PACKAGE
97 
98     /* Sanity checks */
99     HDassert(max_nfiles > 0);
100 
101     /* Allocate EFC struct */
102     if(NULL == (efc = H5FL_CALLOC(H5F_efc_t)))
103         HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "memory allocation failed")
104 
105     /* Initialize maximum number of files */
106     efc->max_nfiles = max_nfiles;
107 
108     /* Initialize temporary ref count */
109     efc->tag = H5F_EFC_TAG_DEFAULT;
110 
111     /* Set the return value */
112     ret_value = efc;
113 
114 done:
115     if(ret_value == NULL && efc)
116         efc = H5FL_FREE(H5F_efc_t, efc);
117 
118     FUNC_LEAVE_NOAPI(ret_value)
119 } /* end H5F__efc_create() */
120 
121 
122 /*-------------------------------------------------------------------------
123  * Function:    H5F__efc_open
124  *
125  * Purpose:     Opens a file using the external file cache.  The target
126  *              file is added to the external file cache of the parent
127  *              if it is not already present.  If the target file is in
128  *              the parent's EFC, simply returns the target file.  When
129  *              the file object is no longer in use, it should be closed
130  *              with H5F_efc_close (will not actually close the file
131  *              until it is evicted from the EFC).
132  *
133  * Return:      Pointer to open file on success
134  *              NULL on failure
135  *
136  * Programmer:  Neil Fortner
137  *              Tuesday, December 14, 2010
138  *
139  *-------------------------------------------------------------------------
140  */
141 H5F_t *
H5F__efc_open(H5F_t * parent,const char * name,unsigned flags,hid_t fcpl_id,hid_t fapl_id)142 H5F__efc_open(H5F_t *parent, const char *name, unsigned flags, hid_t fcpl_id, hid_t fapl_id)
143 {
144     H5F_efc_t   *efc = NULL;    /* External file cache for parent file */
145     H5F_efc_ent_t *ent = NULL;  /* Entry for target file in efc */
146     hbool_t     open_file = FALSE; /* Whether ent->file needs to be closed in case of error */
147     H5F_t       *ret_value = NULL; /* Return value */
148 
149     FUNC_ENTER_PACKAGE
150 
151     /* Sanity checks */
152     HDassert(parent);
153     HDassert(parent->shared);
154     HDassert(name);
155 
156     /* Get external file cache */
157     efc = parent->shared->efc;
158 
159     /* Check if the EFC exists.  If it does not, just call H5F_open().  We
160      * support this so clients do not have to make 2 different calls depending
161      * on the state of the efc. */
162     if(!efc) {
163         if(NULL == (ret_value = H5F_open(name, flags, fcpl_id, fapl_id)))
164             HGOTO_ERROR(H5E_FILE, H5E_CANTOPENFILE, NULL, "can't open file")
165 
166         /* Increment the number of open objects to prevent the file from being
167          * closed out from under us - "simulate" having an open file id.  Note
168          * that this behaviour replaces the calls to H5F_incr_nopen_objs() and
169          * H5F_decr_nopen_objs() in H5L_extern_traverse(). */
170         ret_value->nopen_objs++;
171 
172         HGOTO_DONE(ret_value)
173     } /* end if */
174 
175     /* Search the skip list for name if the skip list exists, create the skip
176      * list otherwise */
177     if(efc->slist) {
178         if(efc->nfiles > 0)
179             ent = (H5F_efc_ent_t *)H5SL_search(efc->slist, name);
180     } /* end if */
181     else {
182         HDassert(efc->nfiles == 0);
183         if(NULL == (efc->slist = H5SL_create(H5SL_TYPE_STR, NULL)))
184             HGOTO_ERROR(H5E_FILE, H5E_CANTCREATE, NULL, "can't create skip list")
185     } /* end else */
186 
187     /* If we found the file update the LRU list and return the cached file,
188      * otherwise open the file and cache it */
189     if(ent) {
190         HDassert(efc->LRU_head);
191         HDassert(efc->LRU_tail);
192 
193         /* Move ent to the head of the LRU list, if it is not already there */
194         if(ent->LRU_prev) {
195             HDassert(efc->LRU_head != ent);
196 
197             /* Remove from current position.  Note that once we touch the LRU
198              * list we cannot revert to the previous state.  Make sure there can
199              * be no errors between when we first touch the LRU list and when
200              * the cache is in a consistent state! */
201             if(ent->LRU_next)
202                 ent->LRU_next->LRU_prev = ent->LRU_prev;
203             else {
204                 HDassert(efc->LRU_tail == ent);
205                 efc->LRU_tail = ent->LRU_prev;
206             } /* end else */
207             ent->LRU_prev->LRU_next = ent->LRU_next;
208 
209             /* Add to head of LRU list */
210             ent->LRU_next = efc->LRU_head;
211             ent->LRU_next->LRU_prev = ent;
212             ent->LRU_prev = NULL;
213             efc->LRU_head = ent;
214         } /* end if */
215 
216         /* Mark the file as open */
217         ent->nopen++;
218     } /* end if */
219     else {
220         /* Check if we need to evict something */
221         if(efc->nfiles == efc->max_nfiles) {
222             /* Search for an unopened file from the tail */
223             for(ent = efc->LRU_tail; ent && ent->nopen; ent = ent->LRU_prev);
224 
225             /* Evict the file if found, otherwise just open the target file and
226              * do not add it to cache */
227             if(ent) {
228                 if(H5F__efc_remove_ent(efc, ent) < 0)
229                     HGOTO_ERROR(H5E_FILE, H5E_CANTREMOVE, NULL, "can't remove entry from external file cache")
230 
231                 /* Do not free ent, we will recycle it below */
232             } /* end if */
233             else {
234                 /* Cannot cache file, just open file and return */
235                 if(NULL == (ret_value = H5F_open(name, flags, fcpl_id, fapl_id)))
236                     HGOTO_ERROR(H5E_FILE, H5E_CANTOPENFILE, NULL, "can't open file")
237 
238                 /* Increment the number of open objects to prevent the file from
239                  * being closed out from under us - "simulate" having an open
240                  * file id */
241                 ret_value->nopen_objs++;
242 
243                 HGOTO_DONE(ret_value)
244             } /* end else */
245         } /* end if */
246         else
247             /* Allocate new entry */
248             if(NULL == (ent = H5FL_MALLOC(H5F_efc_ent_t)))
249                 HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "memory allocation failed")
250 
251         /* Build new entry */
252         if(NULL == (ent->name = H5MM_strdup(name)))
253             HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "memory allocation failed")
254 
255         /* Open the file */
256         if(NULL == (ent->file = H5F_open(name, flags, fcpl_id, fapl_id)))
257             HGOTO_ERROR(H5E_FILE, H5E_CANTOPENFILE, NULL, "can't open file")
258         open_file = TRUE;
259 
260         /* Increment the number of open objects to prevent the file from being
261          * closed out from under us - "simulate" having an open file id */
262         ent->file->nopen_objs++;
263 
264         /* Add the file to the cache */
265         /* Skip list */
266         if(H5SL_insert(efc->slist, ent, ent->name) < 0)
267             HGOTO_ERROR(H5E_FILE, H5E_CANTINSERT, NULL, "can't insert entry into skip list")
268 
269         /* Add to head of LRU list and update tail if necessary */
270         ent->LRU_next = efc->LRU_head;
271         if(ent->LRU_next)
272             ent->LRU_next->LRU_prev = ent;
273         ent->LRU_prev = NULL;
274         efc->LRU_head = ent;
275         if(!efc->LRU_tail) {
276             HDassert(!ent->LRU_next);
277             efc->LRU_tail = ent;
278         } /* end if */
279 
280         /* Mark the file as open */
281         ent->nopen = 1;
282 
283         /* Update nfiles and nrefs */
284         efc->nfiles++;
285         if(ent->file->shared->efc)
286             ent->file->shared->efc->nrefs++;
287     } /* end else */
288 
289     HDassert(ent);
290     HDassert(ent->file);
291     HDassert(ent->name);
292     HDassert(ent->nopen);
293 
294     /* Set the return value */
295     ret_value = ent->file;
296 
297 done:
298     if(!ret_value)
299         if(ent) {
300             if(open_file) {
301                 ent->file->nopen_objs--;
302                 if(H5F_try_close(ent->file, NULL) < 0)
303                     HDONE_ERROR(H5E_FILE, H5E_CANTCLOSEFILE, NULL, "can't close external file")
304             } /* end if */
305             ent->name = (char *)H5MM_xfree(ent->name);
306             ent = H5FL_FREE(H5F_efc_ent_t, ent);
307         } /* end if */
308 
309     FUNC_LEAVE_NOAPI(ret_value)
310 } /* end H5F__efc_open() */
311 
312 
313 /*-------------------------------------------------------------------------
314  * Function:    H5F_efc_close
315  *
316  * Purpose:     Closes (unlocks) a file opened using the external file
317  *              cache.  The target file is not immediately closed unless
318  *              there is no external file cache for the parent file.
319  *
320  * Return:      Non-negative on success
321  *              Negative on failure
322  *
323  * Programmer:  Neil Fortner
324  *              Wednesday, December 15, 2010
325  *
326  *-------------------------------------------------------------------------
327  */
328 herr_t
H5F_efc_close(H5F_t * parent,H5F_t * file)329 H5F_efc_close(H5F_t *parent, H5F_t *file)
330 {
331     H5F_efc_t   *efc = NULL;    /* External file cache for parent file */
332     H5F_efc_ent_t *ent = NULL;  /* Entry for target file in efc */
333     herr_t      ret_value = SUCCEED; /* Return value */
334 
335     FUNC_ENTER_NOAPI_NOINIT
336 
337     /* Sanity checks */
338     HDassert(parent);
339     HDassert(parent->shared);
340     HDassert(file);
341     HDassert(file->shared);
342 
343     /* Get external file cache */
344     efc = parent->shared->efc;
345 
346     /* Check if the EFC exists.  If it does not, just call H5F_try_close().  We
347      * support this so clients do not have to make 2 different calls depending
348      * on the state of the efc. */
349     if(!efc) {
350         file->nopen_objs--;
351         if(H5F_try_close(file, NULL) < 0)
352             HGOTO_ERROR(H5E_FILE, H5E_CANTCLOSEFILE, FAIL, "can't close external file")
353 
354         HGOTO_DONE(SUCCEED)
355     } /* end if */
356 
357     /* Scan the parent's LRU list from the head to file file.  We do this
358      * instead of a skip list lookup because the file will almost always be at
359      * the head.  In the unlikely case that the file is not found, just call
360      * H5F_try_close().  This could happen if the EFC was full of open files
361      * when the file was opened. */
362     for(ent = efc->LRU_head; ent && ent->file != file; ent = ent->LRU_next);
363     if(!ent) {
364         file->nopen_objs--;
365         if(H5F_try_close(file, NULL) < 0)
366             HGOTO_ERROR(H5E_FILE, H5E_CANTCLOSEFILE, FAIL, "can't close external file")
367     } /* end if */
368     else
369         /* Reduce the open count on this entry */
370         ent->nopen--;
371 
372 done:
373     FUNC_LEAVE_NOAPI(ret_value)
374 } /* end H5F_efc_close() */
375 
376 
377 /*-------------------------------------------------------------------------
378  * Function:    H5F__efc_max_nfiles
379  *
380  * Purpose:     Returns the maximum number of files in the provided
381  *              external file cache.
382  *
383  * Return:      Maximum number of files (never fails)
384  *
385  * Programmer:  Neil Fortner
386  *              Wednesday, December 15, 2010
387  *
388  *-------------------------------------------------------------------------
389  */
390 unsigned
H5F__efc_max_nfiles(H5F_efc_t * efc)391 H5F__efc_max_nfiles(H5F_efc_t *efc)
392 {
393     FUNC_ENTER_PACKAGE_NOERR
394 
395     HDassert(efc);
396     HDassert(efc->max_nfiles > 0);
397 
398     FUNC_LEAVE_NOAPI(efc->max_nfiles)
399 } /* end H5F__efc_max_nfiles */
400 
401 
402 /*-------------------------------------------------------------------------
403  * Function:    H5F__efc_release_real
404  *
405  * Purpose:     Releases the external file cache, potentially closing any
406  *              cached files unless they are held open from somewhere
407  *              else (or are currently opened by a client).
408  *
409  * Return:      Non-negative on success
410  *              Negative on failure
411  *
412  * Programmer:  Neil Fortner
413  *              Wednesday, December 15, 2010
414  *
415  *-------------------------------------------------------------------------
416  */
417 static herr_t
H5F__efc_release_real(H5F_efc_t * efc)418 H5F__efc_release_real(H5F_efc_t *efc)
419 {
420     H5F_efc_ent_t *ent = NULL;          /* EFC entry */
421     H5F_efc_ent_t *prev_ent = NULL;     /* Previous EFC entry */
422     herr_t      ret_value = SUCCEED;    /* Return value */
423 
424     FUNC_ENTER_STATIC
425 
426     /* Sanity checks */
427     HDassert(efc);
428 
429     /* Lock the EFC to prevent manipulation of the EFC wile we are releasing it.
430      * The EFC should never be locked when we enter this function because that
431      * would require a cycle, a cycle would necessarily invoke
432      * H5F__efc_try_close(), and that function checks the status of the lock
433      * before calling this one. */
434     HDassert((efc->tag == H5F_EFC_TAG_DEFAULT)
435             || (efc->tag == H5F_EFC_TAG_CLOSE));
436     efc->tag = H5F_EFC_TAG_LOCK;
437 
438     /* Walk down the LRU list, releasing any files that are not opened by an EFC
439      * client */
440     ent = efc->LRU_head;
441     while(ent)
442         if(!ent->nopen) {
443             if(H5F__efc_remove_ent(efc, ent) < 0)
444                 HGOTO_ERROR(H5E_FILE, H5E_CANTREMOVE, FAIL, "can't remove entry from external file cache")
445 
446             /* Free the entry and move to next entry in LRU list */
447             prev_ent = ent;
448             ent = ent->LRU_next;
449             prev_ent = H5FL_FREE(H5F_efc_ent_t, prev_ent);
450         } /* end if */
451         else
452             /* Can't release file because it's open; just advance the pointer */
453             ent = ent->LRU_next;
454 
455     /* Reset tag.  No need to reset to CLOSE if that was the original tag, as in
456      * that case the file must be getting closed anyways. */
457     efc->tag = H5F_EFC_TAG_DEFAULT;
458 
459 done:
460     FUNC_LEAVE_NOAPI(ret_value);
461 } /* end H5F__efc_release_real() */
462 
463 
464 /*-------------------------------------------------------------------------
465  * Function:    H5F__efc_release
466  *
467  * Purpose:     Releases the external file cache, potentially closing any
468  *              cached files unless they are held open from somewhere
469  *              else (or are currently opened by a client).
470  *
471  * Return:      Non-negative on success
472  *              Negative on failure
473  *
474  * Programmer:  Quincey Koziol
475  *              Sunday, February 18, 2018
476  *
477  *-------------------------------------------------------------------------
478  */
479 herr_t
H5F__efc_release(H5F_efc_t * efc)480 H5F__efc_release(H5F_efc_t *efc)
481 {
482     herr_t ret_value = SUCCEED;         /* Return value */
483 
484     FUNC_ENTER_PACKAGE_VOL
485 
486     /* Sanity checks */
487     HDassert(efc);
488 
489     /* Call 'real' routine */
490     if(H5F__efc_release_real(efc) < 0)
491         HGOTO_ERROR(H5E_FILE, H5E_CANTRELEASE, FAIL, "can't remove entry from external file cache")
492 
493 done:
494     FUNC_LEAVE_NOAPI_VOL(ret_value)
495 } /* end H5F_efc_release() */
496 
497 
498 /*-------------------------------------------------------------------------
499  * Function:    H5F__efc_destroy
500  *
501  * Purpose:     Frees an external file cache object, releasing it first
502  *              if necessary.  If it cannot be fully released, for example
503  *              if there are open files, returns an error.
504  *
505  * Return:      Non-negative on success
506  *              Negative on failure
507  *
508  * Programmer:  Neil Fortner
509  *              Wednesday, December 15, 2010
510  *
511  *-------------------------------------------------------------------------
512  */
513 herr_t
H5F__efc_destroy(H5F_efc_t * efc)514 H5F__efc_destroy(H5F_efc_t *efc)
515 {
516     herr_t      ret_value = SUCCEED;    /* Return value */
517 
518     FUNC_ENTER_PACKAGE
519 
520     /* Sanity checks */
521     HDassert(efc);
522 
523     if(efc->nfiles > 0) {
524         /* Release (clear) the efc */
525         if(H5F__efc_release_real(efc) < 0)
526             HGOTO_ERROR(H5E_FILE, H5E_CANTRELEASE, FAIL, "can't release external file cache")
527 
528         /* If there are still cached files, return an error */
529         if(efc->nfiles > 0)
530             HGOTO_ERROR(H5E_FILE, H5E_CANTFREE, FAIL, "can't destroy EFC after incomplete release")
531     } /* end if */
532 
533     HDassert(efc->nfiles == 0);
534     HDassert(efc->LRU_head == NULL);
535     HDassert(efc->LRU_tail == NULL);
536 
537     /* Close skip list */
538     if(efc->slist)
539         if(H5SL_close(efc->slist) < 0)
540             HGOTO_ERROR(H5E_FILE, H5E_CANTFREE, FAIL, "can't close skip list")
541 
542     /* Free EFC object */
543     (void)H5FL_FREE(H5F_efc_t, efc);
544 
545 done:
546     FUNC_LEAVE_NOAPI(ret_value);
547 } /* end H5F__efc_destroy() */
548 
549 
550 /*-------------------------------------------------------------------------
551  * Function:    H5F__efc_remove_ent
552  *
553  * Purpose:     Removes the specified entry from the specified EFC,
554  *              closing the file if requested.  Does not free the entry.
555  *
556  * Return:      Non-negative on success
557  *              Negative on failure
558  *
559  * Programmer:  Neil Fortner
560  *              Wednesday, December 15, 2010
561  *
562  *-------------------------------------------------------------------------
563  */
564 static herr_t
H5F__efc_remove_ent(H5F_efc_t * efc,H5F_efc_ent_t * ent)565 H5F__efc_remove_ent(H5F_efc_t *efc, H5F_efc_ent_t *ent)
566 {
567     herr_t      ret_value = SUCCEED;    /* Return value */
568 
569     FUNC_ENTER_STATIC
570 
571     /* Sanity checks */
572     HDassert(efc);
573     HDassert(efc->slist);
574     HDassert(ent);
575 
576     /* Remove from skip list */
577     if(ent != H5SL_remove(efc->slist, ent->name))
578         HGOTO_ERROR(H5E_FILE, H5E_CANTDELETE, FAIL, "can't delete entry from skip list")
579 
580     /* Remove from LRU list */
581     if(ent->LRU_next)
582         ent->LRU_next->LRU_prev = ent->LRU_prev;
583     else {
584         HDassert(efc->LRU_tail == ent);
585         efc->LRU_tail = ent->LRU_prev;
586     } /* end else */
587     if(ent->LRU_prev)
588         ent->LRU_prev->LRU_next = ent->LRU_next;
589     else {
590         HDassert(efc->LRU_head == ent);
591         efc->LRU_head = ent->LRU_next;
592     } /* end else */
593 
594     /* Update nfiles and nrefs */
595     efc->nfiles--;
596     if(ent->file->shared->efc)
597         ent->file->shared->efc->nrefs--;
598 
599     /* Free the name */
600     ent->name = (char *)H5MM_xfree(ent->name);
601 
602     /* Close the file.  Note that since H5F_t structs returned from H5F_open()
603      * are *always* unique, there is no need to reference count this struct.
604      * However we must still manipulate the nopen_objs field to prevent the file
605      * from being closed out from under us. */
606     ent->file->nopen_objs--;
607     if(H5F_try_close(ent->file, NULL) < 0)
608         HGOTO_ERROR(H5E_FILE, H5E_CANTCLOSEFILE, FAIL, "can't close external file")
609     ent->file = NULL;
610 
611 done:
612     FUNC_LEAVE_NOAPI(ret_value)
613 } /* end H5F__efc_remove_ent() */
614 
615 
616 /*-------------------------------------------------------------------------
617  * Function:    H5F__efc_try_close_tag1
618  *
619  * Purpose:     Recursively traverse the EFC tree, keeping a temporary
620  *              reference count on each file that assumes all reachable
621  *              files will eventually be closed.
622  *
623  * Return:      void (never fails)
624  *
625  * Programmer:  Neil Fortner
626  *              Monday, January 10, 2011
627  *
628  *-------------------------------------------------------------------------
629  */
630 static void
H5F__efc_try_close_tag1(H5F_file_t * sf,H5F_file_t ** tail)631 H5F__efc_try_close_tag1(H5F_file_t *sf, H5F_file_t **tail)
632 {
633     H5F_efc_ent_t       *ent = NULL;    /* EFC entry */
634     H5F_file_t          *esf;           /* Convenience pointer to ent->file->shared */
635 
636     FUNC_ENTER_STATIC_NOERR
637 
638     /* Sanity checks */
639     HDassert(sf);
640     HDassert(sf->efc);
641     HDassert((sf->efc->tag > 0) || (sf->nrefs == sf->efc->nrefs));
642     HDassert(sf->efc->tag != H5F_EFC_TAG_LOCK);
643     HDassert(tail);
644     HDassert(*tail);
645 
646     /* Recurse into this file's cached files */
647     for(ent = sf->efc->LRU_head; ent; ent = ent->LRU_next) {
648         esf = ent->file->shared;
649 
650         if(esf->efc) {
651             /* If tag were 0, that would mean there are more actual references
652              * than are counted by nrefs */
653             HDassert(esf->efc->tag != 0);
654 
655             /* If tag has been set, we have already visited this file so just
656              * decrement tag and continue */
657             if(esf->efc->tag > 0)
658                 esf->efc->tag--;
659             /* If there are references that are not from an EFC, it will never
660              * be possible to close the file.  Just continue.  Also continue if
661              * the EFC is locked or the file is open (through the EFC).  Note
662              * that the reference counts will never match for the root file, but
663              * that's ok because the root file will always have a tag and enter
664              * the branch above. */
665             else if((esf->nrefs == esf->efc->nrefs)
666                     && (esf->efc->tag != H5F_EFC_TAG_LOCK) && !(ent->nopen)) {
667                 /* If we get here, this file's "tmp_next" pointer must be NULL
668                  */
669                 HDassert(esf->efc->tmp_next == NULL);
670 
671                 /* If nrefs > 1, Add this file to the list of files with nrefs >
672                  * 1 and initialize tag to the number of references (except this
673                  * one) */
674                 if(esf->nrefs > 1) {
675                     (*tail)->efc->tmp_next = esf;
676                     *tail = esf;
677                     esf->efc->tag = (int)esf->nrefs - 1;
678                 } /* end if */
679 
680                 /* Recurse into the entry */
681                 H5F__efc_try_close_tag1(ent->file->shared, tail);
682             } /* end if */
683         } /* end if */
684     } /* end for */
685 
686     FUNC_LEAVE_NOAPI_VOID
687 } /* end H5F__efc_try_close_tag1() */
688 
689 
690 /*-------------------------------------------------------------------------
691  * Function:    H5F__efc_try_close_tag2
692  *
693  * Purpose:     Recuresively mark all files reachable through this one as
694  *              uncloseable, and add newly uncloseable files to the tail
695  *              of the provided linked list.
696  *
697  * Return:      void (never fails)
698  *
699  * Programmer:  Neil Fortner
700  *              Monday, January 10, 2011
701  *
702  *-------------------------------------------------------------------------
703  */
704 static void
H5F__efc_try_close_tag2(H5F_file_t * sf,H5F_file_t ** tail)705 H5F__efc_try_close_tag2(H5F_file_t *sf, H5F_file_t **tail)
706 {
707     H5F_efc_ent_t       *ent = NULL;    /* EFC entry */
708     H5F_file_t          *esf;           /* Convenience pointer to ent->file->shared */
709 
710     FUNC_ENTER_STATIC_NOERR
711 
712     /* Sanity checks */
713     HDassert(sf);
714     HDassert(sf->efc);
715 
716     /* Recurse into this file's cached files */
717     for(ent = sf->efc->LRU_head; ent; ent = ent->LRU_next) {
718         esf = ent->file->shared;
719 
720         /* Only recurse if the file is tagged CLOSE or DEFAULT.  If it is tagged
721          * DONTCLOSE, we have already visited this file *or* it will be the
722          * start point of another iteration.  No files should be tagged with a
723          * nonegative value at this point.  If it is tagged as DEFAULT, we must
724          * apply the same conditions as in cb1 above for recursion in order to
725          * make sure  we do not go off into somewhere cb1 didn't touch.  The
726          * root file should never be tagged DEFAULT here, so the reference check
727          * is still appropriate. */
728         if((esf->efc) && ((esf->efc->tag == H5F_EFC_TAG_CLOSE)
729                 || ((esf->efc->tag == H5F_EFC_TAG_DEFAULT)
730                 && (esf->nrefs == esf->efc->nrefs) && !(ent->nopen)))) {
731             /* tag should always be CLOSE is nrefs > 1 or DEFAULT if nrefs == 1
732              * here */
733             HDassert(((esf->nrefs > 1)
734                     && ((esf->efc->tag == H5F_EFC_TAG_CLOSE)))
735                     || ((esf->nrefs == 1)
736                     && (esf->efc->tag == H5F_EFC_TAG_DEFAULT)));
737 
738             /* If tag is set to DONTCLOSE, we have already visited this file
739              * *or* it will be the start point of another iteration so just
740              * continue */
741             if(esf->efc->tag != H5F_EFC_TAG_DONTCLOSE) {
742                 /* If tag is CLOSE, set to DONTCLOSE and add to the list of
743                  * uncloseable files. */
744                 if(esf->efc->tag == H5F_EFC_TAG_CLOSE) {
745                     esf->efc->tag = H5F_EFC_TAG_DONTCLOSE;
746                     esf->efc->tmp_next = NULL;
747                     (*tail)->efc->tmp_next = esf;
748                     *tail = esf;
749                 } /* end if */
750 
751                 /* Recurse into the entry */
752                 H5F__efc_try_close_tag2(esf, tail);
753             } /* end if */
754         } /* end if */
755     } /* end for */
756 
757     FUNC_LEAVE_NOAPI_VOID
758 } /* end H5F__efc_try_close_tag2() */
759 
760 
761 /*-------------------------------------------------------------------------
762  * Function:    H5F__efc_try_close
763  *
764  * Purpose:     Attempts to close the provided (shared) file by checking
765  *              to see if the releasing the EFC would cause its reference
766  *              count to drop to 0.  Necessary to handle the case where
767  *              chained EFCs form a cycle.  Note that this function does
768  *              not actually close the file (though it closes all children
769  *              as appropriate), as that is left up to the calling
770  *              function H5F_try_close().
771  *
772  *              Because H5F_try_close() has no way of telling if it is
773  *              called recursively from within this function, this
774  *              function serves as both the root of iteration and the
775  *              "callback" for the final pass (the one where the files are
776  *              actually closed).  The code for the callback case is at
777  *              the top of this function; luckily it only consists of a
778  *              (possible) call to H5F__efc_release_real().
779  *
780  *              The algorithm basically consists of 3 passes over the EFC
781  *              tree.  The first pass assumes that every reachable file is
782  *              closed, and keeps track of what the final reference count
783  *              would be for every reachable file.  The files are then
784  *              tagged as either closeable or uncloseable based on whether
785  *              this reference count drops to 0.
786  *
787  *              The second pass initiates a traversal from each file
788  *              marked as uncloseable in the first pass, and marks every
789  *              file reachable from the initial uncloseable file as
790  *              uncloseable.  This eliminates files that were marked as
791  *              closeable only because the first pass assumed that an
792  *              uncloseable file would be closed.
793  *
794  *              The final pass exploits the H5F__efc_release_real()->
795  *              H5F__efc_remove_ent()->H5F_try_close()->H5F__efc_try_close()
796  *              calling chain to recursively close the tree, but only the
797  *              files that are still marked as closeable.  All files
798  *              marked as closeable have their EFCs released, and will
799  *              eventually be closed when their last parent EFC is
800  *              released (the last part is guaranteed to be true by the
801  *              first 2 passes).
802  *
803  * Return:      Non-negative on success
804  *              Negative on failure
805  *
806  * Programmer:  Neil Fortner
807  *              Thursday, January 6, 2011
808  *
809  *-------------------------------------------------------------------------
810  */
811 herr_t
H5F__efc_try_close(H5F_t * f)812 H5F__efc_try_close(H5F_t *f)
813 {
814     H5F_file_t  *tail;                  /* Tail of linked list of found files.  Head will be f->shared. */
815     H5F_file_t  *uncloseable_head = NULL; /* Head of linked list of files found to be uncloseable by the first pass */
816     H5F_file_t  *uncloseable_tail = NULL; /* Tail of linked list of files found to be uncloseable by the first pass */
817     H5F_file_t  *sf;                    /* Temporary file pointer */
818     H5F_file_t  *next;                  /* Temporary file pointer */
819     herr_t      ret_value = SUCCEED;    /* Return value */
820 
821     FUNC_ENTER_PACKAGE
822 
823     /* Sanity checks */
824     HDassert(f);
825     HDassert(f->shared);
826     HDassert(f->shared->efc);
827     HDassert(f->shared->nrefs > f->shared->efc->nrefs);
828     HDassert(f->shared->nrefs > 1);
829     HDassert(f->shared->efc->tag < 0);
830 
831     if(f->shared->efc->tag == H5F_EFC_TAG_CLOSE) {
832         /* We must have reentered this function, and we should close this file.
833          * In actuality, we just release the EFC, the recursion should
834          * eventually reduce this file's reference count to 1 (though possibly
835          * not from this call to H5F__efc_release_real()). */
836         if(H5F__efc_release_real(f->shared->efc) < 0)
837             HGOTO_ERROR(H5E_FILE, H5E_CANTRELEASE, FAIL, "can't release external file cache")
838 
839         /* If we marked the file as closeable, there must be no open files in
840          * its EFC.  This is because, in order to close an open child file, the
841          * client must keep a copy of the parent file open.  The algorithm
842          * detect that the parent file is open (directly or through an EFC) and
843          * refuse to close it.  Verify that all files were released from this
844          * EFC (i.e. none were open). */
845         HDassert(f->shared->efc->nfiles == 0);
846 
847         HGOTO_DONE(SUCCEED)
848     } /* end if */
849 
850     /* Conditions where we should not do anything and just return immediately */
851     /* If there are references that are not from an EFC or f, it will never
852      * be possible to close the file.  Just return.  Note that this holds true
853      * for the case that this file is being closed through H5F__efc_release_real()
854      * because that function (through H5F__efc_remove_ent()) decrements the EFC
855      * reference count before it calls H5F_try_close(). This may occur if this
856      * function is reentered. */
857     /* If the tag is H5F_EFC_TAG_DONTCLOSE, then we have definitely reentered
858      * this function, and this file has been marked as uncloseable, so we should
859      * not close/release it */
860     /* If nfiles is 0, then there is nothing to do.  Just return.  This may also
861      * occur on reentry (for example if this file was previously released). */
862     if((f->shared->nrefs != f->shared->efc->nrefs + 1)
863             || (f->shared->efc->tag == H5F_EFC_TAG_DONTCLOSE)
864             || (f->shared->efc->nfiles == 0))
865         /* We must have reentered this function, and we should not close this
866          * file.  Just return. */
867         HGOTO_DONE(SUCCEED)
868 
869     /* If the file EFC were locked, that should always mean that there exists
870      * a reference to this file that is not in an EFC (it may have just been
871      * removed from an EFC), and should have been caught by the above check */
872     /* If we get here then we must be beginning a new run.  Make sure that the
873      * temporary variables in f->shared->efc are at the default value */
874     HDassert(f->shared->efc->tag == H5F_EFC_TAG_DEFAULT);
875     HDassert(f->shared->efc->tmp_next == NULL);
876 
877     /* Set up linked list for traversal into EFC tree.  f->shared is guaranteed
878      * to always be at the head. */
879     tail = f->shared;
880 
881     /* Set up temporary reference count on root file */
882     f->shared->efc->tag = (int)f->shared->efc->nrefs;
883 
884     /* First Pass: simulate closing all files reachable from this one, use "tag"
885      * field to keep track of final reference count for each file (including
886      * this one).  Keep list of files with starting reference count > 1 (head is
887      * f->shared). */
888     H5F__efc_try_close_tag1(f->shared, &tail);
889 
890     /* Check if f->shared->efc->tag dropped to 0.  If it did not,
891      * we cannot close anything.  Just reset temporary values and return. */
892     if(f->shared->efc->tag > 0) {
893         sf = f->shared;
894         while(sf) {
895             next = sf->efc->tmp_next;
896             sf->efc->tag = H5F_EFC_TAG_DEFAULT;
897             sf->efc->tmp_next = NULL;
898             sf = next;
899         } /* end while */
900         HGOTO_DONE(SUCCEED)
901     } /* end if */
902 
903     /* Run through the linked list , separating into two lists, one with tag ==
904      * 0 and one with tag > 0.  Mark them as either H5F_EFC_TAG_CLOSE or
905      * H5F_EFC_TAG_DONTCLOSE as appropriate. */
906     sf = f->shared;
907     tail = NULL;
908     while(sf) {
909         HDassert(sf->efc->tag >= 0);
910         next = sf->efc->tmp_next;
911         if(sf->efc->tag > 0) {
912             /* Remove from main list */
913             HDassert(tail);
914             tail->efc->tmp_next = sf->efc->tmp_next;
915             sf->efc->tmp_next = NULL;
916 
917             /* Add to uncloseable list */
918             if(!uncloseable_head)
919                 uncloseable_head = sf;
920             else
921                 uncloseable_tail->efc->tmp_next = sf;
922             uncloseable_tail = sf;
923 
924             /* Mark as uncloseable */
925             sf->efc->tag = H5F_EFC_TAG_DONTCLOSE;
926         } /* end if */
927         else {
928             sf->efc->tag = H5F_EFC_TAG_CLOSE;
929             tail = sf;
930         } /* end else */
931         sf = next;
932     } /* end while */
933 
934     /* Second pass: Determine which of the reachable files found in pass 1
935      * cannot be closed by releasing the root file's EFC.  Run through the
936      * uncloseable list, for each item traverse the files reachable through the
937      * EFC, mark the file as uncloseable, and add it to the list of uncloseable
938      * files (for cleanup).  Use "tail" to store the original uncloseable tail
939      * so we know when to stop.  We do not need to keep track of the closeable
940      * list any more. */
941     sf = uncloseable_head;
942     if(sf) {
943         tail = uncloseable_tail;
944         HDassert(tail);
945         while(sf != tail->efc->tmp_next) {
946             H5F__efc_try_close_tag2(sf, &uncloseable_tail);
947             sf = sf->efc->tmp_next;
948         } /* end while */
949     } /* end if */
950 
951     /* If the root file's tag is still H5F_EFC_TAG_CLOSE, release its EFC.  This
952      * should start the recursive release that should close all closeable files.
953      * Also, see the top of this function. */
954     if(f->shared->efc->tag == H5F_EFC_TAG_CLOSE) {
955         if(H5F__efc_release_real(f->shared->efc) < 0)
956             HGOTO_ERROR(H5E_FILE, H5E_CANTRELEASE, FAIL, "can't release external file cache")
957 
958         /* Make sure the file's reference count is now 1 and will be closed by
959          * H5F_dest(). */
960         HDassert(f->shared->nrefs == 1);
961     } /* end if */
962 
963     /* Clean up uncloseable files (reset tag and tmp_next).  All closeable files
964      * should have been closed, and therefore do not need to be cleaned up. */
965     if(uncloseable_head) {
966         sf = uncloseable_head;
967         while(sf) {
968             next = sf->efc->tmp_next;
969             HDassert(sf->efc->tag == H5F_EFC_TAG_DONTCLOSE);
970             sf->efc->tag = H5F_EFC_TAG_DEFAULT;
971             sf->efc->tmp_next = NULL;
972             sf = next;
973         } /* end while */
974     } /* end if */
975 
976 done:
977     FUNC_LEAVE_NOAPI(ret_value)
978 } /* end H5F__efc_try_close() */
979 
980