mirror of
https://github.com/HDFGroup/hdf5.git
synced 2024-12-09 07:32:32 +08:00
9c9ee2008c
Refactor function name macros and simplify the FUNC_ENTER macros, to clear away the cruft and prepare for further cleanups. Tested on: Mac OSX/64 10.7.3 (amazon) w/debug, production & parallel
952 lines
36 KiB
C
952 lines
36 KiB
C
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
* Copyright by The HDF Group. *
|
||
* Copyright by the Board of Trustees of the University of Illinois. *
|
||
* All rights reserved. *
|
||
* *
|
||
* This file is part of HDF5. The full HDF5 copyright notice, including *
|
||
* terms governing use, modification, and redistribution, is contained in *
|
||
* the files COPYING and Copyright.html. COPYING can be found at the root *
|
||
* of the source code distribution tree; Copyright.html can be found at the *
|
||
* root level of an installed copy of the electronic HDF5 document set and *
|
||
* is linked from the top-level documents page. It can also be found at *
|
||
* http://hdfgroup.org/HDF5/doc/Copyright.html. If you do not have *
|
||
* access to either file, you may request a copy from help@hdfgroup.org. *
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
/*-------------------------------------------------------------------------
|
||
*
|
||
* Created: H5Defc.c
|
||
* December 13, 2010
|
||
* Neil Fortner <nfortne2@hdfgroup.org>
|
||
*
|
||
* Purpose: External file caching routines - implements a
|
||
* cache of external files to minimize the number of
|
||
* file opens and closes.
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
|
||
#define H5F_PACKAGE /*suppress error about including H5Fpkg */
|
||
|
||
|
||
/* Packages needed by this file... */
|
||
#include "H5private.h" /* Generic Functions */
|
||
#include "H5Eprivate.h" /* Error handling */
|
||
#include "H5Fpkg.h" /* File access */
|
||
#include "H5MMprivate.h" /* Memory management */
|
||
#include "H5Pprivate.h" /* Property lists */
|
||
|
||
|
||
/* Special values for the "tag" field below */
|
||
#define H5F_EFC_TAG_DEFAULT -1
|
||
#define H5F_EFC_TAG_LOCK -2
|
||
#define H5F_EFC_TAG_CLOSE -3
|
||
#define H5F_EFC_TAG_DONTCLOSE -4
|
||
|
||
/* Structure for each entry in a file's external file cache */
|
||
typedef struct H5F_efc_ent_t {
|
||
char *name; /* Name of the file */
|
||
H5F_t *file; /* File object */
|
||
struct H5F_efc_ent_t *LRU_next; /* Next item in LRU list */
|
||
struct H5F_efc_ent_t *LRU_prev; /* Previous item in LRU list */
|
||
unsigned nopen; /* Number of times this file is currently opened by an EFC client */
|
||
} H5F_efc_ent_t;
|
||
|
||
/* Structure for a shared file struct's external file cache */
|
||
struct H5F_efc_t {
|
||
H5SL_t *slist; /* Skip list of cached external files */
|
||
H5F_efc_ent_t *LRU_head; /* Head of LRU list. This is the least recently used file */
|
||
H5F_efc_ent_t *LRU_tail; /* Tail of LRU list. This is the most recently used file */
|
||
unsigned nfiles; /* Size of the external file cache */
|
||
unsigned max_nfiles; /* Maximum size of the external file cache */
|
||
unsigned nrefs; /* Number of times this file appears in another file's EFC */
|
||
int tag; /* Temporary variable used by H5F_efc_try_close() */
|
||
H5F_file_t *tmp_next; /* Next file in temporary list used by H5F_efc_try_close() */
|
||
};
|
||
|
||
/* Private prototypes */
|
||
static herr_t H5F_efc_remove_ent(H5F_efc_t *efc, H5F_efc_ent_t *ent);
|
||
static void H5F_efc_try_close_tag1(H5F_file_t *sf, H5F_file_t **tail);
|
||
static void H5F_efc_try_close_tag2(H5F_file_t *sf, H5F_file_t **tail);
|
||
|
||
/* Free lists */
|
||
H5FL_DEFINE_STATIC(H5F_efc_ent_t);
|
||
H5FL_DEFINE_STATIC(H5F_efc_t);
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_create
|
||
*
|
||
* Purpose: Allocate and initialize a new external file cache object,
|
||
* which can the be used to cache open external files.
|
||
* the object must be freed with H5F_efc_destroy.
|
||
*
|
||
* Return: Pointer to new external file cache object on success
|
||
* NULL on failure
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Tuesday, December 14, 2010
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
H5F_efc_t *
|
||
H5F_efc_create(unsigned max_nfiles)
|
||
{
|
||
H5F_efc_t *efc = NULL; /* EFC object */
|
||
H5F_efc_t *ret_value; /* Return value */
|
||
|
||
FUNC_ENTER_NOAPI(NULL)
|
||
|
||
/* Sanity checks */
|
||
HDassert(max_nfiles > 0);
|
||
|
||
/* Allocate EFC struct */
|
||
if(NULL == (efc = H5FL_CALLOC(H5F_efc_t)))
|
||
HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "memory allocation failed")
|
||
|
||
/* Initialize maximum number of files */
|
||
efc->max_nfiles = max_nfiles;
|
||
|
||
/* Initialize temporary ref count */
|
||
efc->tag = H5F_EFC_TAG_DEFAULT;
|
||
|
||
/* Set the return value */
|
||
ret_value = efc;
|
||
|
||
done:
|
||
if(ret_value == NULL && efc)
|
||
efc = H5FL_FREE(H5F_efc_t, efc);
|
||
|
||
FUNC_LEAVE_NOAPI(ret_value)
|
||
} /* end H5F_efc_create() */
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_open
|
||
*
|
||
* Purpose: Opens a file using the external file cache. The target
|
||
* file is added to the external file cache of the parent
|
||
* if it is not already present. If the target file is in
|
||
* the parent's EFC, simply returns the target file. When
|
||
* the file object is no longer in use, it should be closed
|
||
* with H5F_efc_close (will not actually close the file
|
||
* until it is evicted from the EFC).
|
||
*
|
||
* Return: Pointer to open file on success
|
||
* NULL on failure
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Tuesday, December 14, 2010
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
H5F_t *
|
||
H5F_efc_open(H5F_t *parent, const char *name, unsigned flags, hid_t fcpl_id,
|
||
hid_t fapl_id, hid_t dxpl_id)
|
||
{
|
||
H5F_efc_t *efc = NULL; /* External file cache for parent file */
|
||
H5F_efc_ent_t *ent = NULL; /* Entry for target file in efc */
|
||
hbool_t open_file = FALSE; /* Whether ent->file needs to be closed in case of error */
|
||
H5F_t *ret_value = NULL; /* Return value */
|
||
|
||
FUNC_ENTER_NOAPI_NOINIT
|
||
|
||
/* Sanity checks */
|
||
HDassert(parent);
|
||
HDassert(parent->shared);
|
||
HDassert(name);
|
||
|
||
/* Get external file cache */
|
||
efc = parent->shared->efc;
|
||
|
||
/* Check if the EFC exists. If it does not, just call H5F_open(). We
|
||
* support this so clients do not have to make 2 different calls depending
|
||
* on the state of the efc. */
|
||
if(!efc) {
|
||
if(NULL == (ret_value = H5F_open(name, flags, fcpl_id, fapl_id,
|
||
dxpl_id)))
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTOPENFILE, NULL, "can't open file")
|
||
|
||
/* Increment the number of open objects to prevent the file from being
|
||
* closed out from under us - "simulate" having an open file id. Note
|
||
* that this behaviour replaces the calls to H5F_incr_nopen_objs() and
|
||
* H5F_decr_nopen_objs() in H5L_extern_traverse(). */
|
||
ret_value->nopen_objs++;
|
||
|
||
HGOTO_DONE(ret_value)
|
||
} /* end if */
|
||
|
||
/* Search the skip list for name if the skip list exists, create the skip
|
||
* list otherwise */
|
||
if(efc->slist) {
|
||
if(efc->nfiles > 0)
|
||
ent = (H5F_efc_ent_t *)H5SL_search(efc->slist, name);
|
||
} /* end if */
|
||
else {
|
||
HDassert(efc->nfiles == 0);
|
||
if(NULL == (efc->slist = H5SL_create(H5SL_TYPE_STR, NULL)))
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTCREATE, NULL, "can't create skip list")
|
||
} /* end else */
|
||
|
||
/* If we found the file update the LRU list and return the cached file,
|
||
* otherwise open the file and cache it */
|
||
if(ent) {
|
||
HDassert(efc->LRU_head);
|
||
HDassert(efc->LRU_tail);
|
||
|
||
/* Move ent to the head of the LRU list, if it is not already there */
|
||
if(ent->LRU_prev) {
|
||
HDassert(efc->LRU_head != ent);
|
||
|
||
/* Remove from current position. Note that once we touch the LRU
|
||
* list we cannot revert to the previous state. Make sure there can
|
||
* be no errors between when we first touch the LRU list and when
|
||
* the cache is in a consistent state! */
|
||
if(ent->LRU_next)
|
||
ent->LRU_next->LRU_prev = ent->LRU_prev;
|
||
else {
|
||
HDassert(efc->LRU_tail == ent);
|
||
efc->LRU_tail = ent->LRU_prev;
|
||
} /* end else */
|
||
ent->LRU_prev->LRU_next = ent->LRU_next;
|
||
|
||
/* Add to head of LRU list */
|
||
ent->LRU_next = efc->LRU_head;
|
||
ent->LRU_next->LRU_prev = ent;
|
||
ent->LRU_prev = NULL;
|
||
efc->LRU_head = ent;
|
||
} /* end if */
|
||
|
||
/* Mark the file as open */
|
||
ent->nopen++;
|
||
} /* end if */
|
||
else {
|
||
/* Check if we need to evict something */
|
||
if(efc->nfiles == efc->max_nfiles) {
|
||
/* Search for an unopened file from the tail */
|
||
for(ent = efc->LRU_tail; ent && ent->nopen; ent = ent->LRU_prev);
|
||
|
||
/* Evict the file if found, otherwise just open the target file and
|
||
* do not add it to cache */
|
||
if(ent) {
|
||
if(H5F_efc_remove_ent(efc, ent) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTREMOVE, NULL, "can't remove entry from external file cache")
|
||
|
||
/* Do not free ent, we will recycle it below */
|
||
} /* end if */
|
||
else {
|
||
/* Cannot cache file, just open file and return */
|
||
if(NULL == (ret_value = H5F_open(name, flags, fcpl_id, fapl_id,
|
||
dxpl_id)))
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTOPENFILE, NULL, "can't open file")
|
||
|
||
/* Increment the number of open objects to prevent the file from
|
||
* being closed out from under us - "simulate" having an open
|
||
* file id */
|
||
ret_value->nopen_objs++;
|
||
|
||
HGOTO_DONE(ret_value)
|
||
} /* end else */
|
||
} /* end if */
|
||
else
|
||
/* Allocate new entry */
|
||
if(NULL == (ent = H5FL_MALLOC(H5F_efc_ent_t)))
|
||
HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "memory allocation failed")
|
||
|
||
/* Build new entry */
|
||
if(NULL == (ent->name = H5MM_strdup(name)))
|
||
HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "memory allocation failed")
|
||
|
||
/* Open the file */
|
||
if(NULL == (ent->file = H5F_open(name, flags, fcpl_id, fapl_id,
|
||
dxpl_id)))
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTOPENFILE, NULL, "can't open file")
|
||
open_file = TRUE;
|
||
|
||
/* Increment the number of open objects to prevent the file from being
|
||
* closed out from under us - "simulate" having an open file id */
|
||
ent->file->nopen_objs++;
|
||
|
||
/* Add the file to the cache */
|
||
/* Skip list */
|
||
if(H5SL_insert(efc->slist, ent, ent->name) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTINSERT, NULL, "can't insert entry into skip list")
|
||
|
||
/* Add to head of LRU list and update tail if necessary */
|
||
ent->LRU_next = efc->LRU_head;
|
||
if(ent->LRU_next)
|
||
ent->LRU_next->LRU_prev = ent;
|
||
ent->LRU_prev = NULL;
|
||
efc->LRU_head = ent;
|
||
if(!efc->LRU_tail) {
|
||
HDassert(!ent->LRU_next);
|
||
efc->LRU_tail = ent;
|
||
} /* end if */
|
||
|
||
/* Mark the file as open */
|
||
ent->nopen = 1;
|
||
|
||
/* Update nfiles and nrefs */
|
||
efc->nfiles++;
|
||
if(ent->file->shared->efc)
|
||
ent->file->shared->efc->nrefs++;
|
||
} /* end else */
|
||
|
||
HDassert(ent);
|
||
HDassert(ent->file);
|
||
HDassert(ent->name);
|
||
HDassert(ent->nopen);
|
||
|
||
/* Set the return value */
|
||
ret_value = ent->file;
|
||
|
||
done:
|
||
if(!ret_value)
|
||
if(ent) {
|
||
if(open_file) {
|
||
ent->file->nopen_objs--;
|
||
if(H5F_try_close(ent->file) < 0)
|
||
HDONE_ERROR(H5E_FILE, H5E_CANTCLOSEFILE, NULL, "can't close external file")
|
||
} /* end if */
|
||
ent->name = (char *)H5MM_xfree(ent->name);
|
||
ent = H5FL_FREE(H5F_efc_ent_t, ent);
|
||
} /* end if */
|
||
|
||
FUNC_LEAVE_NOAPI(ret_value)
|
||
} /* end H5F_efc_open() */
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_close
|
||
*
|
||
* Purpose: Closes (unlocks) a file opened using the external file
|
||
* cache. The target file is not immediately closed unless
|
||
* there is no external file cache for the parent file.
|
||
*
|
||
* Return: Non-negative on success
|
||
* Negative on failure
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Wednesday, December 15, 2010
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
herr_t
|
||
H5F_efc_close(H5F_t *parent, H5F_t *file)
|
||
{
|
||
H5F_efc_t *efc = NULL; /* External file cache for parent file */
|
||
H5F_efc_ent_t *ent = NULL; /* Entry for target file in efc */
|
||
herr_t ret_value = SUCCEED; /* Return value */
|
||
|
||
FUNC_ENTER_NOAPI_NOINIT
|
||
|
||
/* Sanity checks */
|
||
HDassert(parent);
|
||
HDassert(parent->shared);
|
||
HDassert(file);
|
||
HDassert(file->shared);
|
||
|
||
/* Get external file cache */
|
||
efc = parent->shared->efc;
|
||
|
||
/* Check if the EFC exists. If it does not, just call H5F_try_close(). We
|
||
* support this so clients do not have to make 2 different calls depending
|
||
* on the state of the efc. */
|
||
if(!efc) {
|
||
file->nopen_objs--;
|
||
if(H5F_try_close(file) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTCLOSEFILE, FAIL, "can't close external file")
|
||
|
||
HGOTO_DONE(SUCCEED)
|
||
} /* end if */
|
||
|
||
/* Scan the parent's LRU list from the head to file file. We do this
|
||
* instead of a skip list lookup because the file will almost always be at
|
||
* the head. In the unlikely case that the file is not found, just call
|
||
* H5F_try_close(). This could happen if the EFC was full of open files
|
||
* when the file was opened. */
|
||
for(ent = efc->LRU_head; ent && ent->file != file; ent = ent->LRU_next);
|
||
if(!ent) {
|
||
file->nopen_objs--;
|
||
if(H5F_try_close(file) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTCLOSEFILE, FAIL, "can't close external file")
|
||
} /* end if */
|
||
else
|
||
/* Reduce the open count on this entry */
|
||
ent->nopen--;
|
||
|
||
done:
|
||
FUNC_LEAVE_NOAPI(ret_value)
|
||
} /* end H5F_efc_close() */
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_max_nfiles
|
||
*
|
||
* Purpose: Returns the maximum number of files in the provided
|
||
* external file cache.
|
||
*
|
||
* Return: Maximum number of files (never fails)
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Wednesday, December 15, 2010
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
unsigned
|
||
H5F_efc_max_nfiles(H5F_efc_t *efc)
|
||
{
|
||
FUNC_ENTER_NOAPI_NOINIT_NOERR
|
||
|
||
HDassert(efc);
|
||
HDassert(efc->max_nfiles > 0);
|
||
|
||
FUNC_LEAVE_NOAPI(efc->max_nfiles)
|
||
} /* end H5F_efc_max_nfiles */
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_release
|
||
*
|
||
* Purpose: Releases the external file cache, potentially closing any
|
||
* cached files unless they are held open from somewhere
|
||
* else (or are currently opened by a client).
|
||
*
|
||
* Return: Non-negative on success
|
||
* Negative on failure
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Wednesday, December 15, 2010
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
herr_t
|
||
H5F_efc_release(H5F_efc_t *efc)
|
||
{
|
||
H5F_efc_ent_t *ent = NULL; /* EFC entry */
|
||
H5F_efc_ent_t *prev_ent = NULL; /* Previous EFC entry */
|
||
herr_t ret_value = SUCCEED; /* Return value */
|
||
|
||
FUNC_ENTER_NOAPI_NOINIT
|
||
|
||
/* Sanity checks */
|
||
HDassert(efc);
|
||
|
||
/* Lock the EFC to prevent manipulation of the EFC wile we are releasing it.
|
||
* The EFC should never be locked when we enter this function because that
|
||
* would require a cycle, a cycle would necessarily invoke
|
||
* H5F_efc_try_close(), and that function checks the status of the lock
|
||
* before calling this one. */
|
||
HDassert((efc->tag == H5F_EFC_TAG_DEFAULT)
|
||
|| (efc->tag == H5F_EFC_TAG_CLOSE));
|
||
efc->tag = H5F_EFC_TAG_LOCK;
|
||
|
||
/* Walk down the LRU list, releasing any files that are not opened by an EFC
|
||
* client */
|
||
ent = efc->LRU_head;
|
||
while(ent)
|
||
if(!ent->nopen) {
|
||
if(H5F_efc_remove_ent(efc, ent) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTREMOVE, FAIL, "can't remove entry from external file cache")
|
||
|
||
/* Free the entry and move to next entry in LRU list */
|
||
prev_ent = ent;
|
||
ent = ent->LRU_next;
|
||
prev_ent = H5FL_FREE(H5F_efc_ent_t, prev_ent);
|
||
} /* end if */
|
||
else
|
||
/* Can't release file because it's open; just advance the pointer */
|
||
ent = ent->LRU_next;
|
||
|
||
/* Reset tag. No need to reset to CLOSE if that was the original tag, as in
|
||
* that case the file must be getting closed anyways. */
|
||
efc->tag = H5F_EFC_TAG_DEFAULT;
|
||
|
||
done:
|
||
FUNC_LEAVE_NOAPI(ret_value);
|
||
} /* end H5F_efc_release() */
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_destroy
|
||
*
|
||
* Purpose: Frees an external file cache object, releasing it first
|
||
* if necessary. If it cannot be fully released, for example
|
||
* if there are open files, returns an error.
|
||
*
|
||
* Return: Non-negative on success
|
||
* Negative on failure
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Wednesday, December 15, 2010
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
herr_t
|
||
H5F_efc_destroy(H5F_efc_t *efc)
|
||
{
|
||
herr_t ret_value = SUCCEED; /* Return value */
|
||
|
||
FUNC_ENTER_NOAPI_NOINIT
|
||
|
||
/* Sanity checks */
|
||
HDassert(efc);
|
||
|
||
if(efc->nfiles > 0) {
|
||
/* Release (clear) the efc */
|
||
if(H5F_efc_release(efc) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTRELEASE, FAIL, "can't release external file cache")
|
||
|
||
/* If there are still cached files, return an error */
|
||
if(efc->nfiles > 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTFREE, FAIL, "can't destroy EFC after incomplete release")
|
||
} /* end if */
|
||
|
||
HDassert(efc->nfiles == 0);
|
||
HDassert(efc->LRU_head == NULL);
|
||
HDassert(efc->LRU_tail == NULL);
|
||
|
||
/* Close skip list */
|
||
if(efc->slist)
|
||
if(H5SL_close(efc->slist) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTFREE, FAIL, "can't close skip list")
|
||
|
||
/* Free EFC object */
|
||
(void)H5FL_FREE(H5F_efc_t, efc);
|
||
|
||
done:
|
||
FUNC_LEAVE_NOAPI(ret_value);
|
||
} /* end H5F_efc_destroy() */
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_remove_ent
|
||
*
|
||
* Purpose: Removes the specified entry from the specified EFC,
|
||
* closing the file if requested. Does not free the entry.
|
||
*
|
||
* Return: Non-negative on success
|
||
* Negative on failure
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Wednesday, December 15, 2010
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
static herr_t
|
||
H5F_efc_remove_ent(H5F_efc_t *efc, H5F_efc_ent_t *ent)
|
||
{
|
||
herr_t ret_value = SUCCEED; /* Return value */
|
||
|
||
FUNC_ENTER_NOAPI_NOINIT
|
||
|
||
/* Sanity checks */
|
||
HDassert(efc);
|
||
HDassert(efc->slist);
|
||
HDassert(ent);
|
||
|
||
/* Remove from skip list */
|
||
if(ent != H5SL_remove(efc->slist, ent->name))
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTDELETE, FAIL, "can't delete entry from skip list")
|
||
|
||
/* Remove from LRU list */
|
||
if(ent->LRU_next)
|
||
ent->LRU_next->LRU_prev = ent->LRU_prev;
|
||
else {
|
||
HDassert(efc->LRU_tail == ent);
|
||
efc->LRU_tail = ent->LRU_prev;
|
||
} /* end else */
|
||
if(ent->LRU_prev)
|
||
ent->LRU_prev->LRU_next = ent->LRU_next;
|
||
else {
|
||
HDassert(efc->LRU_head == ent);
|
||
efc->LRU_head = ent->LRU_next;
|
||
} /* end else */
|
||
|
||
/* Update nfiles and nrefs */
|
||
efc->nfiles--;
|
||
if(ent->file->shared->efc)
|
||
ent->file->shared->efc->nrefs--;
|
||
|
||
/* Free the name */
|
||
ent->name = (char *)H5MM_xfree(ent->name);
|
||
|
||
/* Close the file. Note that since H5F_t structs returned from H5F_open()
|
||
* are *always* unique, there is no need to reference count this struct.
|
||
* However we must still manipulate the nopen_objs field to prevent the file
|
||
* from being closed out from under us. */
|
||
ent->file->nopen_objs--;
|
||
if(H5F_try_close(ent->file) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTCLOSEFILE, FAIL, "can't close external file")
|
||
ent->file = NULL;
|
||
|
||
done:
|
||
FUNC_LEAVE_NOAPI(ret_value)
|
||
} /* end H5F_efc_remove_ent() */
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_try_close_tag1
|
||
*
|
||
* Purpose: Recursively traverse the EFC tree, keeping a temporary
|
||
* reference count on each file that assumes all reachable
|
||
* files will eventually be closed.
|
||
*
|
||
* Return: void (never fails)
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Monday, January 10, 2011
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
static void
|
||
H5F_efc_try_close_tag1(H5F_file_t *sf, H5F_file_t **tail)
|
||
{
|
||
H5F_efc_ent_t *ent = NULL; /* EFC entry */
|
||
H5F_file_t *esf; /* Convenience pointer to ent->file->shared */
|
||
|
||
FUNC_ENTER_NOAPI_NOINIT_NOERR
|
||
|
||
/* Sanity checks */
|
||
HDassert(sf);
|
||
HDassert(sf->efc);
|
||
HDassert((sf->efc->tag > 0) || (sf->nrefs == sf->efc->nrefs));
|
||
HDassert(sf->efc->tag != H5F_EFC_TAG_LOCK);
|
||
HDassert(tail);
|
||
HDassert(*tail);
|
||
|
||
/* Recurse into this file's cached files */
|
||
for(ent = sf->efc->LRU_head; ent; ent = ent->LRU_next) {
|
||
esf = ent->file->shared;
|
||
|
||
if(esf->efc) {
|
||
/* If tag were 0, that would mean there are more actual references
|
||
* than are counted by nrefs */
|
||
HDassert(esf->efc->tag != 0);
|
||
|
||
/* If tag has been set, we have already visited this file so just
|
||
* decrement tag and continue */
|
||
if(esf->efc->tag > 0)
|
||
esf->efc->tag--;
|
||
/* If there are references that are not from an EFC, it will never
|
||
* be possible to close the file. Just continue. Also continue if
|
||
* the EFC is locked or the file is open (through the EFC). Note
|
||
* that the reference counts will never match for the root file, but
|
||
* that's ok because the root file will always have a tag and enter
|
||
* the branch above. */
|
||
else if((esf->nrefs == esf->efc->nrefs)
|
||
&& (esf->efc->tag != H5F_EFC_TAG_LOCK) && !(ent->nopen)) {
|
||
/* If we get here, this file's "tmp_next" pointer must be NULL
|
||
*/
|
||
HDassert(esf->efc->tmp_next == NULL);
|
||
|
||
/* If nrefs > 1, Add this file to the list of files with nrefs >
|
||
* 1 and initialize tag to the number of references (except this
|
||
* one) */
|
||
if(esf->nrefs > 1) {
|
||
(*tail)->efc->tmp_next = esf;
|
||
*tail = esf;
|
||
esf->efc->tag = (int)esf->nrefs - 1;
|
||
} /* end if */
|
||
|
||
/* Recurse into the entry */
|
||
H5F_efc_try_close_tag1(ent->file->shared, tail);
|
||
} /* end if */
|
||
} /* end if */
|
||
} /* end for */
|
||
|
||
FUNC_LEAVE_NOAPI_VOID
|
||
} /* end H5F_efc_try_close_tag1() */
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_try_close_tag2
|
||
*
|
||
* Purpose: Recuresively mark all files reachable through this one as
|
||
* uncloseable, and add newly uncloseable files to the tail
|
||
* of the provided linked list.
|
||
*
|
||
* Return: void (never fails)
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Monday, January 10, 2011
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
static void
|
||
H5F_efc_try_close_tag2(H5F_file_t *sf, H5F_file_t **tail)
|
||
{
|
||
H5F_efc_ent_t *ent = NULL; /* EFC entry */
|
||
H5F_file_t *esf; /* Convenience pointer to ent->file->shared */
|
||
|
||
FUNC_ENTER_NOAPI_NOINIT_NOERR
|
||
|
||
/* Sanity checks */
|
||
HDassert(sf);
|
||
HDassert(sf->efc);
|
||
|
||
/* Recurse into this file's cached files */
|
||
for(ent = sf->efc->LRU_head; ent; ent = ent->LRU_next) {
|
||
esf = ent->file->shared;
|
||
|
||
/* Only recurse if the file is tagged CLOSE or DEFAULT. If it is tagged
|
||
* DONTCLOSE, we have already visited this file *or* it will be the
|
||
* start point of another iteration. No files should be tagged with a
|
||
* nonegative value at this point. If it is tagged as DEFAULT, we must
|
||
* apply the same conditions as in cb1 above for recursion in order to
|
||
* make sure we do not go off into somewhere cb1 didn't touch. The
|
||
* root file should never be tagged DEFAULT here, so the reference check
|
||
* is still appropriate. */
|
||
if((esf->efc) && ((esf->efc->tag == H5F_EFC_TAG_CLOSE)
|
||
|| ((esf->efc->tag == H5F_EFC_TAG_DEFAULT)
|
||
&& (esf->nrefs == esf->efc->nrefs) && !(ent->nopen)))) {
|
||
/* tag should always be CLOSE is nrefs > 1 or DEFAULT if nrefs == 1
|
||
* here */
|
||
HDassert(((esf->nrefs > 1)
|
||
&& ((esf->efc->tag == H5F_EFC_TAG_CLOSE)))
|
||
|| ((esf->nrefs == 1)
|
||
&& (esf->efc->tag == H5F_EFC_TAG_DEFAULT)));
|
||
|
||
/* If tag is set to DONTCLOSE, we have already visited this file
|
||
* *or* it will be the start point of another iteration so just
|
||
* continue */
|
||
if(esf->efc->tag != H5F_EFC_TAG_DONTCLOSE) {
|
||
/* If tag is CLOSE, set to DONTCLOSE and add to the list of
|
||
* uncloseable files. */
|
||
if(esf->efc->tag == H5F_EFC_TAG_CLOSE) {
|
||
esf->efc->tag = H5F_EFC_TAG_DONTCLOSE;
|
||
esf->efc->tmp_next = NULL;
|
||
(*tail)->efc->tmp_next = esf;
|
||
*tail = esf;
|
||
} /* end if */
|
||
|
||
/* Recurse into the entry */
|
||
H5F_efc_try_close_tag2(esf, tail);
|
||
} /* end if */
|
||
} /* end if */
|
||
} /* end for */
|
||
|
||
FUNC_LEAVE_NOAPI_VOID
|
||
} /* end H5F_efc_try_close_tag2() */
|
||
|
||
|
||
/*-------------------------------------------------------------------------
|
||
* Function: H5F_efc_try_close
|
||
*
|
||
* Purpose: Attempts to close the provided (shared) file by checking
|
||
* to see if the releasing the EFC would cause its reference
|
||
* count to drop to 0. Necessary to handle the case where
|
||
* chained EFCs form a cycle. Note that this function does
|
||
* not actually close the file (though it closes all children
|
||
* as appropriate), as that is left up to the calling
|
||
* function H5F_try_close().
|
||
*
|
||
* Because H5F_try_close() has no way of telling if it is
|
||
* called recursively from within this function, this
|
||
* function serves as both the root of iteration and the
|
||
* "callback" for the final pass (the one where the files are
|
||
* actually closed). The code for the callback case is at
|
||
* the top of this function; luckily it only consists of a
|
||
* (possible) call to H5F_efc_release().
|
||
*
|
||
* The algorithm basically consists of 3 passes over the EFC
|
||
* tree. The first pass assumes that every reachable file is
|
||
* closed, and keeps track of what the final reference count
|
||
* would be for every reachable file. The files are then
|
||
* tagged as either closeable or uncloseable based on whether
|
||
* this reference count drops to 0.
|
||
*
|
||
* The second pass initiates a traversal from each file
|
||
* marked as uncloseable in the first pass, and marks every
|
||
* file reachable from the initial uncloseable file as
|
||
* uncloseable. This eliminates files that were marked as
|
||
* closeable only because the first pass assumed that an
|
||
* uncloseable file would be closed.
|
||
*
|
||
* The final pass exploits the H5F_efc_release()->
|
||
* H5F_efc_remove_ent()->H5F_try_close()->H5F_efc_try_close()
|
||
* calling chain to recursively close the tree, but only the
|
||
* files that are still marked as closeable. All files
|
||
* marked as closeable have their EFCs released, and will
|
||
* eventually be closed when their last parent EFC is
|
||
* released (the last part is guaranteed to be true by the
|
||
* first 2 passes).
|
||
*
|
||
* Return: Non-negative on success
|
||
* Negative on failure
|
||
*
|
||
* Programmer: Neil Fortner
|
||
* Thursday, January 6, 2011
|
||
*
|
||
*-------------------------------------------------------------------------
|
||
*/
|
||
herr_t
|
||
H5F_efc_try_close(H5F_t *f)
|
||
{
|
||
H5F_file_t *tail; /* Tail of linked list of found files. Head will be f->shared. */
|
||
H5F_file_t *uncloseable_head = NULL; /* Head of linked list of files found to be uncloseable by the first pass */
|
||
H5F_file_t *uncloseable_tail = NULL; /* Tail of linked list of files found to be uncloseable by the first pass */
|
||
H5F_file_t *sf; /* Temporary file pointer */
|
||
H5F_file_t *next; /* Temporary file pointer */
|
||
herr_t ret_value = SUCCEED; /* Return value */
|
||
|
||
FUNC_ENTER_NOAPI_NOINIT
|
||
|
||
/* Sanity checks */
|
||
HDassert(f);
|
||
HDassert(f->shared);
|
||
HDassert(f->shared->efc);
|
||
HDassert(f->shared->nrefs > f->shared->efc->nrefs);
|
||
HDassert(f->shared->nrefs > 1);
|
||
HDassert(f->shared->efc->tag < 0);
|
||
|
||
if(f->shared->efc->tag == H5F_EFC_TAG_CLOSE) {
|
||
/* We must have reentered this function, and we should close this file.
|
||
* In actuality, we just release the EFC, the recursion should
|
||
* eventually reduce this file's reference count to 1 (though possibly
|
||
* not from this call to H5F_efc_release()). */
|
||
if(H5F_efc_release(f->shared->efc) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTRELEASE, FAIL, "can't release external file cache")
|
||
|
||
/* If we marked the file as closeable, there must be no open files in
|
||
* its EFC. This is because, in order to close an open child file, the
|
||
* client must keep a copy of the parent file open. The algorithm
|
||
* detect that the parent file is open (directly or through an EFC) and
|
||
* refuse to close it. Verify that all files were released from this
|
||
* EFC (i.e. none were open). */
|
||
HDassert(f->shared->efc->nfiles == 0);
|
||
|
||
HGOTO_DONE(SUCCEED)
|
||
} /* end if */
|
||
|
||
/* Conditions where we should not do anything and just return immediately */
|
||
/* If there are references that are not from an EFC or f, it will never
|
||
* be possible to close the file. Just return. Note that this holds true
|
||
* for the case that this file is being closed through H5F_efc_release()
|
||
* because that function (through H5F_efc_remove_ent()) decrements the EFC
|
||
* reference count before it calls H5F_try_close(). This may occur if this
|
||
* function is reentered. */
|
||
/* If the tag is H5F_EFC_TAG_DONTCLOSE, then we have definitely reentered
|
||
* this function, and this file has been marked as uncloseable, so we should
|
||
* not close/release it */
|
||
/* If nfiles is 0, then there is nothing to do. Just return. This may also
|
||
* occur on reentry (for example if this file was previously released). */
|
||
if((f->shared->nrefs != f->shared->efc->nrefs + 1)
|
||
|| (f->shared->efc->tag == H5F_EFC_TAG_DONTCLOSE)
|
||
|| (f->shared->efc->nfiles == 0))
|
||
/* We must have reentered this function, and we should not close this
|
||
* file. Just return. */
|
||
HGOTO_DONE(SUCCEED)
|
||
|
||
/* If the file EFC were locked, that should always mean that there exists
|
||
* a reference to this file that is not in an EFC (it may have just been
|
||
* removed from an EFC), and should have been caught by the above check */
|
||
/* If we get here then we must be beginning a new run. Make sure that the
|
||
* temporary variables in f->shared->efc are at the default value */
|
||
HDassert(f->shared->efc->tag == H5F_EFC_TAG_DEFAULT);
|
||
HDassert(f->shared->efc->tmp_next == NULL);
|
||
|
||
/* Set up linked list for traversal into EFC tree. f->shared is guaranteed
|
||
* to always be at the head. */
|
||
tail = f->shared;
|
||
|
||
/* Set up temporary reference count on root file */
|
||
f->shared->efc->tag = (int)f->shared->efc->nrefs;
|
||
|
||
/* First Pass: simulate closing all files reachable from this one, use "tag"
|
||
* field to keep track of final reference count for each file (including
|
||
* this one). Keep list of files with starting reference count > 1 (head is
|
||
* f->shared). */
|
||
H5F_efc_try_close_tag1(f->shared, &tail);
|
||
|
||
/* Check if f->shared->efc->tag dropped to 0. If it did not,
|
||
* we cannot close anything. Just reset temporary values and return. */
|
||
if(f->shared->efc->tag > 0) {
|
||
sf = f->shared;
|
||
while(sf) {
|
||
next = sf->efc->tmp_next;
|
||
sf->efc->tag = H5F_EFC_TAG_DEFAULT;
|
||
sf->efc->tmp_next = NULL;
|
||
sf = next;
|
||
} /* end while */
|
||
HGOTO_DONE(SUCCEED)
|
||
} /* end if */
|
||
|
||
/* Run through the linked list , separating into two lists, one with tag ==
|
||
* 0 and one with tag > 0. Mark them as either H5F_EFC_TAG_CLOSE or
|
||
* H5F_EFC_TAG_DONTCLOSE as appropriate. */
|
||
sf = f->shared;
|
||
tail = NULL;
|
||
while(sf) {
|
||
HDassert(sf->efc->tag >= 0);
|
||
next = sf->efc->tmp_next;
|
||
if(sf->efc->tag > 0) {
|
||
/* Remove from main list */
|
||
HDassert(tail);
|
||
tail->efc->tmp_next = sf->efc->tmp_next;
|
||
sf->efc->tmp_next = NULL;
|
||
|
||
/* Add to uncloseable list */
|
||
if(!uncloseable_head)
|
||
uncloseable_head = sf;
|
||
else
|
||
uncloseable_tail->efc->tmp_next = sf;
|
||
uncloseable_tail = sf;
|
||
|
||
/* Mark as uncloseable */
|
||
sf->efc->tag = H5F_EFC_TAG_DONTCLOSE;
|
||
} /* end if */
|
||
else {
|
||
sf->efc->tag = H5F_EFC_TAG_CLOSE;
|
||
tail = sf;
|
||
} /* end else */
|
||
sf = next;
|
||
} /* end while */
|
||
|
||
/* Second pass: Determine which of the reachable files found in pass 1
|
||
* cannot be closed by releasing the root file's EFC. Run through the
|
||
* uncloseable list, for each item traverse the files reachable through the
|
||
* EFC, mark the file as uncloseable, and add it to the list of uncloseable
|
||
* files (for cleanup). Use "tail" to store the original uncloseable tail
|
||
* so we know when to stop. We do not need to keep track of the closeable
|
||
* list any more. */
|
||
sf = uncloseable_head;
|
||
if(sf) {
|
||
tail = uncloseable_tail;
|
||
HDassert(tail);
|
||
while(sf != tail->efc->tmp_next) {
|
||
H5F_efc_try_close_tag2(sf, &uncloseable_tail);
|
||
sf = sf->efc->tmp_next;
|
||
} /* end while */
|
||
} /* end if */
|
||
|
||
/* If the root file's tag is still H5F_EFC_TAG_CLOSE, release its EFC. This
|
||
* should start the recursive release that should close all closeable files.
|
||
* Also, see the top of this function. */
|
||
if(f->shared->efc->tag == H5F_EFC_TAG_CLOSE) {
|
||
if(H5F_efc_release(f->shared->efc) < 0)
|
||
HGOTO_ERROR(H5E_FILE, H5E_CANTRELEASE, FAIL, "can't release external file cache")
|
||
|
||
/* Make sure the file's reference count is now 1 and will be closed by
|
||
* H5F_dest(). */
|
||
HDassert(f->shared->nrefs == 1);
|
||
} /* end if */
|
||
|
||
/* Clean up uncloseable files (reset tag and tmp_next). All closeable files
|
||
* should have been closed, and therefore do not need to be cleaned up. */
|
||
if(uncloseable_head) {
|
||
sf = uncloseable_head;
|
||
while(sf) {
|
||
next = sf->efc->tmp_next;
|
||
HDassert(sf->efc->tag == H5F_EFC_TAG_DONTCLOSE);
|
||
sf->efc->tag = H5F_EFC_TAG_DEFAULT;
|
||
sf->efc->tmp_next = NULL;
|
||
sf = next;
|
||
} /* end while */
|
||
} /* end if */
|
||
|
||
done:
|
||
FUNC_LEAVE_NOAPI(ret_value)
|
||
} /* end H5F_efc_try_close() */
|
||
|