Fix issues with writing vars with unlimited dimensions using NetCDF/HDF5 - #710
Fix issues with writing vars with unlimited dimensions using NetCDF/HDF5#710jayeshkrishna wants to merge 4 commits into
Conversation
Adding collective access when writing/putting vars using NetCDF4 parallel I/O. The collective access is already set when defining these variables. However it was not set when a file is re-opened and the variable was appended (variable id is queried here) Also adding a util function to check if a variable has unlimited dimension. This function is currently not being used but is useful for debugging (and for future code development)
|
NetCDF and HDF5 cleanup calls,
|
There was a problem hiding this comment.
Pull request overview
This PR targets robustness of SCORPIO’s NetCDF/HDF5 backends by (a) enforcing COLLECTIVE access for NetCDF4 parallel I/O to better support variables with unlimited dimensions, and (b) performing more aggressive cleanup during PIOc_finalize_impl() to avoid MPI-related teardown problems (Fixes #711).
Changes:
- Adjust file-finalization logic to close/cleanup remaining open files and delete I/O decompositions during
PIOc_finalize_impl(). - Add NetCDF4-parallel “set COLLECTIVE access” behavior during open and var definition paths.
- Add explicit NetCDF/HDF5 library cleanup calls (
nc_finalize(),H5close()) when the last IO system is finalized.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/clib/pio_internal.h | Updates internal helper signature for finalize-time file cleanup. |
| src/clib/core/util/pio_lists.cpp | Adds conditional behavior around waiting on hard-close vs. not waiting during finalize cleanup. |
| src/clib/core/pioc.cpp | Extends PIOc_finalize_impl() to close remaining files, delete decomps, and call NetCDF/HDF5 cleanup. |
| src/clib/core/pioc_support.cpp | Adds NetCDF4-parallel “set COLLECTIVE access” logic on file open paths. |
| src/clib/core/pio_nc.cpp | Small control-flow bracing change around nc_inq_varid() call. |
| src/clib/core/pio_getput_int.cpp | Adds an (currently unused) helper related to detecting unlimited dimensions; adds includes. |
Suppressed comments (2)
src/clib/core/pioc_support.cpp:5141
- In the NCZarr open path this attempts
nc_var_par_access(file->fh, NC_GLOBAL, ...)even whennc_open_par()was never called (e.g., empty NCZarr filename) or failed, andNC_GLOBALis not a valid varid fornc_var_par_access(). This can generate spurious warnings and does not achieve the intended COLLECTIVE setting for variables.
/* Set COLLECTIVE access for all variables.
* FIXME: We might just need it for some variables (unlimited dims, chunked,
* compressed etc). Revisit this call after measuring performance
*/
ierr = nc_var_par_access(file->fh, NC_GLOBAL, NC_COLLECTIVE);
if(ierr != PIO_NOERR){
src/clib/core/pioc.cpp:1766
H5close()returns a status; ignoring it can hide failures to shut down the HDF5 library cleanly (often indicating leaked open objects). Consider checking the return value and emitting a warning on failure.
#ifdef _HDF5
if(niosysid == 1){
/* All data should already be flushed by now. Free any HDF5 internal structures/objects/memory */
H5close();
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| /* Set COLLECTIVE access for all variables. | ||
| * FIXME: We might just need it for some variables (unlimited dims, chunked, | ||
| * compressed etc). Revisit this call after measuring performance | ||
| */ | ||
| ierr = nc_var_par_access(file->fh, NC_GLOBAL, NC_COLLECTIVE); | ||
| if(ierr != PIO_NOERR){ | ||
| std::string warn_msg = std::string("Setting COLLECTIVE access to all variables in file failed") + | ||
| std::string(", file : ") + filename + | ||
| std::string(", iotype :") + pio_iotype_to_string(file->iotype); | ||
|
|
||
| PIOc_warn(iosysid, file->fh, __FILE__, __LINE__, warn_msg.c_str()); | ||
| } |
| if(wait_on_hard_close){ | ||
| //ret = spio_hard_closefile(file->iosystem, file, true); | ||
| ret = spio_wait_on_hard_close(file->iosystem, file); | ||
| if(ret != PIO_NOERR){ | ||
| return pio_err(file->iosystem, file, PIO_EINTERNAL, __FILE__, __LINE__, | ||
| "Error closing file (hard close failed)"); | ||
| } | ||
| } | ||
| ncids_to_del_from_list.push_back(file->pio_ncid); | ||
| } |
| /* All data should already be flushed by now. Free any NetCDF internal structures/objects/memory */ | ||
| nc_finalize(); | ||
| } |
| } | ||
|
|
||
| /* Returns true if a variable has unlimited dimensions, false otherwise (and on error) */ | ||
| static bool spio_var_has_unlimited_dims(file_desc_t *file, int varid, int &ret) |
d3957eb to
f2984c8
Compare
Adding functions to cleanup internal memory/objects in NetCDF and HDF5 libraries
Make sure that unclosed files are closed on finalize. Moving the function out of async block so that it runs for non-async cases as well. Also update the function to handle async and non-async cases appropriately
Make sure that all I/O decomps are finalized/deleted in finalize. Moving the call to delete I/O decomps outside "async block" of code so that it also runs for non-async cases. I/O decomps contain MPI types and required MPI calls for cleanup, so destructors cannot automatically clean it up (since the destructors will end up being called after MPI finalize).
f2984c8 to
d0eccce
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
src/clib/core/util/pio_lists.cpp:228
- When wait_on_hard_close is false (the default in non-async builds), this function deletes file descriptors from the global list without actually closing the underlying file handles. That can leak resources and undermines the intent of closing files during PIOc_finalize_impl(). Consider performing a synchronous hard close in the non-wait path before deleting from the list.
if(file->iosystem->iosysid == iosysid){
if(wait_on_hard_close){
//ret = spio_hard_closefile(file->iosystem, file, true);
ret = spio_wait_on_hard_close(file->iosystem, file);
if(ret != PIO_NOERR){
| #ifdef _NETCDF | ||
| if(niosysid == 1){ | ||
| /* All data should already be flushed by now. Free any NetCDF internal structures/objects/memory */ | ||
| nc_finalize(); | ||
| } | ||
| #endif | ||
| #ifdef _HDF5 | ||
| if(niosysid == 1){ | ||
| /* All data should already be flushed by now. Free any HDF5 internal structures/objects/memory */ | ||
| H5close(); | ||
| } | ||
| #endif |
| #ifdef _NETCDF4 | ||
| /* If using NetCDF4 parallel I/O set the access mode to COLLECTIVE */ | ||
| if((file->iotype == PIO_IOTYPE_NETCDF4P) || (file->iotype == PIO_IOTYPE_NETCDF4P_NCZARR)){ | ||
| /* FIXME: We might just need this for vars with unlimited dims | ||
| * i.e., spio_var_has_unlimited_dims(file, varid, ierr) == true | ||
| * However to be consistent with PIOc_def_var() setting it for all vars | ||
| */ | ||
| ierr = nc_var_par_access(file->fh, varid, NC_COLLECTIVE); | ||
| } | ||
| #endif /* _NETCDF4 */ |
Variables with unlimited dimensions require collective puts with NetCDF
Fixes #711