From c03780200d57f98b95dfa946ce648750b3bfdff7 Mon Sep 17 00:00:00 2001 From: Morten Enemark Lund Date: Wed, 19 Nov 2025 13:13:09 +0100 Subject: [PATCH 1/7] Add DEBUG environment var for testing job handling --- anypytools/abcutils.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/anypytools/abcutils.py b/anypytools/abcutils.py index 5381fc0..d4bc5ed 100644 --- a/anypytools/abcutils.py +++ b/anypytools/abcutils.py @@ -60,7 +60,14 @@ ] if ON_WINDOWS: - from .jobpopen import JobPopen as Popen + if "ANYPYTOOLS_DEBUG_USE_PYTHON_POPEN" in os.environ: + print( + "[yellow]Warning: Using Python's subprocess.Popen instead of JobPopen.[/yellow]" + ) + from subprocess import Popen + else: + from .jobpopen import JobPopen as Popen + from subprocess import CREATE_NEW_PROCESS_GROUP else: from subprocess import Popen From 36c5ad51633abc35b7b2d9e8a03c699c3c5bf6cb Mon Sep 17 00:00:00 2001 From: Morten Enemark Lund Date: Wed, 19 Nov 2025 13:14:48 +0100 Subject: [PATCH 2/7] Refactor subprocess management in AnyPyProcess and _SubProcessContainer --- anypytools/abcutils.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/anypytools/abcutils.py b/anypytools/abcutils.py index d4bc5ed..02d4a7c 100644 --- a/anypytools/abcutils.py +++ b/anypytools/abcutils.py @@ -116,8 +116,8 @@ def stop_all(self): self._pids.clear() -_subprocess_container = _SubProcessContainer() -atexit.register(_subprocess_container.stop_all) +_global_subprocess_container = _SubProcessContainer() +atexit.register(_global_subprocess_container.stop_all) def _progress_print(progress, content): @@ -138,6 +138,7 @@ def execute_anybodycon( debug_mode=0, folder=None, interactive_mode=False, + subprocess_container=_global_subprocess_container, ): """Launch a single AnyBodyConsole applicaiton. @@ -281,7 +282,7 @@ def execute_anybodycon( proc = Popen(cmd, **kwargs) retcode = None - _subprocess_container.add(proc.pid) + subprocess_container.add(proc.pid) try: proc.wait(timeout=timeout) retcode = ctypes.c_int32(proc.returncode).value @@ -300,7 +301,7 @@ def execute_anybodycon( if ON_WINDOWS: proc._close_job_object(proc._win32_job) else: - _subprocess_container.remove(proc.pid) + subprocess_container.remove(proc.pid) if retcode == _TIMEDOUT_BY_ANYPYTOOLS: logfile.write(f"\nERROR: AnyPyTools : Timeout after {int(timeout)} sec.") @@ -614,6 +615,8 @@ def __init__( self.env = env else: self.env = None + + self._local_subprocess_container = _SubProcessContainer() logging.debug("\nAnyPyProcess initialized") def save_results(self, filename, append=False): @@ -785,7 +788,7 @@ def start_macro( """ # Handle different input types - if isinstance(macrolist, types.GeneratorType): + if isinstance(macrolist, (types.GeneratorType, tuple)): macrolist = list(macrolist) if isinstance(macrolist, AnyMacro): macrolist = macrolist.create_macros() @@ -870,7 +873,7 @@ def start_macro( except KeyboardInterrupt: _progress_print(progress, "[red]KeyboardInterrupt: User aborted[/red]") finally: - _subprocess_container.stop_all() + self._local_subprocess_container.stop_all() if not self.silent: _progress_print(progress, _tasklist_summery(tasklist)) @@ -925,6 +928,7 @@ def _worker(self, task, task_queue): debug_mode=self.debug_mode, folder=task.folder, interactive_mode=self.interactive_mode, + subprocess_container=self._local_subprocess_container, ) try: task.retcode = execute_anybodycon(**exe_args) @@ -1004,3 +1008,7 @@ def cleanup_logfiles(self, tasklist): silentremove(macrofile) except OSError as e: logger.debug(f"Could not remove: {macrofile} {e}") + + def __del__(self): + """Destructor to clean up any remaining subprocesses.""" + self._local_subprocess_container.stop_all() From 7c55fac89448422fbc6ef3689612a70f6d8e8fee Mon Sep 17 00:00:00 2001 From: Morten Enemark Lund Date: Wed, 19 Nov 2025 13:22:35 +0100 Subject: [PATCH 3/7] Bump version to 1.20.0 and update changelog for multiple instance support --- CHANGELOG.md | 11 ++++++----- anypytools/__init__.py | 2 +- pixi.toml | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94c33d2..c3c4a12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # AnyPyTools Change Log +## v1.20.0 + +* Fixed a problem that prevented multiple AnyPyTools instances from running + simultaneously in the same process. Previously, other running AnyBody instances + were shut down when the first `AnyPyProcess.start_macro()` call finished. + ## v1.19.2 * Fixed missing widget information is user-guide documentation. @@ -32,11 +38,6 @@ assert results[0]["SubjectID"] == "S001" assert results[0]["SubjectHeight"] == 1.8 ``` -* {meth}`results.to_dataframe() - ` has a new argument - `exclude_task_info` which can exclude task information (variables starting - with 'task_') when exporting results to a dataframe - ## v1.18 diff --git a/anypytools/__init__.py b/anypytools/__init__.py index 4178cf1..e743d04 100644 --- a/anypytools/__init__.py +++ b/anypytools/__init__.py @@ -36,7 +36,7 @@ "NORMAL_PRIORITY_CLASS", ] -__version__ = "1.19.2" +__version__ = "1.20.0" def print_versions(): diff --git a/pixi.toml b/pixi.toml index 9dba32c..56dcb32 100644 --- a/pixi.toml +++ b/pixi.toml @@ -10,7 +10,7 @@ anypytools = {path= "."} [package] name = "anypytools" -version="1.19.2" +version="1.20.0" [package.build] backend = { name = "pixi-build-python", version = "*" } From eddef46159b57b4387f5769bdc1bb3b6b6fd7442 Mon Sep 17 00:00:00 2001 From: Morten Enemark Lund Date: Wed, 19 Nov 2025 13:27:39 +0100 Subject: [PATCH 4/7] Update lock file --- pixi.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pixi.lock b/pixi.lock index f970c13..dfc9995 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1548,7 +1548,7 @@ packages: timestamp: 1758634638734 - conda: . name: anypytools - version: 1.19.2 + version: 1.20.0 build: pyh4616a5c_0 subdir: noarch depends: @@ -1566,7 +1566,7 @@ packages: - pywin32 license: MIT input: - hash: 7e2d0d0a7d3047761d10a95adebb4c0fcb16d6bf9d24ce308e404018832af082 + hash: 9fa6ba0badfc0ba8f381d093d418ecf329742d29ac1f91dbaf351feb6402988f globs: - pyproject.toml - conda: https://prefix.dev/conda-forge/win-64/aom-3.9.1-he0c23c2_0.conda From afa5d3857a3c07eca1016a1fb52ab58443888d4a Mon Sep 17 00:00:00 2001 From: Morten Enemark Lund Date: Wed, 19 Nov 2025 13:32:25 +0100 Subject: [PATCH 5/7] Update anypytools/abcutils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- anypytools/abcutils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/anypytools/abcutils.py b/anypytools/abcutils.py index 02d4a7c..f8c0b0f 100644 --- a/anypytools/abcutils.py +++ b/anypytools/abcutils.py @@ -1011,4 +1011,5 @@ def cleanup_logfiles(self, tasklist): def __del__(self): """Destructor to clean up any remaining subprocesses.""" - self._local_subprocess_container.stop_all() + if hasattr(self, '_local_subprocess_container'): + self._local_subprocess_container.stop_all() From 9ea2b57e40cdacae410462d7f6b6cb4aee807bf5 Mon Sep 17 00:00:00 2001 From: Morten Enemark Lund Date: Wed, 19 Nov 2025 13:32:41 +0100 Subject: [PATCH 6/7] Update anypytools/abcutils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- anypytools/abcutils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anypytools/abcutils.py b/anypytools/abcutils.py index f8c0b0f..57925b8 100644 --- a/anypytools/abcutils.py +++ b/anypytools/abcutils.py @@ -61,8 +61,8 @@ if ON_WINDOWS: if "ANYPYTOOLS_DEBUG_USE_PYTHON_POPEN" in os.environ: - print( - "[yellow]Warning: Using Python's subprocess.Popen instead of JobPopen.[/yellow]" + logger.warning( + "Warning: Using Python's subprocess.Popen instead of JobPopen." ) from subprocess import Popen else: From 42afc5a829ca746fef48f626ce915c263314a34f Mon Sep 17 00:00:00 2001 From: Morten Enemark Lund Date: Wed, 19 Nov 2025 13:39:56 +0100 Subject: [PATCH 7/7] black formatting --- anypytools/abcutils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/anypytools/abcutils.py b/anypytools/abcutils.py index 57925b8..3baa926 100644 --- a/anypytools/abcutils.py +++ b/anypytools/abcutils.py @@ -61,9 +61,7 @@ if ON_WINDOWS: if "ANYPYTOOLS_DEBUG_USE_PYTHON_POPEN" in os.environ: - logger.warning( - "Warning: Using Python's subprocess.Popen instead of JobPopen." - ) + logger.warning("Warning: Using Python's subprocess.Popen instead of JobPopen.") from subprocess import Popen else: from .jobpopen import JobPopen as Popen @@ -1011,5 +1009,5 @@ def cleanup_logfiles(self, tasklist): def __del__(self): """Destructor to clean up any remaining subprocesses.""" - if hasattr(self, '_local_subprocess_container'): + if hasattr(self, "_local_subprocess_container"): self._local_subprocess_container.stop_all()