From c7eb357e288e5fc8f99e3cc2ec37892096cd0932 Mon Sep 17 00:00:00 2001 From: mmccrackan Date: Fri, 3 Jul 2026 14:01:49 -0400 Subject: [PATCH 1/8] add option to override h5 ctx temp file creation --- sotodlib/core/util.py | 19 +++++--- sotodlib/preprocess/preprocess_util.py | 44 ++++++++++++++----- .../multilayer_preprocess_tod.py | 34 ++++++++++---- sotodlib/site_pipeline/preprocess_tod.py | 26 ++++++++--- 4 files changed, 91 insertions(+), 32 deletions(-) diff --git a/sotodlib/core/util.py b/sotodlib/core/util.py index a5985c48d..3665db56e 100644 --- a/sotodlib/core/util.py +++ b/sotodlib/core/util.py @@ -53,19 +53,22 @@ class H5ContextManager: Number of times to retry opening the file if it is locked. delay : int or float, optional Delay in seconds between retries. + override_temp : bool + Override creation of temporary file and just use original file. **kwargs : Additional keyword arguments passed to `h5py.File`. """ temp_suffix = ".temporary" - def __init__(self, filename, mode="r", max_attempts=3, delay=5, **kwargs): + def __init__(self, filename, mode="r", max_attempts=3, delay=5, override_temp=False, **kwargs): self.filename = filename self.kwargs = kwargs self.max_attempts = max_attempts self.delay = delay self.f = None self.mode = mode + self.override_temp = override_temp self._check_file_for_mode() if self.max_attempts <= 0: raise RuntimeError("max_attempts should be at least one") @@ -95,12 +98,18 @@ def open(self): self.f = h5py.File(self.filename, mode=self.mode, **self.kwargs) elif self.mode == "a" or self.mode == "r+": # Copy the existing file to a temp location for modification - if os.path.isfile(self.filename): - shutil.copy(self.filename, temp_path) - self.f = h5py.File(temp_path, mode=self.mode, **self.kwargs) + if not self.override_temp: + if os.path.isfile(self.filename): + shutil.copy(self.filename, temp_path) + self.f = h5py.File(temp_path, mode=self.mode, **self.kwargs) + else: + self.f = h5py.File(self.filename, mode=self.mode, **self.kwargs) else: # Writing and truncating - self.f = h5py.File(temp_path, mode=self.mode, **self.kwargs) + if not self.override_temp: + self.f = h5py.File(temp_path, mode=self.mode, **self.kwargs) + else: + self.f = h5py.File(self.filename, mode=self.mode, **self.kwargs) return self.f except BlockingIOError as e: # If the file is locked, retry opening it after a delay diff --git a/sotodlib/preprocess/preprocess_util.py b/sotodlib/preprocess/preprocess_util.py index 88127ad3c..98b91637f 100644 --- a/sotodlib/preprocess/preprocess_util.py +++ b/sotodlib/preprocess/preprocess_util.py @@ -1019,7 +1019,7 @@ def find_db(obs_id, configs, dets, context=None, logger=None): return dbexist -def cleanup_archive(configs, logger=None): +def cleanup_archive(configs, logger=None, make_temp_h5=False): """This function finds the final preprocess archive file and deletes any datasets that are not found in the preprocess database. This helps avoid cases where the database writing was interrupted in a previous run. @@ -1030,6 +1030,9 @@ def cleanup_archive(configs, logger=None): Filepath or dictionary containing the preprocess configuration file. logger : PythonLogger Optional. Logger object or None will generate a new one. + make_temp_h5 : bool + Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. + For use on site-computing. """ if type(configs) == str: @@ -1056,7 +1059,7 @@ def cleanup_archive(configs, logger=None): key=lambda t: t[0])[1] db_datasets = [d['dataset'] for d in db.inspect()] - with H5ContextManager(latest_file, mode="r+") as f: + with H5ContextManager(latest_file, mode="r+", make_temp_h5=make_temp_h5) as f: keys = list(f.keys()) for key in keys: if key not in db_datasets: @@ -1126,7 +1129,7 @@ def get_preproc_group_out_dict(obs_id, configs, dets, context=None, subdir='temp def save_group_and_cleanup(obs_id, configs, context=None, subdir='temp', - logger=None, remove=False): + logger=None, remove=False, make_temp_h5=False): """This function checks if any temporary files exist from a preprocessing run and will either add them to the config policy file and create an entry in the manifest db by calling ``cleanup_mandb``. If the file exists but @@ -1150,6 +1153,9 @@ def save_group_and_cleanup(obs_id, configs, context=None, subdir='temp', remove : bool Optional. Default is False. Whether to remove a file if found. Used when ``overwrite`` is True in driving functions. + make_temp_h5 : bool + Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. + For use on site-computing. Returns ------- @@ -1184,7 +1190,7 @@ def save_group_and_cleanup(obs_id, configs, context=None, subdir='temp', try: if not remove: cleanup_mandb(outputs_grp, (obs_id, g), - (None, None, None), configs, logger) + (None, None, None), configs, logger, make_temp_h5=make_temp_h5) else: # if we're overwriting, remove file so it will re-run os.remove(outputs_grp['temp_file']) @@ -1208,7 +1214,7 @@ def save_group_and_cleanup(obs_id, configs, context=None, subdir='temp', def cleanup_obs(obs_id, policy_dir, errlog, configs, context=None, - subdir='temp', remove=False): + subdir='temp', remove=False, make_temp_h5=False): """For a given obs id, this function will search the policy_dir directory if it exists for any files with that obsnum in their filename. If any are found, it will run save_group_and_cleanup for that obs id. @@ -1230,6 +1236,9 @@ def cleanup_obs(obs_id, policy_dir, errlog, configs, context=None, remove : bool Optional. Default is False. Whether to remove a file if found. Used when ``overwrite`` is True in driving functions. + make_temp_h5 : bool + Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. + For use on site-computing. """ if os.path.exists(policy_dir): @@ -1241,7 +1250,8 @@ def cleanup_obs(obs_id, policy_dir, errlog, configs, context=None, if found: errors = save_group_and_cleanup(obs_id, configs, context, - subdir=subdir, remove=remove) + subdir=subdir, remove=remove, + make_temp_h5=make_temp_h5) if errors[0] is not None: with open(errlog, 'a') as f: @@ -1252,7 +1262,8 @@ def cleanup_obs(obs_id, policy_dir, errlog, configs, context=None, def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, logger=None, overwrite=False, save_archive=False, save_proc_aman=True, compress=False, - skip_missing=False, ignore_cfg_check=False): + skip_missing=False, ignore_cfg_check=False, + make_temp_h5=False): """ This function is expected to receive a single obs_id, and dets dictionary. The dets dictionary must match the grouping specified in the preprocess @@ -1302,6 +1313,9 @@ def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, ignore_cfg_check : bool If True, do not attempt to validate that configs_init is the same as the config used to create the existing init db when running ``multilayer_load_and_preprocess``. + make_temp_h5 : bool + Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. + For use on site-computing. Returns ------- @@ -1470,7 +1484,8 @@ def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, if save_archive: logger.info(f"Adding result to init db for {obs_id}: {group}") cleanup_mandb(out_dict_init, (obs_id, group), (None, None, None), - configs_init, logger=logger, overwrite=overwrite) + configs_init, logger=logger, overwrite=overwrite, + make_temp_h5=make_temp_h5) # Make init plots if make_lmsi_init: new_plots = os.path.join(configs_init["plot_dir"], @@ -1539,7 +1554,8 @@ def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, if save_archive: logger.info(f"Adding result to proc db for {obs_id}: {group}") cleanup_mandb(out_dict_proc, (obs_id, group), (None, None, None), - configs_proc, logger=logger, overwrite=overwrite) + configs_proc, logger=logger, overwrite=overwrite, + make_temp_h5=make_temp_h5) if 'valid_data' in aman.preprocess: aman.preprocess.move('valid_data', None) aman.preprocess.merge(proc_aman) @@ -1562,7 +1578,8 @@ def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, return aman, out_dict_init, out_dict_proc, (None, None, None) -def cleanup_mandb(out_dict, out_meta, errors, configs, logger=None, overwrite=False, db_manager=None): +def cleanup_mandb(out_dict, out_meta, errors, configs, logger=None, overwrite=False, + db_manager=None, make_temp_h5=False): """Function to update the manifest db when data is collected from the ``preproc_or_load_group`` function. If used in an mpi framework this function is expected to be run from rank 0 after a ``comm.gather``. @@ -1601,6 +1618,9 @@ def cleanup_mandb(out_dict, out_meta, errors, configs, logger=None, overwrite=Fa db_manager : DbBatchManager, optional External database batch manager for optimized operations. If provided, uses the manager instead of creating individual connections. + make_temp_h5 : bool + Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. + For use on site-computing. """ if logger is None: @@ -1628,8 +1648,8 @@ def cleanup_mandb(out_dict, out_meta, errors, configs, logger=None, overwrite=Fa src_file = out_dict['temp_file'] - with H5ContextManager(dest_file, mode='a') as f_dest: - with H5ContextManager(src_file, mode='r') as f_src: + with H5ContextManager(dest_file, mode='a', make_temp_h5=make_temp_h5) as f_dest: + with H5ContextManager(src_file, mode='r', make_temp_h5=make_temp_h5) as f_src: for dts in f_src.keys(): # If the dataset or group already exists, delete it to overwrite if overwrite and dts in f_dest: diff --git a/sotodlib/site_pipeline/multilayer_preprocess_tod.py b/sotodlib/site_pipeline/multilayer_preprocess_tod.py index ff82a2e86..0b36e703a 100644 --- a/sotodlib/site_pipeline/multilayer_preprocess_tod.py +++ b/sotodlib/site_pipeline/multilayer_preprocess_tod.py @@ -32,7 +32,8 @@ def multilayer_preprocess_tod(obs_id: str, group: list, verbosity: int = 0, compress: bool = False, - overwrite: bool = False): + overwrite: bool = False, + make_temp_h5: bool = False): """Meant to be run as part of a batched script, this function calls the preprocessing pipeline a specific Observation ID and group combination and saves the results in the ManifestDb specified in the configs. @@ -55,6 +56,9 @@ def multilayer_preprocess_tod(obs_id: str, Whether or not to compress the preprocessing h5 files. overwrite : bool If True, overwrite contents of temporary h5 files. + make_temp_h5 : bool + Copy h5 archives to temporary files to prevent corruption from metadata sync. + For use on site-computing. Returns ------- @@ -85,6 +89,7 @@ def multilayer_preprocess_tod(obs_id: str, overwrite=overwrite, save_archive=False, compress=compress, + make_temp_h5=make_temp_h5, ) return out_dict_init, out_dict_proc, errors @@ -191,7 +196,8 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], compress: bool = False, run_from_jobdb: bool = False, raise_error: bool = False, - pb_path: Optional[str] = None): + pb_path: Optional[str] = None, + make_temp_h5: Optional[bool] = False): init_temp_subdir = "temp" proc_temp_subdir = "temp_proc" @@ -259,13 +265,15 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], for obs_id in obs_list: pp_util.cleanup_obs(obs_id, init_policy_dir, errlog, configs_init, - context_init, subdir=init_temp_subdir, remove=overwrite) + context_init, subdir=init_temp_subdir, remove=overwrite, + make_temp_h5=make_temp_h5) pp_util.cleanup_obs(obs_id, proc_policy_dir, errlog, configs_proc, - context_proc, subdir=proc_temp_subdir, remove=overwrite) + context_proc, subdir=proc_temp_subdir, remove=overwrite, + make_temp_h5=make_temp_h5) # remove datasets from final archive file not found in init db for config in (configs_init, configs_proc): - pp_util.cleanup_archive(config, logger) + pp_util.cleanup_archive(config, logger, make_temp_h5) run_list = [] @@ -435,11 +443,11 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], logger.info(f"Adding future result to init db for {obs_id}: {group}") pp_util.cleanup_mandb(out_dict_init, out_meta, errors, configs_init, logger, overwrite, - db_manager=db_mgr_init) + db_manager=db_mgr_init, make_temp_h5=make_temp_h5) logger.info(f"Adding future result to proc db for {obs_id}: {group}") pp_util.cleanup_mandb(out_dict_proc, out_meta, errors, configs_proc, logger, overwrite, - db_manager=db_mgr_proc) + db_manager=db_mgr_proc, make_temp_h5=make_temp_h5) # update jobdb if jobdb_path is not None: @@ -559,6 +567,12 @@ def get_parser(parser=None): type=str, default=None ) + parser.add_argument( + '--make-temp', + help="Copy h5 archives to temporary files to prevent file corruption (for site-computing).", + type=bool, + default=True + ) return parser @@ -577,7 +591,8 @@ def main(configs_init: str, nproc: int = 4, run_from_jobdb: bool = False, raise_error: bool = False, - pb_path: Optional[str] = None): + pb_path: Optional[str] = None, + make_temp_h5: Optional[bool] = False): rank, executor, as_completed_callable = get_exec_env(nproc) if rank == 0: @@ -598,7 +613,8 @@ def main(configs_init: str, compress=compress, run_from_jobdb=run_from_jobdb, raise_error=raise_error, - pb_path=pb_path) + pb_path=pb_path, + make_temp_h5=make_temp_h5) if __name__ == '__main__': diff --git a/sotodlib/site_pipeline/preprocess_tod.py b/sotodlib/site_pipeline/preprocess_tod.py index 111342894..0e92ebd41 100644 --- a/sotodlib/site_pipeline/preprocess_tod.py +++ b/sotodlib/site_pipeline/preprocess_tod.py @@ -95,6 +95,7 @@ def preprocess_tod(configs: Union[str, dict], verbosity: int = 0, compress: bool = False, overwrite: bool = False, + make_temp_h5: bool = False ): """Meant to be run as part of a batched script, this function calls the preprocessing pipeline a specific Observation ID and group combination @@ -116,6 +117,9 @@ def preprocess_tod(configs: Union[str, dict], Whether or not to compress the preprocessing h5 files. overwrite : bool If True, overwrite contents of temporary h5 files. + make_temp_h5 : bool + Copy h5 archives to temporary files to prevent corruption from metadata sync. + For use on site-computing. Returns ------- @@ -141,6 +145,7 @@ def preprocess_tod(configs: Union[str, dict], overwrite=overwrite, save_archive=False, compress=compress, + make_temp_h5=make_temp_h5, ) return out_dict, errors @@ -162,7 +167,8 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], compress: bool = False, run_from_jobdb: bool = False, raise_error: bool = False, - pb_path: Optional[str] = None): + pb_path: Optional[str] = None, + make_temp_h5: Optional[bool] = False): temp_subdir = "temp" @@ -209,10 +215,10 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], ) for obs_id in obs_list: pp_util.cleanup_obs(obs_id, policy_dir, errlog, configs, context, - subdir=temp_subdir, remove=overwrite) + subdir=temp_subdir, remove=overwrite, make_temp_h5=make_temp_h5) # remove datasets from final archive file not found in db - pp_util.cleanup_archive(configs, logger) + pp_util.cleanup_archive(configs, logger, make_temp_h5) run_list = [] @@ -340,7 +346,7 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], futures.remove(future) pp_util.cleanup_mandb(out_dict, out_meta, errors, configs, - logger, overwrite, db_manager=db_manager) + logger, overwrite, db_manager=db_manager, make_temp_h5=make_temp_h5) # update jobdb if jobdb_path is not None: @@ -456,6 +462,12 @@ def get_parser(parser=None): type=str, default=None ) + parser.add_argument( + '--make-temp', + help="Copy h5 archives to temporary files to prevent file corruption (for site-computing).", + type=bool, + default=True + ) return parser @@ -473,7 +485,8 @@ def main(configs: str, compress: bool = False, run_from_jobdb: bool = False, raise_error: bool = False, - pb_path: Optional[str] = None): + pb_path: Optional[str] = None, + make_temp_h5: Optional[bool] = False): rank, executor, as_completed_callable = get_exec_env(nproc) if rank == 0: @@ -493,7 +506,8 @@ def main(configs: str, compress=compress, run_from_jobdb=run_from_jobdb, raise_error=raise_error, - pb_path=pb_path) + pb_path=pb_path, + make_temp_h5=make_temp_h5) if __name__ == '__main__': main_launcher(main, get_parser) From 15b4a4c0038aaab821b0c3ec940f74e15286d2ef Mon Sep 17 00:00:00 2001 From: mmccrackan Date: Fri, 3 Jul 2026 14:34:55 -0400 Subject: [PATCH 2/8] option to bypass h5 ctx manager for preproc --- sotodlib/core/util.py | 16 ++--- sotodlib/preprocess/preprocess_util.py | 60 ++++++++++--------- .../multilayer_preprocess_tod.py | 28 ++++----- sotodlib/site_pipeline/preprocess_tod.py | 24 ++++---- 4 files changed, 62 insertions(+), 66 deletions(-) diff --git a/sotodlib/core/util.py b/sotodlib/core/util.py index 3665db56e..1ed48fe9f 100644 --- a/sotodlib/core/util.py +++ b/sotodlib/core/util.py @@ -53,8 +53,6 @@ class H5ContextManager: Number of times to retry opening the file if it is locked. delay : int or float, optional Delay in seconds between retries. - override_temp : bool - Override creation of temporary file and just use original file. **kwargs : Additional keyword arguments passed to `h5py.File`. """ @@ -98,18 +96,12 @@ def open(self): self.f = h5py.File(self.filename, mode=self.mode, **self.kwargs) elif self.mode == "a" or self.mode == "r+": # Copy the existing file to a temp location for modification - if not self.override_temp: - if os.path.isfile(self.filename): - shutil.copy(self.filename, temp_path) - self.f = h5py.File(temp_path, mode=self.mode, **self.kwargs) - else: - self.f = h5py.File(self.filename, mode=self.mode, **self.kwargs) + if os.path.isfile(self.filename): + shutil.copy(self.filename, temp_path) + self.f = h5py.File(temp_path, mode=self.mode, **self.kwargs) else: # Writing and truncating - if not self.override_temp: - self.f = h5py.File(temp_path, mode=self.mode, **self.kwargs) - else: - self.f = h5py.File(self.filename, mode=self.mode, **self.kwargs) + self.f = h5py.File(temp_path, mode=self.mode, **self.kwargs) return self.f except BlockingIOError as e: # If the file is locked, retry opening it after a delay diff --git a/sotodlib/preprocess/preprocess_util.py b/sotodlib/preprocess/preprocess_util.py index 98b91637f..debb66a6c 100644 --- a/sotodlib/preprocess/preprocess_util.py +++ b/sotodlib/preprocess/preprocess_util.py @@ -1019,7 +1019,7 @@ def find_db(obs_id, configs, dets, context=None, logger=None): return dbexist -def cleanup_archive(configs, logger=None, make_temp_h5=False): +def cleanup_archive(configs, logger=None, use_h5_ctx=False): """This function finds the final preprocess archive file and deletes any datasets that are not found in the preprocess database. This helps avoid cases where the database writing was interrupted in a previous run. @@ -1030,9 +1030,9 @@ def cleanup_archive(configs, logger=None, make_temp_h5=False): Filepath or dictionary containing the preprocess configuration file. logger : PythonLogger Optional. Logger object or None will generate a new one. - make_temp_h5 : bool - Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. - For use on site-computing. + use_h5_ctx : bool + Optional. Use h5 context manager to avoid metadata corruption and locked sqlite + dbs. For use on site-computing. """ if type(configs) == str: @@ -1059,12 +1059,14 @@ def cleanup_archive(configs, logger=None, make_temp_h5=False): key=lambda t: t[0])[1] db_datasets = [d['dataset'] for d in db.inspect()] - with H5ContextManager(latest_file, mode="r+", make_temp_h5=make_temp_h5) as f: + + h5_file_func = H5ContextManager if use_h5_ctx else h5py.File + with h5_file_func(latest_file, mode="r+") as f: keys = list(f.keys()) for key in keys: if key not in db_datasets: logger.debug(f"{key} not found in db. deleting it from {latest_file}.") - del f[key] + del f[key] db.conn.close() @@ -1129,7 +1131,7 @@ def get_preproc_group_out_dict(obs_id, configs, dets, context=None, subdir='temp def save_group_and_cleanup(obs_id, configs, context=None, subdir='temp', - logger=None, remove=False, make_temp_h5=False): + logger=None, remove=False, use_h5_ctx=False): """This function checks if any temporary files exist from a preprocessing run and will either add them to the config policy file and create an entry in the manifest db by calling ``cleanup_mandb``. If the file exists but @@ -1153,9 +1155,9 @@ def save_group_and_cleanup(obs_id, configs, context=None, subdir='temp', remove : bool Optional. Default is False. Whether to remove a file if found. Used when ``overwrite`` is True in driving functions. - make_temp_h5 : bool - Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. - For use on site-computing. + use_h5_ctx : bool + Optional. Use h5 context manager to avoid metadata corruption and locked sqlite + dbs. For use on site-computing. Returns ------- @@ -1190,7 +1192,7 @@ def save_group_and_cleanup(obs_id, configs, context=None, subdir='temp', try: if not remove: cleanup_mandb(outputs_grp, (obs_id, g), - (None, None, None), configs, logger, make_temp_h5=make_temp_h5) + (None, None, None), configs, logger, use_h5_ctx=use_h5_ctx) else: # if we're overwriting, remove file so it will re-run os.remove(outputs_grp['temp_file']) @@ -1214,7 +1216,7 @@ def save_group_and_cleanup(obs_id, configs, context=None, subdir='temp', def cleanup_obs(obs_id, policy_dir, errlog, configs, context=None, - subdir='temp', remove=False, make_temp_h5=False): + subdir='temp', remove=False, use_h5_ctx=False): """For a given obs id, this function will search the policy_dir directory if it exists for any files with that obsnum in their filename. If any are found, it will run save_group_and_cleanup for that obs id. @@ -1236,9 +1238,9 @@ def cleanup_obs(obs_id, policy_dir, errlog, configs, context=None, remove : bool Optional. Default is False. Whether to remove a file if found. Used when ``overwrite`` is True in driving functions. - make_temp_h5 : bool - Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. - For use on site-computing. + use_h5_ctx : bool + Optional. Use h5 context manager to avoid metadata corruption and locked sqlite + dbs. For use on site-computing. """ if os.path.exists(policy_dir): @@ -1251,7 +1253,7 @@ def cleanup_obs(obs_id, policy_dir, errlog, configs, context=None, if found: errors = save_group_and_cleanup(obs_id, configs, context, subdir=subdir, remove=remove, - make_temp_h5=make_temp_h5) + use_h5_ctx=use_h5_ctx) if errors[0] is not None: with open(errlog, 'a') as f: @@ -1263,7 +1265,7 @@ def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, logger=None, overwrite=False, save_archive=False, save_proc_aman=True, compress=False, skip_missing=False, ignore_cfg_check=False, - make_temp_h5=False): + use_h5_ctx=False): """ This function is expected to receive a single obs_id, and dets dictionary. The dets dictionary must match the grouping specified in the preprocess @@ -1313,9 +1315,9 @@ def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, ignore_cfg_check : bool If True, do not attempt to validate that configs_init is the same as the config used to create the existing init db when running ``multilayer_load_and_preprocess``. - make_temp_h5 : bool - Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. - For use on site-computing. + use_h5_ctx : bool + Optional. Use h5 context manager to avoid metadata corruption and locked sqlite + dbs. For use on site-computing. Returns ------- @@ -1485,7 +1487,7 @@ def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, logger.info(f"Adding result to init db for {obs_id}: {group}") cleanup_mandb(out_dict_init, (obs_id, group), (None, None, None), configs_init, logger=logger, overwrite=overwrite, - make_temp_h5=make_temp_h5) + use_h5_ctx=use_h5_ctx) # Make init plots if make_lmsi_init: new_plots = os.path.join(configs_init["plot_dir"], @@ -1555,7 +1557,7 @@ def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, logger.info(f"Adding result to proc db for {obs_id}: {group}") cleanup_mandb(out_dict_proc, (obs_id, group), (None, None, None), configs_proc, logger=logger, overwrite=overwrite, - make_temp_h5=make_temp_h5) + use_h5_ctx=use_h5_ctx) if 'valid_data' in aman.preprocess: aman.preprocess.move('valid_data', None) aman.preprocess.merge(proc_aman) @@ -1579,7 +1581,7 @@ def preproc_or_load_group(obs_id, configs_init, dets, configs_proc=None, def cleanup_mandb(out_dict, out_meta, errors, configs, logger=None, overwrite=False, - db_manager=None, make_temp_h5=False): + db_manager=None, use_h5_ctx=False): """Function to update the manifest db when data is collected from the ``preproc_or_load_group`` function. If used in an mpi framework this function is expected to be run from rank 0 after a ``comm.gather``. @@ -1618,9 +1620,9 @@ def cleanup_mandb(out_dict, out_meta, errors, configs, logger=None, overwrite=Fa db_manager : DbBatchManager, optional External database batch manager for optimized operations. If provided, uses the manager instead of creating individual connections. - make_temp_h5 : bool - Optional. Copy h5 archives to temporary files to prevent corruption from metadata sync. - For use on site-computing. + use_h5_ctx : bool + Optional. Use h5 context manager to avoid metadata corruption and locked sqlite + dbs. For use on site-computing. """ if logger is None: @@ -1648,8 +1650,10 @@ def cleanup_mandb(out_dict, out_meta, errors, configs, logger=None, overwrite=Fa src_file = out_dict['temp_file'] - with H5ContextManager(dest_file, mode='a', make_temp_h5=make_temp_h5) as f_dest: - with H5ContextManager(src_file, mode='r', make_temp_h5=make_temp_h5) as f_src: + h5_file_func = H5ContextManager if use_h5_ctx else h5py.File + + with h5_file_func(dest_file, mode='a') as f_dest: + with h5_file_func(src_file, mode='r') as f_src: for dts in f_src.keys(): # If the dataset or group already exists, delete it to overwrite if overwrite and dts in f_dest: diff --git a/sotodlib/site_pipeline/multilayer_preprocess_tod.py b/sotodlib/site_pipeline/multilayer_preprocess_tod.py index 0b36e703a..729a0d8d4 100644 --- a/sotodlib/site_pipeline/multilayer_preprocess_tod.py +++ b/sotodlib/site_pipeline/multilayer_preprocess_tod.py @@ -33,7 +33,7 @@ def multilayer_preprocess_tod(obs_id: str, verbosity: int = 0, compress: bool = False, overwrite: bool = False, - make_temp_h5: bool = False): + use_h5_ctx: bool = False): """Meant to be run as part of a batched script, this function calls the preprocessing pipeline a specific Observation ID and group combination and saves the results in the ManifestDb specified in the configs. @@ -56,9 +56,9 @@ def multilayer_preprocess_tod(obs_id: str, Whether or not to compress the preprocessing h5 files. overwrite : bool If True, overwrite contents of temporary h5 files. - make_temp_h5 : bool - Copy h5 archives to temporary files to prevent corruption from metadata sync. - For use on site-computing. + use_h5_ctx : bool + Use h5 context manager to avoid metadata corruption and locked sqlite + dbs. For use on site-computing. Returns ------- @@ -89,7 +89,7 @@ def multilayer_preprocess_tod(obs_id: str, overwrite=overwrite, save_archive=False, compress=compress, - make_temp_h5=make_temp_h5, + use_h5_ctx=use_h5_ctx, ) return out_dict_init, out_dict_proc, errors @@ -197,7 +197,7 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], run_from_jobdb: bool = False, raise_error: bool = False, pb_path: Optional[str] = None, - make_temp_h5: Optional[bool] = False): + use_h5_ctx: Optional[bool] = False): init_temp_subdir = "temp" proc_temp_subdir = "temp_proc" @@ -266,14 +266,14 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], for obs_id in obs_list: pp_util.cleanup_obs(obs_id, init_policy_dir, errlog, configs_init, context_init, subdir=init_temp_subdir, remove=overwrite, - make_temp_h5=make_temp_h5) + use_h5_ctx=use_h5_ctx) pp_util.cleanup_obs(obs_id, proc_policy_dir, errlog, configs_proc, context_proc, subdir=proc_temp_subdir, remove=overwrite, - make_temp_h5=make_temp_h5) + use_h5_ctx=use_h5_ctx) # remove datasets from final archive file not found in init db for config in (configs_init, configs_proc): - pp_util.cleanup_archive(config, logger, make_temp_h5) + pp_util.cleanup_archive(config, logger, use_h5_ctx) run_list = [] @@ -443,11 +443,11 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], logger.info(f"Adding future result to init db for {obs_id}: {group}") pp_util.cleanup_mandb(out_dict_init, out_meta, errors, configs_init, logger, overwrite, - db_manager=db_mgr_init, make_temp_h5=make_temp_h5) + db_manager=db_mgr_init, use_h5_ctx=use_h5_ctx) logger.info(f"Adding future result to proc db for {obs_id}: {group}") pp_util.cleanup_mandb(out_dict_proc, out_meta, errors, configs_proc, logger, overwrite, - db_manager=db_mgr_proc, make_temp_h5=make_temp_h5) + db_manager=db_mgr_proc, use_h5_ctx=use_h5_ctx) # update jobdb if jobdb_path is not None: @@ -569,7 +569,7 @@ def get_parser(parser=None): ) parser.add_argument( '--make-temp', - help="Copy h5 archives to temporary files to prevent file corruption (for site-computing).", + help="Use h5 context manager to prevent file corruption (for site-computing).", type=bool, default=True ) @@ -592,7 +592,7 @@ def main(configs_init: str, run_from_jobdb: bool = False, raise_error: bool = False, pb_path: Optional[str] = None, - make_temp_h5: Optional[bool] = False): + use_h5_ctx: Optional[bool] = False): rank, executor, as_completed_callable = get_exec_env(nproc) if rank == 0: @@ -614,7 +614,7 @@ def main(configs_init: str, run_from_jobdb=run_from_jobdb, raise_error=raise_error, pb_path=pb_path, - make_temp_h5=make_temp_h5) + use_h5_ctx=use_h5_ctx) if __name__ == '__main__': diff --git a/sotodlib/site_pipeline/preprocess_tod.py b/sotodlib/site_pipeline/preprocess_tod.py index 0e92ebd41..e96ef82ad 100644 --- a/sotodlib/site_pipeline/preprocess_tod.py +++ b/sotodlib/site_pipeline/preprocess_tod.py @@ -95,7 +95,7 @@ def preprocess_tod(configs: Union[str, dict], verbosity: int = 0, compress: bool = False, overwrite: bool = False, - make_temp_h5: bool = False + use_h5_ctx: bool = False ): """Meant to be run as part of a batched script, this function calls the preprocessing pipeline a specific Observation ID and group combination @@ -117,9 +117,9 @@ def preprocess_tod(configs: Union[str, dict], Whether or not to compress the preprocessing h5 files. overwrite : bool If True, overwrite contents of temporary h5 files. - make_temp_h5 : bool - Copy h5 archives to temporary files to prevent corruption from metadata sync. - For use on site-computing. + use_h5_ctx : bool + Use h5 context manager to avoid metadata corruption and locked sqlite + dbs. For use on site-computing. Returns ------- @@ -145,7 +145,7 @@ def preprocess_tod(configs: Union[str, dict], overwrite=overwrite, save_archive=False, compress=compress, - make_temp_h5=make_temp_h5, + use_h5_ctx=use_h5_ctx, ) return out_dict, errors @@ -168,7 +168,7 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], run_from_jobdb: bool = False, raise_error: bool = False, pb_path: Optional[str] = None, - make_temp_h5: Optional[bool] = False): + use_h5_ctx: Optional[bool] = False): temp_subdir = "temp" @@ -215,10 +215,10 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], ) for obs_id in obs_list: pp_util.cleanup_obs(obs_id, policy_dir, errlog, configs, context, - subdir=temp_subdir, remove=overwrite, make_temp_h5=make_temp_h5) + subdir=temp_subdir, remove=overwrite, use_h5_ctx=use_h5_ctx) # remove datasets from final archive file not found in db - pp_util.cleanup_archive(configs, logger, make_temp_h5) + pp_util.cleanup_archive(configs, logger, use_h5_ctx) run_list = [] @@ -346,7 +346,7 @@ def _main(executor: Union["MPICommExecutor", "ProcessPoolExecutor"], futures.remove(future) pp_util.cleanup_mandb(out_dict, out_meta, errors, configs, - logger, overwrite, db_manager=db_manager, make_temp_h5=make_temp_h5) + logger, overwrite, db_manager=db_manager, use_h5_ctx=use_h5_ctx) # update jobdb if jobdb_path is not None: @@ -464,7 +464,7 @@ def get_parser(parser=None): ) parser.add_argument( '--make-temp', - help="Copy h5 archives to temporary files to prevent file corruption (for site-computing).", + help="Use h5 context manager to prevent file corruption (for site-computing).", type=bool, default=True ) @@ -486,7 +486,7 @@ def main(configs: str, run_from_jobdb: bool = False, raise_error: bool = False, pb_path: Optional[str] = None, - make_temp_h5: Optional[bool] = False): + use_h5_ctx: Optional[bool] = False): rank, executor, as_completed_callable = get_exec_env(nproc) if rank == 0: @@ -507,7 +507,7 @@ def main(configs: str, run_from_jobdb=run_from_jobdb, raise_error=raise_error, pb_path=pb_path, - make_temp_h5=make_temp_h5) + use_h5_ctx=use_h5_ctx) if __name__ == '__main__': main_launcher(main, get_parser) From af154d2b82ecdf7d5eeca4b0ce02a070c75ebe49 Mon Sep 17 00:00:00 2001 From: mmccrackan Date: Fri, 3 Jul 2026 14:38:19 -0400 Subject: [PATCH 3/8] remove args --- sotodlib/core/util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sotodlib/core/util.py b/sotodlib/core/util.py index 1ed48fe9f..a5985c48d 100644 --- a/sotodlib/core/util.py +++ b/sotodlib/core/util.py @@ -59,14 +59,13 @@ class H5ContextManager: temp_suffix = ".temporary" - def __init__(self, filename, mode="r", max_attempts=3, delay=5, override_temp=False, **kwargs): + def __init__(self, filename, mode="r", max_attempts=3, delay=5, **kwargs): self.filename = filename self.kwargs = kwargs self.max_attempts = max_attempts self.delay = delay self.f = None self.mode = mode - self.override_temp = override_temp self._check_file_for_mode() if self.max_attempts <= 0: raise RuntimeError("max_attempts should be at least one") From 38e805dcbd94d9d6db91bc777b6e43f82bf59a4f Mon Sep 17 00:00:00 2001 From: mmccrackan Date: Fri, 3 Jul 2026 14:39:55 -0400 Subject: [PATCH 4/8] fix name --- sotodlib/site_pipeline/multilayer_preprocess_tod.py | 2 +- sotodlib/site_pipeline/preprocess_tod.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sotodlib/site_pipeline/multilayer_preprocess_tod.py b/sotodlib/site_pipeline/multilayer_preprocess_tod.py index 729a0d8d4..3c6ea6b0e 100644 --- a/sotodlib/site_pipeline/multilayer_preprocess_tod.py +++ b/sotodlib/site_pipeline/multilayer_preprocess_tod.py @@ -568,7 +568,7 @@ def get_parser(parser=None): default=None ) parser.add_argument( - '--make-temp', + '--use-h5-ctx', help="Use h5 context manager to prevent file corruption (for site-computing).", type=bool, default=True diff --git a/sotodlib/site_pipeline/preprocess_tod.py b/sotodlib/site_pipeline/preprocess_tod.py index e96ef82ad..03516d05b 100644 --- a/sotodlib/site_pipeline/preprocess_tod.py +++ b/sotodlib/site_pipeline/preprocess_tod.py @@ -463,7 +463,7 @@ def get_parser(parser=None): default=None ) parser.add_argument( - '--make-temp', + '--use-h5-ctx', help="Use h5 context manager to prevent file corruption (for site-computing).", type=bool, default=True From 1072228e3732bda6809fa02925780535387c3834 Mon Sep 17 00:00:00 2001 From: mmccrackan Date: Fri, 3 Jul 2026 14:41:45 -0400 Subject: [PATCH 5/8] fix indent --- sotodlib/preprocess/preprocess_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sotodlib/preprocess/preprocess_util.py b/sotodlib/preprocess/preprocess_util.py index debb66a6c..bea9e930f 100644 --- a/sotodlib/preprocess/preprocess_util.py +++ b/sotodlib/preprocess/preprocess_util.py @@ -1066,7 +1066,7 @@ def cleanup_archive(configs, logger=None, use_h5_ctx=False): for key in keys: if key not in db_datasets: logger.debug(f"{key} not found in db. deleting it from {latest_file}.") - del f[key] + del f[key] db.conn.close() From 632f09a26df3155bb8aba54dedc94877b54c6649 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 3 Jul 2026 15:15:58 -0400 Subject: [PATCH 6/8] adding the changes required for make_atomic_filterbin_map.py --- .../make_atomic_filterbin_map.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/sotodlib/site_pipeline/make_atomic_filterbin_map.py b/sotodlib/site_pipeline/make_atomic_filterbin_map.py index 2ee8e007c..969000d17 100644 --- a/sotodlib/site_pipeline/make_atomic_filterbin_map.py +++ b/sotodlib/site_pipeline/make_atomic_filterbin_map.py @@ -110,6 +110,9 @@ class Cfg: Data type for timestreams dtype_map: str Data type for maps + use_h5_ctx : bool + Use h5 context manager to avoid metadata corruption and locked sqlite + dbs. For use on site-computing. """ def __init__( self, @@ -151,6 +154,7 @@ def __init__( wn_label: str = 'preprocess.noiseQ_mapmaking.std', apply_wobble: bool = True, compress: bool = True, + use_h5_ctx: bool = False, ) -> None: self.context = context self.preprocess_config = preprocess_config @@ -190,6 +194,7 @@ def __init__( self.wn_label = wn_label self.apply_wobble = apply_wobble self.compress = compress + self.use_h5_ctx = use_h5_ctx @classmethod def from_yaml(cls, path) -> "Cfg": with open(path, "r") as f: @@ -270,6 +275,8 @@ def main( verbose = args.verbose - args.quiet + use_h5_ctx = args.use_h5_ctx + recenter = None if args.center_at: recenter = mapmaking.parse_recentering(args.center_at) @@ -415,15 +422,15 @@ def main( for obs in obslists_arr: obs_id = obs[0][0] if len(preprocess_config)==1: - preprocess_util.cleanup_obs(obs_id, policy_dir_init, errlog[0], preprocess_config[0], subdir='temp', remove=False) + preprocess_util.cleanup_obs(obs_id, policy_dir_init, errlog[0], preprocess_config[0], subdir='temp', remove=False, use_h5_ctx=use_h5_ctx) else: - preprocess_util.cleanup_obs(obs_id, policy_dir_init, errlog[0], preprocess_config[0], subdir='temp', remove=False) - preprocess_util.cleanup_obs(obs_id, policy_dir_proc, errlog[1], preprocess_config[1], subdir='temp_proc', remove=False) + preprocess_util.cleanup_obs(obs_id, policy_dir_init, errlog[0], preprocess_config[0], subdir='temp', remove=False, use_h5_ctx=use_h5_ctx) + preprocess_util.cleanup_obs(obs_id, policy_dir_proc, errlog[1], preprocess_config[1], subdir='temp_proc', remove=False, use_h5_ctx=use_h5_ctx) # remove datasets from final archive file not found in db - preprocess_util.cleanup_archive(preprocess_config[0], L) + preprocess_util.cleanup_archive(preprocess_config[0], L, use_h5_ctx) if len(preprocess_config) > 1: - preprocess_util.cleanup_archive(preprocess_config[1], L) + preprocess_util.cleanup_archive(preprocess_config[1], L, use_h5_ctx) run_list = [] for oi, ol in enumerate(obslists_arr): @@ -509,7 +516,7 @@ def main( oid = outputs[ii][idx_prepoc]['db_data']['obs:obs_id'] group = [v for k, v in outputs[ii][idx_prepoc]['db_data'].items() if 'dets' in k] preprocess_util.cleanup_mandb(outputs[ii][idx_prepoc], (oid, group), (errors[ii], None, None), - preprocess_config[idx_prepoc], L) + preprocess_config[idx_prepoc], L, use_h5_ctx=use_h5_ctx) L.info("Done") return True From 8acefda9689dd895f567b9eba4a82770472970ec Mon Sep 17 00:00:00 2001 From: mmccrackan Date: Fri, 3 Jul 2026 15:37:12 -0400 Subject: [PATCH 7/8] update argparse description --- sotodlib/site_pipeline/multilayer_preprocess_tod.py | 2 +- sotodlib/site_pipeline/preprocess_tod.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sotodlib/site_pipeline/multilayer_preprocess_tod.py b/sotodlib/site_pipeline/multilayer_preprocess_tod.py index 3c6ea6b0e..5b2c9d072 100644 --- a/sotodlib/site_pipeline/multilayer_preprocess_tod.py +++ b/sotodlib/site_pipeline/multilayer_preprocess_tod.py @@ -569,7 +569,7 @@ def get_parser(parser=None): ) parser.add_argument( '--use-h5-ctx', - help="Use h5 context manager to prevent file corruption (for site-computing).", + help="Use h5 context manager to prevent file corruption and db lock errrors (for site-computing).", type=bool, default=True ) diff --git a/sotodlib/site_pipeline/preprocess_tod.py b/sotodlib/site_pipeline/preprocess_tod.py index 03516d05b..cc71b6703 100644 --- a/sotodlib/site_pipeline/preprocess_tod.py +++ b/sotodlib/site_pipeline/preprocess_tod.py @@ -464,7 +464,7 @@ def get_parser(parser=None): ) parser.add_argument( '--use-h5-ctx', - help="Use h5 context manager to prevent file corruption (for site-computing).", + help="Use h5 context manager to prevent file corruption and db lock errors (for site-computing).", type=bool, default=True ) From c0740567a443ff9b673921e1854b2036a3e0e17e Mon Sep 17 00:00:00 2001 From: mmccrackan Date: Sun, 5 Jul 2026 16:58:45 -0400 Subject: [PATCH 8/8] set ctx arg to default false --- sotodlib/site_pipeline/multilayer_preprocess_tod.py | 2 +- sotodlib/site_pipeline/preprocess_tod.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sotodlib/site_pipeline/multilayer_preprocess_tod.py b/sotodlib/site_pipeline/multilayer_preprocess_tod.py index 5b2c9d072..f12778393 100644 --- a/sotodlib/site_pipeline/multilayer_preprocess_tod.py +++ b/sotodlib/site_pipeline/multilayer_preprocess_tod.py @@ -571,7 +571,7 @@ def get_parser(parser=None): '--use-h5-ctx', help="Use h5 context manager to prevent file corruption and db lock errrors (for site-computing).", type=bool, - default=True + default=False ) return parser diff --git a/sotodlib/site_pipeline/preprocess_tod.py b/sotodlib/site_pipeline/preprocess_tod.py index cc71b6703..1db35c575 100644 --- a/sotodlib/site_pipeline/preprocess_tod.py +++ b/sotodlib/site_pipeline/preprocess_tod.py @@ -466,7 +466,7 @@ def get_parser(parser=None): '--use-h5-ctx', help="Use h5 context manager to prevent file corruption and db lock errors (for site-computing).", type=bool, - default=True + default=False ) return parser