Skip to content

Fix issues with writing vars with unlimited dimensions using NetCDF/HDF5 - #710

Draft
jayeshkrishna wants to merge 4 commits into
masterfrom
jayeshkrishna/nc_put_time_vars_and_chunking
Draft

Fix issues with writing vars with unlimited dimensions using NetCDF/HDF5#710
jayeshkrishna wants to merge 4 commits into
masterfrom
jayeshkrishna/nc_put_time_vars_and_chunking

Conversation

@jayeshkrishna

@jayeshkrishna jayeshkrishna commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Variables with unlimited dimensions require collective puts with NetCDF

  • Use COLLECTIVE for all variables using NetCDF4 parallel I/O
  • Also adding generic cleanup calls for HDF5 and NetCDF
  • Explicitly close open files and I/O decomps in finalize (to free up resources)

Fixes #711

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)
@jayeshkrishna jayeshkrishna self-assigned this Aug 27, 2026
@jayeshkrishna jayeshkrishna added bug Next Release Enhancements slated for the upcoming (next) release HDF5 Issues/PRs related to HDF5 NetCDF Issues/PRs related to NetCDF labels Aug 27, 2026
@jayeshkrishna

jayeshkrishna commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Since I/O decomp freeing/finalizing requires MPI calls we need to explicitly clean it up in finalize - so that it happens before MPI finalize (Also see #698 where logic to automatically cleanup I/O decomps was added). This change fixes #711

@jayeshkrishna

Copy link
Copy Markdown
Contributor Author

NetCDF and HDF5 cleanup calls,

  • nc_finalize() was introduced in NetCDF 4.4.0 (SCORPIO requires NetCDF >= 4.4.0)
  • h5close() is available on all recent versions

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 when nc_open_par() was never called (e.g., empty NCZarr filename) or failed, and NC_GLOBAL is not a valid varid for nc_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.

Comment thread src/clib/core/pioc_support.cpp Outdated
Comment on lines +5103 to +5114
/* 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());
}
Comment on lines +225 to 234
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);
}
Comment thread src/clib/core/pioc.cpp
Comment on lines +1758 to +1760
/* 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)
@jayeshkrishna
jayeshkrishna force-pushed the jayeshkrishna/nc_put_time_vars_and_chunking branch 2 times, most recently from d3957eb to f2984c8 Compare August 27, 2026 18:12
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).
@jayeshkrishna
jayeshkrishna force-pushed the jayeshkrishna/nc_put_time_vars_and_chunking branch from f2984c8 to d0eccce Compare August 27, 2026 18:28
@jayeshkrishna
jayeshkrishna requested a lite review from Copilot August 28, 2026 02:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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){

Comment thread src/clib/core/pioc.cpp
Comment on lines +1756 to 1767
#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
Comment on lines +3347 to +3356
#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 */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug HDF5 Issues/PRs related to HDF5 NetCDF Issues/PRs related to NetCDF Next Release Enhancements slated for the upcoming (next) release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E3SM runs with latest master fails due to MPI calls after finalize

2 participants