diff --git a/java/src-jni/jni/h5util.c b/java/src-jni/jni/h5util.c index 26781471477..1e4b2ec5c3e 100644 --- a/java/src-jni/jni/h5util.c +++ b/java/src-jni/jni/h5util.c @@ -2075,8 +2075,8 @@ h5str_detect_vlen_str(hid_t tid) goto done; } /* end if */ ret = h5str_detect_vlen_str(btid); + H5Tclose(btid); if ((ret == 1) || (ret < 0)) { - H5Tclose(btid); goto done; } /* end if */ } /* end if */ diff --git a/java/src-jni/test/TestH5D.java b/java/src-jni/test/TestH5D.java index 18e33fa6600..e49582c0bc3 100644 --- a/java/src-jni/test/TestH5D.java +++ b/java/src-jni/test/TestH5D.java @@ -1624,6 +1624,77 @@ public class TestH5D { } } + /* + * Reading or writing a dataset whose memory type is an array of a fixed base type + * must not accumulate open datatype IDs. Write and read such a dataset repeatedly + * and verify the library's open-datatype count is unchanged. + */ + @Test + public void testH5DArray_datatype_ids_stable() throws Throwable + { + String dset_name = "ArrayIntData"; + long dtype_id = HDF5Constants.H5I_INVALID_HID; + long dspace_id = HDF5Constants.H5I_INVALID_HID; + long dset_id = HDF5Constants.H5I_INVALID_HID; + long[] arr_dims = {3}; + final int NITER = 50; + int[] wbuf = {10, 20, 30}; + int[] rbuf = new int[3]; + + try { + dtype_id = H5.H5Tarray_create(HDF5Constants.H5T_NATIVE_INT, 1, arr_dims); + assertTrue("testH5DArray_datatype_ids_stable.H5Tarray_create: ", dtype_id >= 0); + dspace_id = H5.H5Screate(HDF5Constants.H5S_SCALAR); + assertTrue("testH5DArray_datatype_ids_stable.H5Screate: ", dspace_id >= 0); + dset_id = H5.H5Dcreate(H5fid, dset_name, dtype_id, dspace_id, HDF5Constants.H5P_DEFAULT, + HDF5Constants.H5P_DEFAULT, HDF5Constants.H5P_DEFAULT); + assertTrue("testH5DArray_datatype_ids_stable.H5Dcreate: ", dset_id >= 0); + + /* + * The leaked IDs are transient datatypes (from H5Tget_super), which are not + * attached to any file, so they are only visible through the H5F_OBJ_ALL path + * that walks the global datatype ID list. Scoping the count to a single file id + * would miss them entirely. + */ + long before = H5.H5Fget_obj_count(HDF5Constants.H5F_OBJ_ALL, HDF5Constants.H5F_OBJ_DATATYPE); + assertTrue("testH5DArray_datatype_ids_stable.H5Fget_obj_count(before): ", before >= 0); + + for (int i = 0; i < NITER; i++) { + H5.H5Dwrite(dset_id, dtype_id, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, wbuf); + H5.H5Dread(dset_id, dtype_id, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, rbuf); + } + + long after = H5.H5Fget_obj_count(HDF5Constants.H5F_OBJ_ALL, HDF5Constants.H5F_OBJ_DATATYPE); + assertTrue("testH5DArray_datatype_ids_stable.H5Fget_obj_count(after): ", after >= 0); + + assertEquals("testH5DArray_datatype_ids_stable: open datatype count changed after " + NITER + + " write/read cycles on an array datatype", + before, after); + } + finally { + if (dset_id >= 0) + try { + H5.H5Dclose(dset_id); + } + catch (Exception ex) { + } + if (dtype_id >= 0) + try { + H5.H5Tclose(dtype_id); + } + catch (Exception ex) { + } + if (dspace_id >= 0) + try { + H5.H5Sclose(dspace_id); + } + catch (Exception ex) { + } + } + } + @Test public void testH5DArray_string_buffer() throws Throwable { diff --git a/java/src-jni/test/testfiles/JUnit-TestH5D.txt b/java/src-jni/test/testfiles/JUnit-TestH5D.txt index 36ce3bd5648..130a72107ba 100644 --- a/java/src-jni/test/testfiles/JUnit-TestH5D.txt +++ b/java/src-jni/test/testfiles/JUnit-TestH5D.txt @@ -17,6 +17,7 @@ JUnit version 4.13.2 .testH5DwriteVL_compound_wrong_member_type .testH5Dget_access_plist .testH5Dread_vlen_of_nested_compound +.testH5DArray_datatype_ids_stable .testH5DArray_string_buffer_flat_StringArray .testH5Dget_space_closed .testH5DArray_string_buffer_flat_StringArray_write @@ -34,5 +35,5 @@ JUnit version 4.13.2 Time: XXXX -OK (32 tests) +OK (33 tests) diff --git a/release_docs/CHANGELOG.md b/release_docs/CHANGELOG.md index 45cbf1f4ac7..ac8ce4e95c0 100644 --- a/release_docs/CHANGELOG.md +++ b/release_docs/CHANGELOG.md @@ -213,6 +213,17 @@ The `h5repack` tool now obtains its default low and high library version bounds ## Java Library +### Fixed a datatype ID leak when reading or writing array/vlen datatypes + + The JNI wrappers for `H5Dread`, `H5Dwrite`, `H5Aread`, and `H5Awrite` inspect the + memory datatype using an internal helper (`h5str_detect_vlen_str()`). For an array + or variable-length datatype whose base type is not a variable-length string, the + helper opened the base type with `H5Tget_super()` but failed to close it if + no variable-length string was found, leaking one datatype ID per + read/write call. The base type ID is now closed on all paths, so Java applications + that repeatedly access datasets or attributes with these datatypes no longer leak + HDF5 datatype IDs. + ## Configuration ## Tools