@@ -149,6 +149,26 @@ NULL
149149 dst_parent [[dst_name ]]
150150}
151151
152+ # Object reference to `obj` (an H5D / H5Group of `file`) for a
153+ # H5T_STD_REF_OBJ attribute. hdf5r's own `create_reference()` opens a
154+ # temporary file handle whose garbage-collection finalizer corrupts hdf5r's
155+ # reference bookkeeping ("r_count can never be more than 1 larger than
156+ # h5_count" after enough references, at GC-dependent moments). A classic
157+ # object reference is the object's header address, so it is assembled here
158+ # from `obj_info()` and attached to the file handle we already hold; the
159+ # caller closes the returned object right after writing the attribute.
160+ .h5w_object_reference <- function (file , obj ) {
161+ addr <- as.numeric(obj $ obj_info()$ addr )
162+ bytes <- integer(8 )
163+ for (i in seq_len(8 )) {
164+ bytes [i ] <- addr %% 256
165+ addr <- addr %/% 256
166+ }
167+ ref <- hdf5r :: H5R_OBJECT $ new(1 , file )
168+ ref $ ref <- as.raw(bytes )
169+ ref
170+ }
171+
152172.h5_is_scalar <- function (dset ) {
153173 isTRUE(tryCatch(dset $ get_space()$ get_simple_extent_type() == " H5S_SCALAR" ,
154174 error = function (e ) FALSE )) ||
@@ -208,12 +228,15 @@ NULL
208228 ! enc %in% .H5AD_MATRIX_ENCODINGS ) {
209229 walk(obj , depth + 1L )
210230 }
231+ .h5_close_quietly(obj )
211232 }
212233 }
213234 walk(h5 , 0L )
214235 root_enc <- .h5ad_encoding(h5 )
215- obs_is_group <- h5 $ exists(" obs" ) && inherits(h5 [[" obs" ]], " H5Group" )
216- obs_df_version <- if (obs_is_group ) as.character(.h5ad_attr(h5 [[" obs" ]], " encoding-version" , " " )) else " "
236+ obs_is_group <- h5 $ exists(" obs" ) && .h5_with_child(h5 , " obs" , function (o ) inherits(o , " H5Group" ))
237+ obs_df_version <- if (obs_is_group ) {
238+ as.character(.h5_with_child(h5 , " obs" , function (o ) .h5ad_attr(o , " encoding-version" , " " )))
239+ } else " "
217240 layout <- if (compound ) {
218241 " compound"
219242 } else if (legacy_cats || identical(obs_df_version , " 0.1.0" ) ||
222245 } else {
223246 " encoded"
224247 }
225- has_manifest <- h5 $ exists(" uns" ) && h5 [[ " uns" ]] $ exists(.H5AD_MANIFEST )
248+ has_manifest <- h5 $ exists(" uns" ) && .h5_with_child( h5 , " uns" , function ( u ) u $ exists(.H5AD_MANIFEST ) )
226249 has_ns <- " nullable-string-array" %in% encs
227250 has_nb <- any(c(" nullable-integer" , " nullable-boolean" ) %in% encs )
228251 has_null <- " null" %in% encs
@@ -448,18 +471,22 @@ print.h5ad_layout <- function(x, ...) {
448471# recorded in the manifest so an upgrade can restore it.
449472.h5ad_write_column_legacy <- function (g , col , x , src_enc , path , ctx , na_note = " " ) {
450473 write_cat <- function (f , note = " " ) {
451- if (! g $ exists(" __categories" )) g $ create_group(" __categories" )
474+ if (! g $ exists(" __categories" )) .h5_close_quietly( g $ create_group(" __categories" ) )
452475 cg <- g [[" __categories" ]]
476+ on.exit(.h5_close_quietly(cg ), add = TRUE )
453477 lv <- levels(f )
454478 cd <- .h5w_dataset(cg , col , lv )
479+ on.exit(.h5_close_quietly(cd ), add = TRUE )
455480 .h5w_attr(cd , " ordered" , is.ordered(f ))
456481 codes <- as.integer(f ) - 1L
457482 codes [is.na(codes )] <- - 1L
458483 d <- .h5w_dataset(g , col , codes , dtype = .h5w_codes_dtype(length(lv )))
459- ref <- cg $ create_reference(col )
484+ on.exit(.h5_close_quietly(d ), add = TRUE )
485+ ref <- .h5w_object_reference(ctx $ dst_file , cd )
486+ on.exit(.h5_close_quietly(ref ), add = TRUE )
460487 d $ create_attr(attr_name = " categories" , robj = ref ,
461488 dtype = hdf5r :: h5types $ H5T_STD_REF_OBJ , space = .h5w_scalar_space())
462- invisible (d )
489+ invisible (NULL )
463490 }
464491 if (is.factor(x )) {
465492 if (src_enc %in% c(" nullable-string-array" , " string-array" )) {
@@ -501,6 +528,10 @@ print.h5ad_layout <- function(x, ...) {
501528# ' @noRd
502529.h5ad_write_dataframe <- function (src , name , dst_parent , path , ctx ) {
503530 obj <- src [[name ]]
531+ on.exit(.h5_close_quietly(obj ), add = TRUE )
532+ child_attr <- function (cn , attr , default ) {
533+ .h5_with_child(obj , cn , function (o ) as.character(.h5ad_attr(o , attr , default )))
534+ }
504535 # ---- decode
505536 if (inherits(obj , " H5D" )) {
506537 # anndata < 0.7: single compound dataset. Categories (if any) live in
@@ -571,12 +602,12 @@ print.h5ad_layout <- function(x, ...) {
571602 }
572603 na_value <- rec $ note %|| % " "
573604 if (! nzchar(na_value ) && inherits(obj , " H5Group" ) && obj $ exists(cn )) {
574- na_value <- as.character(.h5ad_attr( obj [[ cn ]] , " na-value" , " NaN" ) )
605+ na_value <- child_attr( cn , " na-value" , " NaN" )
575606 }
576607 .h5ad_write_column_encoded(g , cn , v , want , ctx , na_value = na_value )
577608 } else {
578609 na_note <- if (inherits(obj , " H5Group" ) && obj $ exists(cn )) {
579- as.character(.h5ad_attr( obj [[ cn ]] , " na-value" , " " ) )
610+ child_attr( cn , " na-value" , " " )
580611 } else " "
581612 .h5ad_write_column_legacy(g , cn , v , src_encs [[cn ]], cpath , ctx , na_note = na_note )
582613 }
@@ -590,7 +621,7 @@ print.h5ad_layout <- function(x, ...) {
590621 if (want == " nullable-string-array" && ! identical(ctx $ strings , " categorical" )) {
591622 na_value <- irec $ note %|| % " "
592623 if (! nzchar(na_value ) && inherits(obj , " H5Group" ) && obj $ exists(index_name )) {
593- na_value <- as.character(.h5ad_attr( obj [[ index_name ]] , " na-value" , " NaN" ) )
624+ na_value <- child_attr( index_name , " na-value" , " NaN" )
594625 }
595626 .h5ad_write_nullable_encoded(g , index_name , idx , " nullable-string-array" ,
596627 na_value = na_value )
@@ -602,21 +633,23 @@ print.h5ad_layout <- function(x, ...) {
602633 } else {
603634 if (idx_enc == " nullable-string-array" ) {
604635 .ctx_record(ctx , ipath , idx_enc , " 0.1.0" ,
605- note = as.character(.h5ad_attr( obj [[ index_name ]] , " na-value" , " " ) ))
636+ note = child_attr( index_name , " na-value" , " " ))
606637 }
607638 if (anyNA(idx )) idx [is.na(idx )] <- " "
608639 .h5w_dataset(g , index_name , idx )
609640 .h5w_encoding(g , " dataframe" , " 0.1.0" )
610641 }
611642 .h5w_attr(g , " _index" , index_name )
612643 .h5w_attr(g , " column-order" , keep , force_array = TRUE )
613- invisible (g )
644+ .h5_close_quietly(g )
645+ invisible (NULL )
614646}
615647
616648# ---- generic element rewriter -----------------------------------------------
617649
618650.h5ad_rewrite_dataset <- function (src , name , dst , path , ctx ) {
619651 obj <- src [[name ]]
652+ on.exit(.h5_close_quietly(obj ), add = TRUE )
620653 enc <- .h5ad_encoding(obj )
621654 if (identical(enc , " null" ) || .h5_is_null_space(obj )) {
622655 # anndata < 0.9 has no `null` encoding: drop it for the legacy layout and
@@ -627,9 +660,11 @@ print.h5ad_layout <- function(x, ...) {
627660 }
628661 d <- .h5w_copy(src , name , dst )
629662 .h5w_encoding(d , " null" , " 0.1.0" )
630- return (invisible (d ))
663+ .h5_close_quietly(d )
664+ return (invisible (NULL ))
631665 }
632666 d <- .h5w_copy(src , name , dst )
667+ on.exit(.h5_close_quietly(d ), add = TRUE )
633668 if (ctx $ target == " encoded" ) {
634669 if (! nzchar(enc ) || enc == " array" || enc == " string-array" ||
635670 enc == " numeric-scalar" || enc == " string" || enc == " bytes" ) {
@@ -644,11 +679,12 @@ print.h5ad_layout <- function(x, ...) {
644679 } else {
645680 .h5w_strip_encoding(d )
646681 }
647- invisible (d )
682+ invisible (NULL )
648683}
649684
650685.h5ad_rewrite_group <- function (src , name , dst , path , ctx , role = " generic" ) {
651686 obj <- src [[name ]]
687+ on.exit(.h5_close_quietly(obj ), add = TRUE )
652688 enc <- .h5ad_encoding(obj )
653689 if (identical(name , .H5AD_MANIFEST ) && role == " uns" ) {
654690 return (invisible (NULL )) # never carry a stale manifest forward
@@ -657,20 +693,22 @@ print.h5ad_layout <- function(x, ...) {
657693 if (enc %in% .H5AD_MATRIX_ENCODINGS ||
658694 (obj $ exists(" data" ) && obj $ exists(" indices" ) && obj $ exists(" indptr" ))) {
659695 g <- .h5w_copy(src , name , dst )
696+ on.exit(.h5_close_quietly(g ), add = TRUE )
660697 if (! nzchar(enc )) enc <- " csr_matrix"
661698 .h5w_encoding(g , enc , " 0.1.0" )
662699 if (! isTRUE(g $ attr_exists(" shape" )) && ctx $ target == " encoded" ) {
663700 # shape is required by anndata >= 0.8; derive it from indptr/indices
664- indptr <- obj [[ " indptr" ]] $ read()
665- indices <- obj [[ " indices" ]] $ read()
701+ indptr <- .h5_with_child( obj , " indptr" , function ( d ) d $ read() )
702+ indices <- .h5_with_child( obj , " indices" , function ( d ) d $ read() )
666703 major <- length(indptr ) - 1L
667704 minor <- if (length(indices )) max(indices ) + 1L else 0L
668705 .h5w_attr(g , " shape" , if (enc == " csc_matrix" ) c(minor , major ) else c(major , minor ))
669706 }
670- return (invisible (g ))
707+ return (invisible (NULL ))
671708 }
672709 if (enc %in% .H5AD_OPAQUE_ENCODINGS ) {
673- return (invisible (.h5w_copy(src , name , dst )))
710+ .h5_close_quietly(.h5w_copy(src , name , dst ))
711+ return (invisible (NULL ))
674712 }
675713 # Dataframes
676714 is_df <- identical(enc , " dataframe" ) || role %in% c(" obs" , " var" ) ||
@@ -707,13 +745,14 @@ print.h5ad_layout <- function(x, ...) {
707745 }
708746 # Plain (dict) group: recurse
709747 g <- dst $ create_group(name )
748+ on.exit(.h5_close_quietly(g ), add = TRUE )
710749 child_role <- if (role == " raw" ) " raw-child" else if (role == " uns" ) " uns" else " generic"
711750 for (child in names(obj )) {
712751 cpath <- paste0(path , " /" , child )
713- cobj <- obj [[ child ]]
752+ child_is_dataset <- .h5_with_child( obj , child , function ( o ) inherits( o , " H5D " ))
714753 r <- child_role
715754 if (role == " raw" && child == " var" ) r <- " var"
716- if (inherits( cobj , " H5D " ) ) {
755+ if (child_is_dataset ) {
717756 .h5ad_rewrite_dataset(obj , child , g , cpath , ctx )
718757 } else {
719758 .h5ad_rewrite_group(obj , child , g , cpath , ctx , role = r )
@@ -724,7 +763,7 @@ print.h5ad_layout <- function(x, ...) {
724763 } else {
725764 .h5w_strip_encoding(g )
726765 }
727- invisible (g )
766+ invisible (NULL )
728767}
729768
730769# ' Rewrite an h5ad file into another on-disk layout
@@ -775,6 +814,7 @@ print.h5ad_layout <- function(x, ...) {
775814 ctx $ source_layout <- info $ layout
776815 ctx $ writer <- pkg
777816 ctx $ writer_version <- tryCatch(as.character(utils :: packageVersion(pkg )), error = function (e ) " " )
817+ ctx $ dst_file <- dst
778818 ctx $ src_uns_exists <- function (key ) src $ exists(" uns" ) && src [[" uns" ]]$ exists(key )
779819 ctx $ src_uns_read <- function (key ) src [[" uns" ]][[key ]]$ read()
780820
@@ -787,7 +827,7 @@ print.h5ad_layout <- function(x, ...) {
787827 role <- roles [nm ] %|| % " generic"
788828 if (is.na(role )) role <- " generic"
789829 if (verbose ) message(" " , nm )
790- if (inherits (src [[ nm ]], " H5D" )) {
830+ if (.h5_with_child (src , nm , function ( o ) inherits( o , " H5D" ) )) {
791831 if (nm %in% c(" obs" , " var" )) {
792832 .h5ad_write_dataframe(src , nm , dst , nm , ctx )
793833 } else {
0 commit comments