@@ -2969,10 +2969,16 @@ def __init__(self, sorting_analyzer):
29692969 self .data = dict ()
29702970
29712971 def __del__ (self ):
2972+ # Ensure open memmap file handles are released when the extension is garbage collected.
2973+ # Best-effort: __del__ must never raise.
2974+ self ._release_data_file_handles ()
2975+
2976+ def _release_data_file_handles (self ):
29722977 # Close any open memmap file handles held in `data` (e.g. when an extension gathers or
29732978 # loads its data as a memmap). On Windows an open memmap prevents deleting the underlying
2974- # file, so releasing the handles here allows the extension folder to be removed when the
2975- # extension is dropped (e.g. on recompute). Best-effort: __del__ must never raise.
2979+ # file, so releasing the handles allows the extension folder to be removed (e.g. on
2980+ # recompute). This closes the file handle but leaves the (now unusable) array object and any
2981+ # non-memmap data in `data` untouched.
29762982 data = self .__dict__ .get ("data" , None )
29772983 if not data :
29782984 return
@@ -3478,12 +3484,14 @@ def _reset_extension_folder(self):
34783484 Delete the extension in a folder (binary or zarr) and create an empty one.
34793485 """
34803486 if self .format == "binary_folder" :
3481- # Drop the analyzer's reference to a previously computed extension of the same name so
3482- # it can be garbage collected. Its __del__ releases any open file handles (e.g. a memmap
3483- # kept in `data` when gathering directly to npy) which, on Windows, would otherwise
3484- # prevent deleting the folder below.
3485- old_extension = self .sorting_analyzer .extensions .pop (self .extension_name , None )
3486- del old_extension
3487+ # Release open file handles (e.g. a memmap kept in `data` when gathering/extracting
3488+ # directly to npy) of a previously computed extension of the same name. On Windows an
3489+ # open memmap would otherwise prevent deleting the folder below. We deliberately do NOT
3490+ # remove it from `self.sorting_analyzer.extensions` nor clear its data: some extensions
3491+ # (e.g. metrics) read the previous extension back during their computation.
3492+ old_extension = self .sorting_analyzer .extensions .get (self .extension_name , None )
3493+ if old_extension is not None and old_extension is not self :
3494+ old_extension ._release_data_file_handles ()
34873495
34883496 extension_folder = self ._get_binary_extension_folder ()
34893497 if extension_folder .is_dir ():
0 commit comments