hypershell 2.8.0__tar.gz → 2.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hypershell-2.8.0 → hypershell-2.8.2}/PKG-INFO +2 -2
- {hypershell-2.8.0 → hypershell-2.8.2}/README.rst +1 -1
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_update_desc.rst +3 -1
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_update_help.rst +3 -2
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/index.rst +1 -1
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/install.rst +19 -17
- {hypershell-2.8.0 → hypershell-2.8.2}/pyproject.toml +1 -1
- {hypershell-2.8.0 → hypershell-2.8.2}/share/man/man1/hs.1 +7 -4
- {hypershell-2.8.0 → hypershell-2.8.2}/share/man/man1/hsx.1 +7 -4
- {hypershell-2.8.0 → hypershell-2.8.2}/share/man/man1/hyper-shell.1 +7 -4
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/config.py +1 -1
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/exceptions.py +19 -1
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/data/model.py +24 -5
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/task.py +12 -4
- hypershell-2.8.2/tests/test_cancel.py +100 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_submit.py +22 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/uv.lock +154 -129
- {hypershell-2.8.0 → hypershell-2.8.2}/.gitignore +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/CODE_OF_CONDUCT.md +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/LICENSE +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/Makefile +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/citation.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/client_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/client_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/client_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/cluster_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/cluster_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/cluster_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_edit_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_edit_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_edit_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_get_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_get_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_get_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_intro.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_intro_alt.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_param_ref.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_pythonpath.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_set_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_set_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_set_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_site_vars.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_task_env.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_task_env_alt.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_which_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_which_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/config_which_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/database.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/database_alt.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/exit_status.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/initdb_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/initdb_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/initdb_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/license.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/security_alt.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/server_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/server_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/server_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/signals.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/submit_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/submit_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/submit_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_groups_alt.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_info_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_info_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_info_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_run_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_run_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_run_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_search_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_search_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_search_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_submit_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_submit_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_submit_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_update_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_wait_desc.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_wait_help.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/task_wait_usage.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/templates.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/templates_alt.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_include/tls_cli_options.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_static/custom.css +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_static/logo-dark-mode.png +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/_static/logo-light-mode.png +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/api/client.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/api/cluster.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/api/data.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/api/index.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/api/server.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/api/submit.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20230329_2_2_0_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20230413_2_3_0_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20230602_2_4_0_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20240518_2_5_0_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20240706_2_5_2_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20241115_2_6_0_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20241115_announce_logo.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20241231_2_6_1_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20250215_2_6_5_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20250405_2_6_6_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20250504_2_7_0_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/20260705_2_8_0_release.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/blog/index.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/client.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/cluster.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/config.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/config_edit.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/config_get.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/config_set.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/config_which.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/index.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/initdb.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/server.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/submit.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/task_info.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/task_run.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/task_search.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/task_submit.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/task_update.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/cli/task_wait.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/conf.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/config.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/database.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/getting_started.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/logging.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/manual.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/roadmap.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/security.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/templates.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/tutorial/advanced.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/tutorial/basic.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/tutorial/distributed.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/docs/tutorial/hybrid.rst +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/share/bash_completion.d/hs +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/share/bash_completion.d/hsx +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/share/zsh/site-functions/_hs +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/__init__.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/client.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/cluster/__init__.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/cluster/local.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/cluster/remote.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/cluster/ssh.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/config.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/__init__.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/fsm.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/heartbeat.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/logging.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/platform.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/pretty_print.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/queue.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/remote.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/resource.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/signal.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/sys.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/tag.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/template.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/thread.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/tls.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/types.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/core/uuid.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/data/__init__.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/data/core.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/server.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/src/hypershell/submit.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/__init__.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/conftest.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_autoscale.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_cluster.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_data/__init__.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_data/test_core.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_data/test_model.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_groups.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_initdb.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_logging.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_main.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_queue_tls.py +0 -0
- {hypershell-2.8.0 → hypershell-2.8.2}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypershell
|
|
3
|
-
Version: 2.8.
|
|
3
|
+
Version: 2.8.2
|
|
4
4
|
Summary: A cross-platform, high-throughput computing utility for processing shell commands over a distributed, asynchronous queue.
|
|
5
5
|
Project-URL: Homepage, https://hypershell.org
|
|
6
6
|
Project-URL: Documentation, https://hypershell.readthedocs.io
|
|
@@ -54,7 +54,7 @@ HyperShell v2: Distributed Task Execution for HPC
|
|
|
54
54
|
:target: https://github.com/hypershell/hypershell/releases
|
|
55
55
|
:alt: Github Release
|
|
56
56
|
|
|
57
|
-
.. image:: https://img.shields.io/badge/Python-3.
|
|
57
|
+
.. image:: https://img.shields.io/badge/Python-3.11+-blue.svg
|
|
58
58
|
:target: https://www.python.org/downloads
|
|
59
59
|
:alt: Python Versions
|
|
60
60
|
|
|
@@ -9,7 +9,7 @@ HyperShell v2: Distributed Task Execution for HPC
|
|
|
9
9
|
:target: https://github.com/hypershell/hypershell/releases
|
|
10
10
|
:alt: Github Release
|
|
11
11
|
|
|
12
|
-
.. image:: https://img.shields.io/badge/Python-3.
|
|
12
|
+
.. image:: https://img.shields.io/badge/Python-3.11+-blue.svg
|
|
13
13
|
:target: https://www.python.org/downloads
|
|
14
14
|
:alt: Python Versions
|
|
15
15
|
|
|
@@ -4,7 +4,9 @@ Include any number of `FIELD=VALUE` or tag `KEY:VALUE` positional arguments.
|
|
|
4
4
|
|
|
5
5
|
The ``-w``/``--where`` and ``-t``/``--with-tag`` operate just as in the search command.
|
|
6
6
|
|
|
7
|
-
Using ``--cancel``
|
|
7
|
+
Using ``--cancel`` marks tasks as cancelled: `schedule_time` and `completion_time`
|
|
8
|
+
are set to now and `exit_status` to -1. A cancelled task is terminal - it will not be
|
|
9
|
+
scheduled, retried, or reverted on ``--restart``.
|
|
8
10
|
|
|
9
11
|
Using ``--revert`` reverts everything as if the task was new again.
|
|
10
12
|
|
|
@@ -10,8 +10,9 @@ Options
|
|
|
10
10
|
``--cancel``
|
|
11
11
|
Cancel specified tasks.
|
|
12
12
|
|
|
13
|
-
Cancelling a task means it will no longer be scheduled.
|
|
14
|
-
|
|
13
|
+
Cancelling a task means it will no longer be scheduled. This is done by setting the
|
|
14
|
+
`schedule_time` and `completion_time` to `now` and the `exit_status` to -1. The task
|
|
15
|
+
becomes terminal: it will not be scheduled, retried, or reverted on ``--restart``.
|
|
15
16
|
A task cannot be cancelled after it is sent to remote clients.
|
|
16
17
|
|
|
17
18
|
``--revert``
|
|
@@ -11,7 +11,7 @@ Release v\ |release| (:ref:`Getting Started <getting_started>`)
|
|
|
11
11
|
:target: https://github.com/hypershell/hypershell/releases
|
|
12
12
|
:alt: Github Release
|
|
13
13
|
|
|
14
|
-
.. image:: https://img.shields.io/badge/Python-3.
|
|
14
|
+
.. image:: https://img.shields.io/badge/Python-3.11+-blue.svg
|
|
15
15
|
:target: https://www.python.org/downloads
|
|
16
16
|
:alt: Python Versions
|
|
17
17
|
|
|
@@ -257,11 +257,9 @@ managed by some `module` system.
|
|
|
257
257
|
|
|
258
258
|
Here we will create an isolated prefix for the installation with version number included
|
|
259
259
|
and only expose the entry-point scripts to users, along with shell completions and the
|
|
260
|
-
manual page.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
`conda-forge <https://conda-forge.org>`_ (or `mamba <https://mamba.readthedocs.io>`_)
|
|
264
|
-
or ``uv`` directly to create a virtual environment.
|
|
260
|
+
manual page. We can either use an existing Python runtime installed externally, otherwise
|
|
261
|
+
`conda-forge <https://conda-forge.org>`_ has the added benefit of complete isolation and
|
|
262
|
+
the ``psycopg-c`` dependencies for maximum security and performance.
|
|
265
263
|
|
|
266
264
|
.. admonition:: Create installation manually on a shared system
|
|
267
265
|
:class: note
|
|
@@ -271,15 +269,16 @@ or ``uv`` directly to create a virtual environment.
|
|
|
271
269
|
mkdir -p /apps/x86_64-any/hypershell/$VERSION
|
|
272
270
|
cd /apps/x86_64-any/hypershell/$VERSION
|
|
273
271
|
|
|
274
|
-
|
|
272
|
+
PKG='hypershell[postgres-system,uuid7,cron,zstd]'
|
|
273
|
+
URL='https://github.com/hypershell/hypershell'
|
|
275
274
|
|
|
276
|
-
python3.
|
|
277
|
-
libexec/bin/pip install
|
|
275
|
+
python3.14 -m venv libexec
|
|
276
|
+
libexec/bin/pip install "$PKG @ git+$URL@$VERSION"
|
|
278
277
|
|
|
279
278
|
mkdir -p bin
|
|
280
|
-
|
|
281
|
-
ln -sf ../libexec/bin/
|
|
282
|
-
ln -sf
|
|
279
|
+
cd bin
|
|
280
|
+
ln -sf ../libexec/bin/hs
|
|
281
|
+
ln -sf ../libexec/bin/hsx
|
|
283
282
|
|
|
284
283
|
|
|
|
285
284
|
|
|
@@ -303,10 +302,13 @@ configuration file might then be:
|
|
|
303
302
|
|
|
304
303
|
prepend_path("PATH", pathJoin(modroot, "bin"))
|
|
305
304
|
prepend_path("MANPATH", pathJoin(modroot, "share", "man"))
|
|
306
|
-
prepend_path("FPATH", pathJoin(modroot, "share", "zsh", "site-functions")) -- Zsh completions
|
|
307
305
|
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
if myShellName() == "bash" then
|
|
307
|
+
source_sh("bash", pathJoin(modroot, "libexec", "share", "bash-completion", "completions", "hs"))
|
|
308
|
+
elseif myShellName() == "zsh" then
|
|
309
|
+
prepend_path("FPATH", pathJoin(modroot, "libexec", "share", "zsh", "site-functions"))
|
|
310
|
+
execute({ cmd = "autoload -Uz compinit && compinit", modeA = { "load" } })
|
|
311
|
+
end
|
|
310
312
|
|
|
311
313
|
Presumably, users would then be able to activate the software by loading the module as such.
|
|
312
314
|
|
|
@@ -344,9 +346,9 @@ And we can include the following paths in our frozen set.
|
|
|
344
346
|
|
|
345
347
|
.. code-block:: shell
|
|
346
348
|
|
|
347
|
-
/apps/x86_64-any/hypershell/<version>/libexec/lib/python3.
|
|
348
|
-
/apps/x86_64-any/hypershell/<version>/libexec/lib/python3.
|
|
349
|
-
/apps/x86_64-any/hypershell/<version>/libexec/lib/python3.
|
|
349
|
+
/apps/x86_64-any/hypershell/<version>/libexec/lib/python3.14
|
|
350
|
+
/apps/x86_64-any/hypershell/<version>/libexec/lib/python3.14/lib-dynload
|
|
351
|
+
/apps/x86_64-any/hypershell/<version>/libexec/lib/python3.14/site-packages
|
|
350
352
|
|
|
351
353
|
|
|
352
354
|
------
|
|
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
|
28
28
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
29
29
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
|
30
30
|
..
|
|
31
|
-
.TH "HS" "1" "Jul
|
|
31
|
+
.TH "HS" "1" "Jul 22, 2026" "2.8.2" "hypershell"
|
|
32
32
|
.SH NAME
|
|
33
33
|
hs \- Process shell commands over a distributed, asynchronous queue
|
|
34
34
|
.SH SYNOPSIS
|
|
@@ -1360,7 +1360,9 @@ Include any number of \fIFIELD=VALUE\fP or tag \fIKEY:VALUE\fP positional argume
|
|
|
1360
1360
|
.sp
|
|
1361
1361
|
The \fB\-w\fP/\fB\-\-where\fP and \fB\-t\fP/\fB\-\-with\-tag\fP operate just as in the search command.
|
|
1362
1362
|
.sp
|
|
1363
|
-
Using \fB\-\-cancel\fP
|
|
1363
|
+
Using \fB\-\-cancel\fP marks tasks as cancelled: \fIschedule_time\fP and \fIcompletion_time\fP
|
|
1364
|
+
are set to now and \fIexit_status\fP to \-1. A cancelled task is terminal \- it will not be
|
|
1365
|
+
scheduled, retried, or reverted on \fB\-\-restart\fP\&.
|
|
1364
1366
|
.sp
|
|
1365
1367
|
Using \fB\-\-revert\fP reverts everything as if the task was new again.
|
|
1366
1368
|
.sp
|
|
@@ -1380,8 +1382,9 @@ Assignment pairs for update.
|
|
|
1380
1382
|
.B \fB\-\-cancel\fP
|
|
1381
1383
|
Cancel specified tasks.
|
|
1382
1384
|
.sp
|
|
1383
|
-
Cancelling a task means it will no longer be scheduled.
|
|
1384
|
-
|
|
1385
|
+
Cancelling a task means it will no longer be scheduled. This is done by setting the
|
|
1386
|
+
\fIschedule_time\fP and \fIcompletion_time\fP to \fInow\fP and the \fIexit_status\fP to \-1. The task
|
|
1387
|
+
becomes terminal: it will not be scheduled, retried, or reverted on \fB\-\-restart\fP\&.
|
|
1385
1388
|
A task cannot be cancelled after it is sent to remote clients.
|
|
1386
1389
|
.TP
|
|
1387
1390
|
.B \fB\-\-revert\fP
|
|
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
|
28
28
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
29
29
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
|
30
30
|
..
|
|
31
|
-
.TH "HS" "1" "Jul
|
|
31
|
+
.TH "HS" "1" "Jul 22, 2026" "2.8.2" "hypershell"
|
|
32
32
|
.SH NAME
|
|
33
33
|
hs \- Process shell commands over a distributed, asynchronous queue
|
|
34
34
|
.SH SYNOPSIS
|
|
@@ -1360,7 +1360,9 @@ Include any number of \fIFIELD=VALUE\fP or tag \fIKEY:VALUE\fP positional argume
|
|
|
1360
1360
|
.sp
|
|
1361
1361
|
The \fB\-w\fP/\fB\-\-where\fP and \fB\-t\fP/\fB\-\-with\-tag\fP operate just as in the search command.
|
|
1362
1362
|
.sp
|
|
1363
|
-
Using \fB\-\-cancel\fP
|
|
1363
|
+
Using \fB\-\-cancel\fP marks tasks as cancelled: \fIschedule_time\fP and \fIcompletion_time\fP
|
|
1364
|
+
are set to now and \fIexit_status\fP to \-1. A cancelled task is terminal \- it will not be
|
|
1365
|
+
scheduled, retried, or reverted on \fB\-\-restart\fP\&.
|
|
1364
1366
|
.sp
|
|
1365
1367
|
Using \fB\-\-revert\fP reverts everything as if the task was new again.
|
|
1366
1368
|
.sp
|
|
@@ -1380,8 +1382,9 @@ Assignment pairs for update.
|
|
|
1380
1382
|
.B \fB\-\-cancel\fP
|
|
1381
1383
|
Cancel specified tasks.
|
|
1382
1384
|
.sp
|
|
1383
|
-
Cancelling a task means it will no longer be scheduled.
|
|
1384
|
-
|
|
1385
|
+
Cancelling a task means it will no longer be scheduled. This is done by setting the
|
|
1386
|
+
\fIschedule_time\fP and \fIcompletion_time\fP to \fInow\fP and the \fIexit_status\fP to \-1. The task
|
|
1387
|
+
becomes terminal: it will not be scheduled, retried, or reverted on \fB\-\-restart\fP\&.
|
|
1385
1388
|
A task cannot be cancelled after it is sent to remote clients.
|
|
1386
1389
|
.TP
|
|
1387
1390
|
.B \fB\-\-revert\fP
|
|
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
|
28
28
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
29
29
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
|
30
30
|
..
|
|
31
|
-
.TH "HYPER-SHELL" "1" "Jul
|
|
31
|
+
.TH "HYPER-SHELL" "1" "Jul 22, 2026" "2.8.2" "hypershell"
|
|
32
32
|
.SH NAME
|
|
33
33
|
hyper-shell \- Process shell commands over a distributed, asynchronous queue
|
|
34
34
|
.SH SYNOPSIS
|
|
@@ -1360,7 +1360,9 @@ Include any number of \fIFIELD=VALUE\fP or tag \fIKEY:VALUE\fP positional argume
|
|
|
1360
1360
|
.sp
|
|
1361
1361
|
The \fB\-w\fP/\fB\-\-where\fP and \fB\-t\fP/\fB\-\-with\-tag\fP operate just as in the search command.
|
|
1362
1362
|
.sp
|
|
1363
|
-
Using \fB\-\-cancel\fP
|
|
1363
|
+
Using \fB\-\-cancel\fP marks tasks as cancelled: \fIschedule_time\fP and \fIcompletion_time\fP
|
|
1364
|
+
are set to now and \fIexit_status\fP to \-1. A cancelled task is terminal \- it will not be
|
|
1365
|
+
scheduled, retried, or reverted on \fB\-\-restart\fP\&.
|
|
1364
1366
|
.sp
|
|
1365
1367
|
Using \fB\-\-revert\fP reverts everything as if the task was new again.
|
|
1366
1368
|
.sp
|
|
@@ -1380,8 +1382,9 @@ Assignment pairs for update.
|
|
|
1380
1382
|
.B \fB\-\-cancel\fP
|
|
1381
1383
|
Cancel specified tasks.
|
|
1382
1384
|
.sp
|
|
1383
|
-
Cancelling a task means it will no longer be scheduled.
|
|
1384
|
-
|
|
1385
|
+
Cancelling a task means it will no longer be scheduled. This is done by setting the
|
|
1386
|
+
\fIschedule_time\fP and \fIcompletion_time\fP to \fInow\fP and the \fIexit_status\fP to \-1. The task
|
|
1387
|
+
becomes terminal: it will not be scheduled, retried, or reverted on \fB\-\-restart\fP\&.
|
|
1385
1388
|
A task cannot be cancelled after it is sent to remote clients.
|
|
1386
1389
|
.TP
|
|
1387
1390
|
.B \fB\-\-revert\fP
|
|
@@ -225,7 +225,7 @@ def partial_load(system: Optional[str] = path.system.config,
|
|
|
225
225
|
'default': default, **preload,
|
|
226
226
|
'system': {} if not system else load_file(system),
|
|
227
227
|
'user': {} if not user else load_file(user),
|
|
228
|
-
'local': {} if not
|
|
228
|
+
'local': {} if not local else load_file(local),
|
|
229
229
|
'env': load_env(),
|
|
230
230
|
})
|
|
231
231
|
|
|
@@ -28,7 +28,7 @@ from hypershell.core.platform import default_path
|
|
|
28
28
|
# Public interface
|
|
29
29
|
__all__ = ['display_warning', 'display_error', 'display_critical', 'traceback_filepath', 'write_traceback',
|
|
30
30
|
'handle_exception', 'handle_exception_silently', 'handle_disconnect', 'handle_address_unknown',
|
|
31
|
-
'HostAddressInfo', 'DatabaseUninitialized',
|
|
31
|
+
'handle_broken_pipe', 'HostAddressInfo', 'DatabaseUninitialized',
|
|
32
32
|
'get_shared_exception_mapping', ]
|
|
33
33
|
|
|
34
34
|
|
|
@@ -96,6 +96,24 @@ def handle_exception_silently(exc: Exception) -> int:
|
|
|
96
96
|
return status
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
def handle_broken_pipe(exc: Exception) -> int: # noqa: unused exc
|
|
100
|
+
"""Handle a downstream reader closing the pipe early (e.g. `hs list | head`).
|
|
101
|
+
|
|
102
|
+
Python replaces the POSIX ``SIGPIPE`` with a `BrokenPipeError` rather than terminating the
|
|
103
|
+
process, so we catch it here instead of dumping a traceback - the consumer simply stopped
|
|
104
|
+
reading, which is not an error. A bare exit would raise a *second* `BrokenPipeError` when the
|
|
105
|
+
interpreter flushes ``stdout`` at shutdown, so we redirect the ``stdout`` file descriptor to
|
|
106
|
+
the null device to discard that final flush. Cross-platform: no reliance on ``SIGPIPE``
|
|
107
|
+
(absent on Windows), and the process signal disposition is left untouched.
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
devnull = os.open(os.devnull, os.O_WRONLY)
|
|
111
|
+
os.dup2(devnull, sys.stdout.fileno())
|
|
112
|
+
except (OSError, ValueError):
|
|
113
|
+
pass # stdout may lack a real file descriptor (already closed or replaced)
|
|
114
|
+
return exit_status.success
|
|
115
|
+
|
|
116
|
+
|
|
99
117
|
def handle_address_unknown(exc: Exception, # noqa: unused
|
|
100
118
|
logger: logging.Logger,
|
|
101
119
|
status: int = exit_status.runtime_error) -> int:
|
|
@@ -25,7 +25,7 @@ from sqlalchemy.dialects.postgresql import SMALLINT, UUID as POSTGRES_UUID, JSON
|
|
|
25
25
|
# Internal libs
|
|
26
26
|
from hypershell.core.logging import Logger, HOSTNAME, INSTANCE
|
|
27
27
|
from hypershell.core.heartbeat import Heartbeat
|
|
28
|
-
from hypershell.core.types import JSONData, to_json_type, from_json_type, serialize, deserialize
|
|
28
|
+
from hypershell.core.types import JSONData, to_json_type, from_json_type, serialize, deserialize, parse_bytes
|
|
29
29
|
from hypershell.core.uuid import uuid
|
|
30
30
|
from hypershell.core.tag import Tag
|
|
31
31
|
from hypershell.data.core import schema, Session
|
|
@@ -34,7 +34,7 @@ from hypershell.data.core import schema, Session
|
|
|
34
34
|
__all__ = [
|
|
35
35
|
'Task', 'Client', 'Entity', 'to_json_type', 'from_json_type', 'serialize_tasks', 'deserialize_tasks',
|
|
36
36
|
'UUID', 'TEXT', 'INTEGER', 'SMALL_INTEGER', 'DATETIME', 'BOOLEAN', 'JSON',
|
|
37
|
-
'DEFAULT_TASK_GROUP', 'TaskGroupInfo',
|
|
37
|
+
'DEFAULT_TASK_GROUP', 'CANCEL_STATUS', 'TaskGroupInfo',
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
# Initialize logger
|
|
@@ -45,6 +45,15 @@ log = Logger.with_name(__name__)
|
|
|
45
45
|
DEFAULT_TASK_GROUP: Final[int] = 0
|
|
46
46
|
"""Default task group for backwards compatibility."""
|
|
47
47
|
|
|
48
|
+
CANCEL_STATUS: Final[int] = -1
|
|
49
|
+
"""Sentinel `exit_status` marking a cancelled task.
|
|
50
|
+
|
|
51
|
+
A cancelled task is terminal: it must never be scheduled, retried, or counted as an
|
|
52
|
+
unrecoverable failure. It is distinguished from a genuine failure (positive exit codes,
|
|
53
|
+
or the negative template/resource sentinels in `client.py`) solely by this value, so the
|
|
54
|
+
scheduler's failure and retry queries filter it out explicitly.
|
|
55
|
+
"""
|
|
56
|
+
|
|
48
57
|
|
|
49
58
|
class DatabaseError(Exception):
|
|
50
59
|
"""Generic database-related exception."""
|
|
@@ -335,7 +344,11 @@ class Task(Entity):
|
|
|
335
344
|
tag = {**(tag or {}), **inline_tags, **{'part': 0, }}
|
|
336
345
|
other['group'] = tag.pop('group', group)
|
|
337
346
|
other['cores'] = tag.pop('cores', other.get('cores', None))
|
|
338
|
-
|
|
347
|
+
# A memory value may arrive as a unit-bearing string ('2GB') from an inline
|
|
348
|
+
# `memory:` tag (parsed by smart_coerce, which leaves units alone) — parse it to
|
|
349
|
+
# integer bytes as the `--memory` flag does, so resource accounting stays numeric.
|
|
350
|
+
memory = tag.pop('memory', other.get('memory', None))
|
|
351
|
+
other['memory'] = parse_bytes(memory) if isinstance(memory, str) else memory
|
|
339
352
|
other['timeout'] = tag.pop('timeout', other.get('timeout', None))
|
|
340
353
|
return Task(id=uuid(), args=args,
|
|
341
354
|
submit_id=INSTANCE, submit_host=HOSTNAME, submit_time=datetime.now().astimezone(),
|
|
@@ -437,6 +450,7 @@ class Task(Entity):
|
|
|
437
450
|
.filter(Task.group == previous_group)
|
|
438
451
|
.filter(cls.exit_status.isnot(None))
|
|
439
452
|
.filter(cls.exit_status != 0)
|
|
453
|
+
.filter(cls.exit_status != CANCEL_STATUS) # NOTE: cancelled is terminal, not a failure
|
|
440
454
|
.filter(cls.attempt >= attempts)
|
|
441
455
|
.filter(cls.retried.is_(False))
|
|
442
456
|
)
|
|
@@ -472,6 +486,7 @@ class Task(Entity):
|
|
|
472
486
|
.order_by(cls.completion_time)
|
|
473
487
|
.filter(cls.exit_status.isnot(None))
|
|
474
488
|
.filter(cls.exit_status != 0)
|
|
489
|
+
.filter(cls.exit_status != CANCEL_STATUS) # NOTE: cancelled is terminal, not a failure
|
|
475
490
|
.filter(cls.attempt < attempts)
|
|
476
491
|
.filter(cls.retried.is_(False)))
|
|
477
492
|
if group is not None:
|
|
@@ -605,11 +620,15 @@ class Task(Entity):
|
|
|
605
620
|
@classmethod
|
|
606
621
|
def cancel_all(cls: Type[Task], ids: List[str]) -> None:
|
|
607
622
|
"""Cancel all tasks identified by `ids`."""
|
|
623
|
+
# NOTE: completion_time is set so the task is terminal - otherwise it reads as
|
|
624
|
+
# "interrupted" (scheduled but incomplete) and gets reverted/re-run on restart.
|
|
625
|
+
now = datetime.now().astimezone()
|
|
608
626
|
cls.update_all([
|
|
609
627
|
{
|
|
610
628
|
'id': id,
|
|
611
|
-
'schedule_time':
|
|
612
|
-
'
|
|
629
|
+
'schedule_time': now,
|
|
630
|
+
'completion_time': now,
|
|
631
|
+
'exit_status': CANCEL_STATUS,
|
|
613
632
|
}
|
|
614
633
|
for id in ids
|
|
615
634
|
])
|
|
@@ -37,14 +37,15 @@ from sqlalchemy.sql.elements import BinaryExpression
|
|
|
37
37
|
# Internal libs
|
|
38
38
|
from hypershell.core.platform import default_path
|
|
39
39
|
from hypershell.core.config import config
|
|
40
|
-
from hypershell.core.exceptions import handle_exception, handle_exception_silently,
|
|
40
|
+
from hypershell.core.exceptions import (handle_exception, handle_exception_silently, handle_broken_pipe,
|
|
41
|
+
get_shared_exception_mapping)
|
|
41
42
|
from hypershell.core.logging import Logger, HOSTNAME
|
|
42
43
|
from hypershell.core.remote import SSHConnection
|
|
43
44
|
from hypershell.core.types import smart_coerce, JSONData, to_json_type
|
|
44
45
|
from hypershell.core.pretty_print import format_tag, format_json, format_bytes
|
|
45
46
|
from hypershell.core.tag import Tag
|
|
46
47
|
from hypershell.data.core import Session
|
|
47
|
-
from hypershell.data.model import Task, JSON
|
|
48
|
+
from hypershell.data.model import Task, JSON, CANCEL_STATUS
|
|
48
49
|
from hypershell.data import ensuredb, DATABASE_DIALECT
|
|
49
50
|
|
|
50
51
|
# Public interface
|
|
@@ -177,6 +178,7 @@ class TaskInfoApp(Application):
|
|
|
177
178
|
|
|
178
179
|
exceptions = {
|
|
179
180
|
Task.NotFound: functools.partial(handle_exception, logger=log, status=exit_status.runtime_error),
|
|
181
|
+
BrokenPipeError: handle_broken_pipe, # e.g. `hs info UUID --stdout | head`
|
|
180
182
|
**get_shared_exception_mapping(__name__)
|
|
181
183
|
}
|
|
182
184
|
|
|
@@ -641,6 +643,7 @@ class TaskSearchApp(Application, SearchableMixin):
|
|
|
641
643
|
|
|
642
644
|
exceptions = {
|
|
643
645
|
StatementError: functools.partial(handle_exception, logger=log, status=exit_status.runtime_error),
|
|
646
|
+
BrokenPipeError: handle_broken_pipe, # e.g. `hs list --all --csv | head`
|
|
644
647
|
**get_shared_exception_mapping(__name__)
|
|
645
648
|
}
|
|
646
649
|
|
|
@@ -820,7 +823,8 @@ UPDATE_HELP = f"""\
|
|
|
820
823
|
Include any number of FIELD=VALUE or tag KEY:VALUE positional arguments.
|
|
821
824
|
The -w/--where and -t/--with-tag operate just as in the search command.
|
|
822
825
|
|
|
823
|
-
Using --cancel
|
|
826
|
+
Using --cancel marks tasks terminal (schedule_time and completion_time to now,
|
|
827
|
+
exit_status to {CANCEL_STATUS}); they will not be scheduled, retried, or reverted.
|
|
824
828
|
Using --revert reverts everything as if the task was new again.
|
|
825
829
|
Using --delete drops the row from the database entirely.
|
|
826
830
|
|
|
@@ -980,7 +984,11 @@ class TaskUpdateApp(Application, SearchableMixin):
|
|
|
980
984
|
prev_count = query.filter(Task.schedule_time.isnot(None)).count()
|
|
981
985
|
if prev_count > 0:
|
|
982
986
|
log.warning(f'{prev_count} cancelled tasks already scheduled')
|
|
983
|
-
|
|
987
|
+
# NOTE: completion_time is set so the task is terminal - otherwise it reads as
|
|
988
|
+
# "interrupted" (scheduled but incomplete) and gets reverted/re-run on restart.
|
|
989
|
+
now = datetime.now().astimezone()
|
|
990
|
+
field_updates['schedule_time'] = now
|
|
991
|
+
field_updates['completion_time'] = now
|
|
984
992
|
field_updates['exit_status'] = CANCEL_STATUS
|
|
985
993
|
|
|
986
994
|
if self.revert_mode:
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Geoffrey Lentner
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Test task cancellation is terminal and never re-scheduled by the server."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Type annotations
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
# Standard libs
|
|
11
|
+
import re
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# External libs
|
|
15
|
+
from pytest import mark
|
|
16
|
+
from cmdkit.app import exit_status
|
|
17
|
+
|
|
18
|
+
# Internal libs
|
|
19
|
+
from tests import main, main_lines, create_taskfile
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def submit_pending(temp_site: Path, count: int) -> None:
|
|
23
|
+
"""Submit `count` uniquely-tagged echo tasks to the database as pending (not run)."""
|
|
24
|
+
tasks = [f'echo RUN_{n} # HYPERSHELL: n:{n}' for n in range(count)]
|
|
25
|
+
taskfile = create_taskfile(temp_site, tasks)
|
|
26
|
+
rc, _, _ = main(['hs', 'submit', str(taskfile)])
|
|
27
|
+
assert rc == exit_status.success
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@mark.integration
|
|
31
|
+
def test_cancel_is_terminal(temp_site: Path) -> None:
|
|
32
|
+
"""Cancelling a task marks it terminal: exit_status is the sentinel and completion_time is set.
|
|
33
|
+
|
|
34
|
+
A cancelled task that only had `schedule_time`/`exit_status` set (but no `completion_time`)
|
|
35
|
+
reads as "interrupted" to the scheduler and gets reverted/re-run - so completion_time
|
|
36
|
+
must be populated for the cancellation to stick.
|
|
37
|
+
"""
|
|
38
|
+
submit_pending(temp_site, 3)
|
|
39
|
+
|
|
40
|
+
rc, _, _ = main(['hs', 'update', '--cancel', '-t', 'n:1', '--no-confirm'])
|
|
41
|
+
assert rc == exit_status.success
|
|
42
|
+
|
|
43
|
+
rc, out, _ = main_lines(['hs', 'list', 'exit_status', '-t', 'n:1'])
|
|
44
|
+
assert out == ['-1']
|
|
45
|
+
|
|
46
|
+
rc, out, _ = main_lines(['hs', 'list', 'completion_time', '-t', 'n:1'])
|
|
47
|
+
assert out not in (['null'], [''])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@mark.integration
|
|
51
|
+
def test_cancelled_task_not_reverted_on_restart(temp_site: Path) -> None:
|
|
52
|
+
"""A cancelled task must not be reverted and re-run by `hs cluster --restart`.
|
|
53
|
+
|
|
54
|
+
Regression: `--cancel` left `completion_time` NULL, so `revert_interrupted()` on restart
|
|
55
|
+
treated the cancellation-set `schedule_time` as an interruption, reverted the task, and
|
|
56
|
+
re-ran it (flipping exit_status from the cancel sentinel to 0).
|
|
57
|
+
"""
|
|
58
|
+
submit_pending(temp_site, 4)
|
|
59
|
+
|
|
60
|
+
rc, _, _ = main(['hs', 'update', '--cancel', '-t', 'n:2', '--no-confirm'])
|
|
61
|
+
assert rc == exit_status.success
|
|
62
|
+
|
|
63
|
+
rc, stdout, _ = main(['hs', 'cluster', '--restart'])
|
|
64
|
+
assert rc == exit_status.success
|
|
65
|
+
|
|
66
|
+
# The pending tasks run; the cancelled one does not.
|
|
67
|
+
assert set(re.findall(r'RUN_\d', stdout)) == {'RUN_0', 'RUN_1', 'RUN_3'}
|
|
68
|
+
assert 'RUN_2' not in stdout
|
|
69
|
+
|
|
70
|
+
# It remains cancelled, not completed successfully.
|
|
71
|
+
rc, out, _ = main_lines(['hs', 'list', 'exit_status', '-t', 'n:2'])
|
|
72
|
+
assert out == ['-1']
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@mark.integration
|
|
76
|
+
def test_cancelled_task_not_retried(temp_site: Path) -> None:
|
|
77
|
+
"""A cancelled task must not be picked up as a "failed" task for retry.
|
|
78
|
+
|
|
79
|
+
Regression: the retry query (`select_failed`) matched any `exit_status != 0`, so with
|
|
80
|
+
retries enabled the cancel sentinel (-1) looked like a failure and was re-scheduled.
|
|
81
|
+
"""
|
|
82
|
+
submit_pending(temp_site, 3)
|
|
83
|
+
|
|
84
|
+
rc, _, _ = main(['hs', 'update', '--cancel', '-t', 'n:2', '--no-confirm'])
|
|
85
|
+
assert rc == exit_status.success
|
|
86
|
+
|
|
87
|
+
# File-mode cluster (not --restart, so no revert path) with retries enabled: once the
|
|
88
|
+
# pending tasks are exhausted the failed-task retry query runs - it must skip the cancelled task.
|
|
89
|
+
extra = create_taskfile(temp_site, ['echo EXTRA # HYPERSHELL: m:0'], filename='extra.in')
|
|
90
|
+
rc, stdout, _ = main(['hs', 'cluster', str(extra), '-r2'])
|
|
91
|
+
assert rc == exit_status.success
|
|
92
|
+
|
|
93
|
+
assert 'RUN_2' not in stdout
|
|
94
|
+
|
|
95
|
+
rc, out, _ = main_lines(['hs', 'list', 'exit_status', '-t', 'n:2'])
|
|
96
|
+
assert out == ['-1']
|
|
97
|
+
|
|
98
|
+
# No retry copy was created (a retry would add a second row with attempt=2).
|
|
99
|
+
rc, out, _ = main_lines(['hs', 'list', 'attempt', '-t', 'n:2'])
|
|
100
|
+
assert out == ['1']
|
|
@@ -18,6 +18,7 @@ from cmdkit.app import exit_status
|
|
|
18
18
|
# Internal libs
|
|
19
19
|
from tests import main, main_lines, NO_OUTPUT, create_taskfile_echo, assert_output, UUID_PATTERN
|
|
20
20
|
from hypershell.submit import SubmitApp
|
|
21
|
+
from hypershell.data.model import Task
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
@mark.integration
|
|
@@ -213,3 +214,24 @@ def test_submit_bundled(temp_site: Path) -> None:
|
|
|
213
214
|
assert main_lines(['hs', 'list', 'args', '-f', 'plain', '-s', 'submit_time']) == (
|
|
214
215
|
exit_status.success, [f'echo {n}' for n in range(100)], NO_OUTPUT
|
|
215
216
|
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@mark.unit
|
|
220
|
+
def test_inline_memory_tag_parses_units_to_bytes() -> None:
|
|
221
|
+
"""An inline ``memory:<size>`` resource tag is parsed to integer bytes — like the ``--memory``
|
|
222
|
+
flag (parse_bytes) — not stored as a raw string.
|
|
223
|
+
|
|
224
|
+
smart_coerce (used for inline tag values) leaves unit-bearing strings like ``2GB`` untouched, so
|
|
225
|
+
without explicit parsing ``Task.memory`` would be a string and client-side resource accounting
|
|
226
|
+
(``memory_total < task.memory``, an int vs str comparison) crashes.
|
|
227
|
+
"""
|
|
228
|
+
# The reported form: an inline HYPERSHELL comment carrying a unit suffix.
|
|
229
|
+
task = Task.new(args='echo AAA #HYPERSHELL: cores:1 memory:2GB timeout:10')
|
|
230
|
+
assert task.memory == 2147483648 # 2 * 1024**3
|
|
231
|
+
assert task.cores == 1 and task.timeout == 10
|
|
232
|
+
# The tag= kwarg form resolves identically.
|
|
233
|
+
assert Task.new(args='echo', tag={'memory': '512MB'}).memory == 536870912
|
|
234
|
+
# Unit-less, already-integer, and absent values are unaffected (no regression).
|
|
235
|
+
assert Task.new(args='echo', tag={'memory': '2000'}).memory == 2000
|
|
236
|
+
assert Task.new(args='echo', tag={'memory': 4096}).memory == 4096
|
|
237
|
+
assert Task.new(args='echo').memory is None
|