|
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | from concurrent.futures import Future, ThreadPoolExecutor, TimeoutError |
| 3 | +try: |
| 4 | + import ctypes |
| 5 | + HAS_CTYPES = True |
| 6 | +except ImportError: |
| 7 | + HAS_CTYPES = False |
3 | 8 | from datetime import datetime |
4 | 9 | import platform |
5 | 10 | import threading |
|
12 | 17 | from chaoslib.activity import run_activities |
13 | 18 | from chaoslib.control import initialize_controls, controls, cleanup_controls, \ |
14 | 19 | Control, initialize_global_controls, cleanup_global_controls |
15 | | -from chaoslib.exceptions import ChaosException, InterruptExecution |
| 20 | +from chaoslib.exceptions import ChaosException, ExperimentExitedException, \ |
| 21 | + InterruptExecution |
16 | 22 | from chaoslib.exit import exit_signals |
17 | 23 | from chaoslib.configuration import load_configuration |
18 | 24 | from chaoslib.hypothesis import run_steady_state_hypothesis |
@@ -754,10 +760,11 @@ def apply_activities(experiment: Experiment, configuration: Configuration, |
754 | 760 | logger.debug("Waiting for background activities to complete") |
755 | 761 | pool.shutdown(wait=True) |
756 | 762 | elif pool: |
| 763 | + harshly_terminate_pending_background_activities(pool) |
757 | 764 | logger.debug( |
758 | 765 | "Do not wait for the background activities to finish " |
759 | 766 | "as per signal") |
760 | | - background_activity_timeout = 0.1 |
| 767 | + background_activity_timeout = 0.2 |
761 | 768 | pool.shutdown(wait=False) |
762 | 769 |
|
763 | 770 | for index, run in enumerate(runs): |
@@ -828,3 +835,39 @@ def has_steady_state_hypothesis_with_probes(experiment: Experiment) -> bool: |
828 | 835 | if probes: |
829 | 836 | return len(probes) > 0 |
830 | 837 | return False |
| 838 | + |
| 839 | + |
| 840 | +def harshly_terminate_pending_background_activities( |
| 841 | + pool: ThreadPoolExecutor) -> None: |
| 842 | + """ |
| 843 | + Ugly hack to try to force background activities to terminate now. |
| 844 | +
|
| 845 | + This cano only have an impact over functions that are still in the Python |
| 846 | + land. Any code outside of the Python VM (say calling a C function, even |
| 847 | + time.sleep()) will not be impacted and therefore will continue hanging |
| 848 | + until it does complete of its own accord. |
| 849 | +
|
| 850 | + This could have really bizarre side effects so it's only applied when |
| 851 | + a SIGUSR2 signal was received. |
| 852 | + """ |
| 853 | + if not HAS_CTYPES: |
| 854 | + logger.debug( |
| 855 | + "Your Python implementation does not provide the `ctypes` " |
| 856 | + "module and we cannot terminate very harshly running background " |
| 857 | + "activities.") |
| 858 | + return |
| 859 | + |
| 860 | + logger.debug( |
| 861 | + "Harshly trying to interrupt remaining background activities still " |
| 862 | + "running") |
| 863 | + |
| 864 | + # oh and of course we use private properties... might as well when trying |
| 865 | + # to be ugly |
| 866 | + for thread in pool._threads: |
| 867 | + tid = ctypes.c_long(thread.ident) |
| 868 | + try: |
| 869 | + gil = ctypes.pythonapi.PyGILState_Ensure() |
| 870 | + ctypes.pythonapi.PyThreadState_SetAsyncExc( |
| 871 | + tid, ctypes.py_object(ExperimentExitedException)) |
| 872 | + finally: |
| 873 | + ctypes.pythonapi.PyGILState_Release(gil) |
0 commit comments