Coverage Report

Created: 2025-08-26 06:30

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