From ee90725a9f16e9132d580e0d7e431f7ba19a17e1 Mon Sep 17 00:00:00 2001 From: M Platypus Date: Sat, 28 Feb 2026 11:31:13 -0500 Subject: [PATCH 1/4] Fix leaked semaphore warning on macOS by shutting down DataLoader workers persistent_workers=True keeps worker processes alive across epochs, but on macOS (spawn start method) the resource_tracker detects unreleased semaphores at exit. Add shutdown_data_loaders() to explicitly terminate workers after training completes. Co-Authored-By: Claude Opus 4.6 --- .../tinyml_tinyverse/references/common/__init__.py | 1 + .../tinyml_tinyverse/references/common/train_base.py | 12 ++++++++++++ .../references/timeseries_anomalydetection/train.py | 3 +++ .../references/timeseries_classification/train.py | 3 +++ .../references/timeseries_forecasting/train.py | 3 +++ .../references/timeseries_regression/train.py | 3 +++ 6 files changed, 25 insertions(+) diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/common/__init__.py b/tinyml-tinyverse/tinyml_tinyverse/references/common/__init__.py index e541add3..e38488bc 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/common/__init__.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/common/__init__.py @@ -25,6 +25,7 @@ setup_training_environment, prepare_transforms, create_data_loaders, + shutdown_data_loaders, # Model creation and setup create_model, log_model_summary, diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py b/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py index 78fba7f5..985c2f34 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py @@ -789,3 +789,15 @@ def create_data_loaders(dataset, dataset_test, train_sampler, test_sampler, args dataset_test, batch_size=args.batch_size, sampler=test_sampler, num_workers=args.workers, pin_memory=True if gpu > 0 else False, collate_fn=utils.collate_fn) return data_loader, data_loader_test + + +def shutdown_data_loaders(*loaders): + """Explicitly shut down DataLoader worker processes to avoid leaked semaphore warnings. + + Must be called before exit when persistent_workers=True (especially on macOS + where the 'spawn' start method tracks semaphores via resource_tracker). + """ + for loader in loaders: + if hasattr(loader, '_iterator') and loader._iterator is not None: + loader._iterator._shutdown_workers() + loader._iterator = None diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/train.py b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/train.py index e8ce6170..834bf77a 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/train.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/train.py @@ -77,6 +77,7 @@ get_output_int_flag, load_onnx_for_inference, create_data_loaders, + shutdown_data_loaders, ) dataset_loader_dict = {'GenericTSDatasetAD': GenericTSDatasetAD} @@ -287,6 +288,8 @@ def main(gpu, args): output_int = get_output_int_flag(args) generate_golden_vectors(args.output_dir, dataset, output_int, threshold, args.generic_model) + shutdown_data_loaders(data_loader, data_loader_test) + def run(args): """Run training with optional distributed mode.""" diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_classification/train.py b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_classification/train.py index 80cf0e60..597e6c09 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_classification/train.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_classification/train.py @@ -73,6 +73,7 @@ setup_training_environment, prepare_transforms, create_data_loaders, + shutdown_data_loaders, log_model_summary, load_pretrained_weights, setup_optimizer_and_scheduler, @@ -362,6 +363,8 @@ def main(gpu, args): output_int = get_output_int_flag(args) generate_golden_vectors(args.output_dir, dataset, output_int, args.generic_model, args.nn_for_feature_extraction) + shutdown_data_loaders(data_loader, data_loader_test) + def run(args): """Run training with optional distributed mode.""" diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_forecasting/train.py b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_forecasting/train.py index e9f2a75c..b62024b0 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_forecasting/train.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_forecasting/train.py @@ -78,6 +78,7 @@ get_output_int_flag, load_onnx_for_inference, create_data_loaders, + shutdown_data_loaders, ) dataset_loader_dict = {'GenericTSDatasetForecasting': GenericTSDatasetForecasting} @@ -308,6 +309,8 @@ def main(gpu, args): output_int = get_output_int_flag(args) generate_golden_vectors(args.output_dir, output_int, dataset, args.generic_model) + shutdown_data_loaders(data_loader, data_loader_test) + def run(args): """Run training with optional distributed mode.""" diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_regression/train.py b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_regression/train.py index 0f34442d..df802549 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_regression/train.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_regression/train.py @@ -76,6 +76,7 @@ get_output_int_flag, load_onnx_for_inference, create_data_loaders, + shutdown_data_loaders, ) dataset_loader_dict = {'GenericTSDatasetReg': GenericTSDatasetReg} @@ -236,6 +237,8 @@ def main(gpu, args): output_int = get_output_int_flag(args) generate_golden_vectors(args.output_dir, output_int, dataset, args.generic_model) + shutdown_data_loaders(data_loader, data_loader_test) + def run(args): """Run training with optional distributed mode.""" From 8a292aba3639b560f7ab76804ce2b7f49146a692 Mon Sep 17 00:00:00 2001 From: M Platypus Date: Sun, 1 Mar 2026 14:05:31 -0500 Subject: [PATCH 2/4] Fix leaked semaphore: shut down DataLoader workers in all test_onnx scripts The previous fix (f3cc7c0) added shutdown_data_loaders() to train.py files but missed all test_onnx scripts. These are the last step in the pipeline and create DataLoaders with num_workers=8 without cleanup, causing macOS resource_tracker to report leaked semaphores at process exit. Add shutdown_data_loaders() calls to all 6 test_onnx files and harden the function itself with try/except and gc.collect() to ensure worker processes fully release their semaphores before exit. Co-Authored-By: Claude Opus 4.6 --- .../references/common/train_base.py | 14 +++++++++++--- .../references/image_classification/test_onnx.py | 2 ++ .../timeseries_anomalydetection/test_onnx.py | 4 ++++ .../timeseries_anomalydetection/test_onnx_cls.py | 2 ++ .../timeseries_classification/test_onnx.py | 2 ++ .../references/timeseries_forecasting/test_onnx.py | 3 +++ .../references/timeseries_regression/test_onnx.py | 2 ++ 7 files changed, 26 insertions(+), 3 deletions(-) diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py b/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py index 985c2f34..6dce92db 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py @@ -794,10 +794,18 @@ def create_data_loaders(dataset, dataset_test, train_sampler, test_sampler, args def shutdown_data_loaders(*loaders): """Explicitly shut down DataLoader worker processes to avoid leaked semaphore warnings. - Must be called before exit when persistent_workers=True (especially on macOS - where the 'spawn' start method tracks semaphores via resource_tracker). + Must be called before exit when DataLoaders use num_workers > 0 (especially + on macOS where the 'spawn' start method tracks semaphores via resource_tracker). + Works for both persistent_workers=True and False. """ + import gc for loader in loaders: if hasattr(loader, '_iterator') and loader._iterator is not None: - loader._iterator._shutdown_workers() + try: + loader._iterator._shutdown_workers() + except Exception: + pass loader._iterator = None + # Force garbage collection so any remaining iterator / queue references + # are released before the process exits. + gc.collect() diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/image_classification/test_onnx.py b/tinyml-tinyverse/tinyml_tinyverse/references/image_classification/test_onnx.py index 4c50b9f2..79fa0fb6 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/image_classification/test_onnx.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/image_classification/test_onnx.py @@ -47,6 +47,7 @@ from tinyml_tinyverse.common.utils import misc_utils, utils, mdcl_utils from tinyml_tinyverse.common.utils.mdcl_utils import Logger from tinyml_tinyverse.common.utils.utils import get_confusion_matrix +from ..common.train_base import shutdown_data_loaders dataset_loader_dict = {'GenericImageDataset': GenericImageDataset} @@ -189,6 +190,7 @@ def main(gpu, args): index=[f"Ground Truth: {x}" for x in dataset.inverse_label_map.values()]), headers="keys", tablefmt='grid'))) except ValueError as e: logger.warning("Not able to compute Confusion Matrix. Error: " + str(e)) + shutdown_data_loaders(data_loader) return def run(args): diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/test_onnx.py b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/test_onnx.py index ee7c729e..109d2216 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/test_onnx.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/test_onnx.py @@ -51,6 +51,7 @@ load_onnx_model, run_distributed_test, ) +from ..common.train_base import shutdown_data_loaders dataset_loader_dict = { 'GenericTSDataset': GenericTSDataset, @@ -138,6 +139,7 @@ def get_reconstruction_errors_stats(args): normal_error_mean = torch.mean(errors) normal_error_std = torch.std(errors) + shutdown_data_loaders(data_loader) return normal_error_mean.cpu(), normal_error_std.cpu() @@ -271,6 +273,8 @@ def main(gpu, args): logger.info('Confusion Matrix:\n {}'.format(tabulate(confusion_matrix_df, headers="keys", tablefmt='grid'))) + shutdown_data_loaders(data_loader) + def run(args): """Run testing with optional distributed mode.""" diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/test_onnx_cls.py b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/test_onnx_cls.py index 1bbd04cb..35fcde70 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/test_onnx_cls.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_anomalydetection/test_onnx_cls.py @@ -47,6 +47,7 @@ from tinyml_tinyverse.common.utils import misc_utils, utils, mdcl_utils from tinyml_tinyverse.common.utils.mdcl_utils import Logger from tinyml_tinyverse.common.utils.utils import get_confusion_matrix +from ..common.train_base import shutdown_data_loaders dataset_loader_dict = {'GenericTSDataset': GenericTSDataset} @@ -192,6 +193,7 @@ def main(gpu, args): logger.info('Confusion Matrix:\n {}'.format(tabulate(pd.DataFrame( confusion_matrix, columns=[f"Predicted as: {x}" for x in dataset.inverse_label_map.values()], index=[f"Ground Truth: {x}" for x in dataset.inverse_label_map.values()]), headers="keys", tablefmt='grid'))) + shutdown_data_loaders(data_loader) return def run(args): diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_classification/test_onnx.py b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_classification/test_onnx.py index fb0d8aad..8dffe18f 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_classification/test_onnx.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_classification/test_onnx.py @@ -53,6 +53,7 @@ load_onnx_model, run_distributed_test, ) +from ..common.train_base import shutdown_data_loaders dataset_loader_dict = {'GenericTSDataset': GenericTSDataset} @@ -172,6 +173,7 @@ def main(gpu, args): except Exception as e: logger.error(f"Failed to generate file-level classification summary: {str(e)}") + shutdown_data_loaders(data_loader) return diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_forecasting/test_onnx.py b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_forecasting/test_onnx.py index d6f0029d..ce213fbf 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_forecasting/test_onnx.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_forecasting/test_onnx.py @@ -52,6 +52,7 @@ load_onnx_model, run_distributed_test, ) +from ..common.train_base import shutdown_data_loaders dataset_loader_dict = {'GenericTSDatasetForecasting': GenericTSDatasetForecasting} @@ -184,6 +185,8 @@ def main(gpu, args): plt.savefig(os.path.join(plots_dir, f'{target_variable_name}_predictions.png')) plt.close() + shutdown_data_loaders(data_loader_test) + def run(args): """Run testing with optional distributed mode.""" diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_regression/test_onnx.py b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_regression/test_onnx.py index c7c77606..fc28e854 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_regression/test_onnx.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/timeseries_regression/test_onnx.py @@ -50,6 +50,7 @@ load_onnx_model, run_distributed_test, ) +from ..common.train_base import shutdown_data_loaders dataset_loader_dict = {'GenericTSDataset': GenericTSDataset, 'GenericTSDatasetReg': GenericTSDatasetReg} @@ -139,6 +140,7 @@ def main(gpu, args): logger = getLogger("root.main.test_data") logger.info(f"{logger.name}: Test Data Evaluation RMSE: {torch.sqrt(metric.compute()):.2f}") logger.info(f"{logger.name}: Test Data Evaluation R2-Score: {r2_score.compute():.2f}") + shutdown_data_loaders(data_loader) return From d448db4fe38309a98b1db8d05009c092ed2c15d3 Mon Sep 17 00:00:00 2001 From: M Platypus Date: Sun, 1 Mar 2026 17:47:03 -0500 Subject: [PATCH 3/4] Fix 41 leaked semaphore objects on macOS shutdown Enhanced shutdown_data_loaders to explicitly break the iterator's references to multiprocessing Queue/Lock/Event objects after calling _shutdown_workers(). Without this, the POSIX named semaphores inside those objects survive until Python's resource_tracker atexit handler, which reports them as leaked. By clearing the references eagerly, CPython's refcount-based deallocation calls sem_unlink immediately. Also added the missing shutdown_data_loaders call in image_classification/train.py, which was the only train.py that did not shut down its DataLoaders. Co-Authored-By: Claude Opus 4.6 --- .../references/common/train_base.py | 53 +++++++++++++++++-- .../references/image_classification/train.py | 5 +- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py b/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py index 6dce92db..ba84ab9a 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py @@ -797,15 +797,58 @@ def shutdown_data_loaders(*loaders): Must be called before exit when DataLoaders use num_workers > 0 (especially on macOS where the 'spawn' start method tracks semaphores via resource_tracker). Works for both persistent_workers=True and False. + + The key issue on macOS: each DataLoader iterator holds multiprocessing Queue + objects whose internal Lock/Semaphore are POSIX named semaphores tracked by + Python's resource_tracker. ``_shutdown_workers()`` joins worker processes + and closes queues, but does NOT release the Queue objects themselves. If + those objects survive until the resource_tracker's ``atexit`` handler runs, + it reports them as "leaked". We break the references here so CPython's + refcount-based deallocation triggers ``sem_unlink`` immediately. """ import gc for loader in loaders: - if hasattr(loader, '_iterator') and loader._iterator is not None: + if not (hasattr(loader, '_iterator') and loader._iterator is not None): + continue + it = loader._iterator + try: + it._shutdown_workers() + except Exception: + pass + # Break the iterator's references to multiprocessing objects so their + # __del__ methods fire (calling sem_unlink) during gc below. + for attr in ('_index_queues', '_workers', '_data_queue', + '_worker_result_queue', '_pin_memory_thread', + '_workers_done_event'): try: - loader._iterator._shutdown_workers() + val = getattr(it, attr, None) + if val is None: + continue + if isinstance(val, list): + for item in val: + if hasattr(item, 'close'): + try: + item.close() + except Exception: + pass + try: + val.clear() + except Exception: + pass + else: + if hasattr(val, 'close'): + try: + val.close() + except Exception: + pass + try: + setattr(it, attr, None) + except Exception: + pass except Exception: pass - loader._iterator = None - # Force garbage collection so any remaining iterator / queue references - # are released before the process exits. + loader._iterator = None + # Two rounds: first collects the iterator itself, second collects + # Queue/Lock/Semaphore objects that were only reachable through it. + gc.collect() gc.collect() diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/image_classification/train.py b/tinyml-tinyverse/tinyml_tinyverse/references/image_classification/train.py index 2d71f649..f707df1d 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/image_classification/train.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/image_classification/train.py @@ -89,7 +89,7 @@ # Tiny ML TinyVerse Modules from tinyml_tinyverse.common.utils import misc_utils, utils, load_weights,gof_utils from tinyml_tinyverse.common.utils.mdcl_utils import Logger, create_dir -from tinyml_tinyverse.references.common.train_base import apply_output_int_default +from tinyml_tinyverse.references.common.train_base import apply_output_int_default, shutdown_data_loaders dataset_loader_dict = {'GenericImageDataset':GenericImageDataset} dataset_load_state = {'dataset': None, 'dataset_test': None, 'train_sampler': None, 'test_sampler': None} @@ -649,7 +649,8 @@ def main(gpu, args): generate_golden_vector_dir(args.output_dir) output_int = args.quantization == TinyMLQuantizationVersion.QUANTIZATION_TINPU and args.output_int generate_golden_vectors(args.output_dir, dataset, output_int, args.generic_model, args.nn_for_feature_extraction) - + + shutdown_data_loaders(data_loader, data_loader_test) return From 771bba690d04b6a202e9796ead25a6fd4fbd1cea Mon Sep 17 00:00:00 2001 From: M Platypus Date: Sun, 1 Mar 2026 22:02:41 -0500 Subject: [PATCH 4/4] Fix leaked semaphore warnings: unregister from resource_tracker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous approach (breaking Queue references for gc) didn't work because on Python <=3.11, _multiprocessing.SemLock's C dealloc only calls sem_close() — it never calls resource_tracker.unregister(). The resource_tracker therefore reports every semaphore as leaked regardless of cleanup efforts. (Fixed in Python 3.12+.) New approach: after _shutdown_workers() joins all worker processes, walk the iterator's Queue and Event objects to find their internal SemLock names, then explicitly call resource_tracker.unregister() for each one. This removes them from the resource_tracker's registry so its atexit handler has nothing to warn about. Co-Authored-By: Claude Opus 4.6 --- .../references/common/train_base.py | 107 +++++++++++------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py b/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py index ba84ab9a..fd6f160e 100644 --- a/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py +++ b/tinyml-tinyverse/tinyml_tinyverse/references/common/train_base.py @@ -791,6 +791,58 @@ def create_data_loaders(dataset, dataset_test, train_sampler, test_sampler, args return data_loader, data_loader_test +def _unregister_tracked_semaphores(*objects): + """Unregister multiprocessing semaphores from Python's resource_tracker. + + On Python <=3.11, ``_multiprocessing.SemLock``'s C dealloc calls + ``sem_close()`` but never ``resource_tracker.unregister()``. The + resource_tracker's ``atexit`` handler therefore reports every + semaphore ever created as "leaked". (Fixed in Python 3.12+ where + SemLock.__del__ calls unregister.) + + This function walks known multiprocessing container attributes + (Queue._rlock, Queue._wlock, Queue._sem, Event._cond, Event._flag, + etc.) to find underlying SemLock objects and unregisters their named + semaphores so the resource_tracker stays quiet. + """ + try: + from multiprocessing.resource_tracker import unregister + except ImportError: + return + + seen = set() + + def _scan(obj): + obj_id = id(obj) + if obj_id in seen: + return + seen.add(obj_id) + # Leaf: a Lock / Semaphore / BoundedSemaphore wrapping a SemLock + semlock = getattr(obj, '_semlock', None) + if semlock is not None: + name = getattr(semlock, 'name', None) + if name: + try: + unregister(name, "semaphore") + except Exception: + pass + return + # Recurse into known container attributes + for attr in ('_rlock', '_wlock', '_sem', '_lock', '_cond', '_flag'): + child = getattr(obj, attr, None) + if child is not None: + _scan(child) + + for obj in objects: + if obj is None: + continue + if isinstance(obj, (list, tuple)): + for item in obj: + _scan(item) + else: + _scan(obj) + + def shutdown_data_loaders(*loaders): """Explicitly shut down DataLoader worker processes to avoid leaked semaphore warnings. @@ -798,13 +850,12 @@ def shutdown_data_loaders(*loaders): on macOS where the 'spawn' start method tracks semaphores via resource_tracker). Works for both persistent_workers=True and False. - The key issue on macOS: each DataLoader iterator holds multiprocessing Queue - objects whose internal Lock/Semaphore are POSIX named semaphores tracked by - Python's resource_tracker. ``_shutdown_workers()`` joins worker processes - and closes queues, but does NOT release the Queue objects themselves. If - those objects survive until the resource_tracker's ``atexit`` handler runs, - it reports them as "leaked". We break the references here so CPython's - refcount-based deallocation triggers ``sem_unlink`` immediately. + After joining workers, we explicitly unregister all POSIX named semaphores + owned by the iterator's multiprocessing Queues and Events from the + resource_tracker. On Python <=3.11, this unregister never happens + automatically (the C SemLock dealloc only calls sem_close, not + resource_tracker.unregister), so without this step the resource_tracker + warns about "leaked semaphore objects" at shutdown. """ import gc for loader in loaders: @@ -815,40 +866,12 @@ def shutdown_data_loaders(*loaders): it._shutdown_workers() except Exception: pass - # Break the iterator's references to multiprocessing objects so their - # __del__ methods fire (calling sem_unlink) during gc below. - for attr in ('_index_queues', '_workers', '_data_queue', - '_worker_result_queue', '_pin_memory_thread', - '_workers_done_event'): - try: - val = getattr(it, attr, None) - if val is None: - continue - if isinstance(val, list): - for item in val: - if hasattr(item, 'close'): - try: - item.close() - except Exception: - pass - try: - val.clear() - except Exception: - pass - else: - if hasattr(val, 'close'): - try: - val.close() - except Exception: - pass - try: - setattr(it, attr, None) - except Exception: - pass - except Exception: - pass + # Unregister all POSIX named semaphores from the resource_tracker + # so it does not report them as leaked at exit. + _unregister_tracked_semaphores( + getattr(it, '_index_queues', None), + getattr(it, '_data_queue', None), + getattr(it, '_workers_done_event', None), + ) loader._iterator = None - # Two rounds: first collects the iterator itself, second collects - # Queue/Lock/Semaphore objects that were only reachable through it. - gc.collect() gc.collect()