task-scheduling 2.3.0__tar.gz → 2.3.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.
Files changed (50) hide show
  1. {task_scheduling-2.3.0/task_scheduling.egg-info → task_scheduling-2.3.2}/PKG-INFO +85 -4
  2. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/README.md +84 -3
  3. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/pyproject.toml +1 -1
  4. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/setup.py +1 -1
  5. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/control/__init__.py +2 -2
  6. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/control/pause_run.py +4 -4
  7. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/control/process_manager.py +9 -9
  8. task_scheduling-2.3.2/task_scheduling/control/terminate_run.py +109 -0
  9. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/control/thread_manager.py +9 -9
  10. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/scheduler/cpu_asyncio_task.py +5 -3
  11. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/scheduler/cpu_liner_task.py +10 -9
  12. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/scheduler/io_asyncio_task.py +1 -1
  13. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/scheduler/io_liner_task.py +10 -8
  14. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/scheduler/timer_task.py +17 -14
  15. {task_scheduling-2.3.0 → task_scheduling-2.3.2/task_scheduling.egg-info}/PKG-INFO +85 -4
  16. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling.egg-info/SOURCES.txt +1 -1
  17. task_scheduling-2.3.0/task_scheduling/control/skip_run.py +0 -39
  18. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/LICENSE +0 -0
  19. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/MANIFEST.in +0 -0
  20. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/setup.cfg +0 -0
  21. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/__init__.py +0 -0
  22. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/common/__init__.py +0 -0
  23. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/common/log_config.py +0 -0
  24. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/config/__init__.py +0 -0
  25. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/config/config.py +0 -0
  26. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/config/config.yaml +0 -0
  27. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/control/timeout_run.py +0 -0
  28. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/control/utils.py +0 -0
  29. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/manager/__init__.py +0 -0
  30. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/manager/task_details_queue.py +0 -0
  31. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/scheduler/__init__.py +0 -0
  32. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/scheduler_management.py +0 -0
  33. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/scheduler_tools/__init__.py +0 -0
  34. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/scheduler_tools/task_scheduler.py +0 -0
  35. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/task_creation.py +0 -0
  36. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/task_data/__init__.py +0 -0
  37. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/task_data/function_type.py +0 -0
  38. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/task_data/tag_added.py +0 -0
  39. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/task_info/__init__.py +0 -0
  40. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/task_info/control_ui.py +0 -0
  41. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/task_info/queue_info_display.py +0 -0
  42. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/utils/__init__.py +0 -0
  43. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/utils/is_async_function.py +0 -0
  44. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/utils/random_name.py +0 -0
  45. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/utils/sleep.py +0 -0
  46. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/utils/worker_initializer.py +0 -0
  47. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling/variable.py +0 -0
  48. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling.egg-info/dependency_links.txt +0 -0
  49. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling.egg-info/requires.txt +0 -0
  50. {task_scheduling-2.3.0 → task_scheduling-2.3.2}/task_scheduling.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: task_scheduling
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: It is mainly used for task scheduling
5
5
  Home-page: https://github.com/fallingmeteorite/task_scheduling
6
6
  Author: fallingmeteorite
@@ -81,7 +81,9 @@ pip install --upgrade task_scheduling
81
81
  ```
82
82
 
83
83
  # Interface UI
84
+
84
85
  Use the function in code
86
+
85
87
  ```
86
88
 
87
89
  from task_scheduling.task_info import start_task_status_ui
@@ -95,13 +97,16 @@ Task status UI available at http://localhost:8000
95
97
  # Function introduction
96
98
 
97
99
  ## Tips
100
+
98
101
  1.Detailed information requires changing the log level to `debug`.
99
102
 
100
- 2.This scheduler does not support terminating blocking tasks due to various factors, so it only supports terminating tasks by injecting exceptions.
103
+ 2.This scheduler does not support terminating blocking tasks due to various factors, so it only supports terminating
104
+ tasks by injecting exceptions.
101
105
 
102
106
  3.Most of the API calls for this task scheduling can be added to the scheduler for execution as tasks.
103
107
 
104
108
  4.The log display level is available
109
+
105
110
  ```
106
111
  from task_scheduling.common import set_log_level
107
112
 
@@ -110,7 +115,9 @@ set_log_level("DEBUG") # INFO, DEBUG, ERROR, WARNING
110
115
  if __name__ == "__main__":
111
116
  ......
112
117
  ```
113
- 5.The task priority is modified to high when necessary, and the task will start executing directly after submission. However, this can also cause some running tasks to pause to free up resources to run high-priority tasks
118
+
119
+ 5.The task priority is modified to high when necessary, and the task will start executing directly after submission.
120
+ However, this can also cause some running tasks to pause to free up resources to run high-priority tasks
114
121
 
115
122
  ### Function: task_creation(delay: int or None, daily_time: str or None, function_type: str, timeout_processing: bool, task_name: str, func: Callable, *args, **kwargs) -> str or None:
116
123
 
@@ -376,6 +383,78 @@ if __name__ == "__main__":
376
383
 
377
384
  ```
378
385
 
386
+ ### pause_and_resume_task(self, task_id: str, action: str) -> bool:
387
+
388
+ A function is used to pause a task and resume running at a specified time.
389
+
390
+ ```
391
+ import asyncio
392
+ import time
393
+
394
+ from task_scheduling.utils import interruptible_sleep
395
+
396
+
397
+ def line_task(input_info):
398
+ for i in range(10):
399
+ interruptible_sleep(1)
400
+ print(input_info)
401
+
402
+
403
+ async def asyncio_task(input_info):
404
+ for i in range(10):
405
+ await asyncio.sleep(1)
406
+ print(input_info)
407
+
408
+
409
+ input_info = "test"
410
+
411
+ from task_scheduling.common import set_log_level
412
+
413
+ set_log_level("DEBUG")
414
+ if __name__ == "__main__":
415
+ from task_scheduling.task_creation import task_creation, shutdown
416
+ from task_scheduling.task_info import start_task_status_ui
417
+ from task_scheduling.scheduler import io_liner_task
418
+
419
+ start_task_status_ui()
420
+ from task_scheduling.variable import *
421
+
422
+ task_id1 = task_creation(None,
423
+ # This is how long the delay is executed (in seconds)
424
+ # This parameter is required when the function_type is "timer",if this parameter is used, the daily_time is not required
425
+ None,
426
+ # This is to be performed at what point (24-hour clock)
427
+ # This parameter is required when the function_type is "timer",if this parameter is used, the delay is not required
428
+ scheduler_io,
429
+ # Running function type, there are "io, cpu, timer"
430
+ True,
431
+ # Set to True to enable timeout detection, tasks that do not finish within the runtime will be forcibly terminated
432
+ "task2",
433
+ # Task ID, in linear tasks, tasks with the same ID will be queued, different IDs will be executed directly, the same applies to asynchronous tasks
434
+ line_task,
435
+ # The function to be executed, parameters should not be passed here
436
+ priority_low,
437
+ # The task priority is modified to high when necessary, and the task will start executing directly after submission
438
+ input_info
439
+ # Pass the parameters required by the function, no restrictions
440
+ )
441
+
442
+ # cf478b6e-5e02-49b8-9031-4adc6ff915c2, cf478b6e-5e02-49b8-9031-4adc6ff915c2
443
+ time.sleep(2.0)
444
+ print("Pause")
445
+ io_liner_task.pause_and_resume_task(task_id1, "pause")
446
+ time.sleep(4.0)
447
+ print("Resume")
448
+ io_liner_task.pause_and_resume_task(task_id1, "resume")
449
+
450
+ try:
451
+ while True:
452
+ time.sleep(1.0)
453
+ except KeyboardInterrupt:
454
+ shutdown(True)
455
+
456
+ ```
457
+
379
458
  ### FunctionRunner(self, func: Callable, task_name: str, *args, **kwargs) -> None:
380
459
 
381
460
  A task that detects what type of function is executed. The detected parameters are stored in a file and read directly at
@@ -818,9 +897,11 @@ if __name__ == "__main__":
818
897
  ### force_stop_task(task_id: str) -> bool:
819
898
 
820
899
  All 4 schedulers have this function for terminating a running task. Warning: If the function is executing a
821
- process-blocking function such as `time.sleep`, it will have to wait for the execution to finish before terminating the function
900
+ process-blocking function such as `time.sleep`, it will have to wait for the execution to finish before terminating the
901
+ function
822
902
 
823
903
  Warn!!! `cpu_liner_task.force_stop_task()` is different and requires the addition of additional parameters
904
+
824
905
  ```
825
906
  import asyncio
826
907
  import time
@@ -58,7 +58,9 @@ pip install --upgrade task_scheduling
58
58
  ```
59
59
 
60
60
  # Interface UI
61
+
61
62
  Use the function in code
63
+
62
64
  ```
63
65
 
64
66
  from task_scheduling.task_info import start_task_status_ui
@@ -72,13 +74,16 @@ Task status UI available at http://localhost:8000
72
74
  # Function introduction
73
75
 
74
76
  ## Tips
77
+
75
78
  1.Detailed information requires changing the log level to `debug`.
76
79
 
77
- 2.This scheduler does not support terminating blocking tasks due to various factors, so it only supports terminating tasks by injecting exceptions.
80
+ 2.This scheduler does not support terminating blocking tasks due to various factors, so it only supports terminating
81
+ tasks by injecting exceptions.
78
82
 
79
83
  3.Most of the API calls for this task scheduling can be added to the scheduler for execution as tasks.
80
84
 
81
85
  4.The log display level is available
86
+
82
87
  ```
83
88
  from task_scheduling.common import set_log_level
84
89
 
@@ -87,7 +92,9 @@ set_log_level("DEBUG") # INFO, DEBUG, ERROR, WARNING
87
92
  if __name__ == "__main__":
88
93
  ......
89
94
  ```
90
- 5.The task priority is modified to high when necessary, and the task will start executing directly after submission. However, this can also cause some running tasks to pause to free up resources to run high-priority tasks
95
+
96
+ 5.The task priority is modified to high when necessary, and the task will start executing directly after submission.
97
+ However, this can also cause some running tasks to pause to free up resources to run high-priority tasks
91
98
 
92
99
  ### Function: task_creation(delay: int or None, daily_time: str or None, function_type: str, timeout_processing: bool, task_name: str, func: Callable, *args, **kwargs) -> str or None:
93
100
 
@@ -353,6 +360,78 @@ if __name__ == "__main__":
353
360
 
354
361
  ```
355
362
 
363
+ ### pause_and_resume_task(self, task_id: str, action: str) -> bool:
364
+
365
+ A function is used to pause a task and resume running at a specified time.
366
+
367
+ ```
368
+ import asyncio
369
+ import time
370
+
371
+ from task_scheduling.utils import interruptible_sleep
372
+
373
+
374
+ def line_task(input_info):
375
+ for i in range(10):
376
+ interruptible_sleep(1)
377
+ print(input_info)
378
+
379
+
380
+ async def asyncio_task(input_info):
381
+ for i in range(10):
382
+ await asyncio.sleep(1)
383
+ print(input_info)
384
+
385
+
386
+ input_info = "test"
387
+
388
+ from task_scheduling.common import set_log_level
389
+
390
+ set_log_level("DEBUG")
391
+ if __name__ == "__main__":
392
+ from task_scheduling.task_creation import task_creation, shutdown
393
+ from task_scheduling.task_info import start_task_status_ui
394
+ from task_scheduling.scheduler import io_liner_task
395
+
396
+ start_task_status_ui()
397
+ from task_scheduling.variable import *
398
+
399
+ task_id1 = task_creation(None,
400
+ # This is how long the delay is executed (in seconds)
401
+ # This parameter is required when the function_type is "timer",if this parameter is used, the daily_time is not required
402
+ None,
403
+ # This is to be performed at what point (24-hour clock)
404
+ # This parameter is required when the function_type is "timer",if this parameter is used, the delay is not required
405
+ scheduler_io,
406
+ # Running function type, there are "io, cpu, timer"
407
+ True,
408
+ # Set to True to enable timeout detection, tasks that do not finish within the runtime will be forcibly terminated
409
+ "task2",
410
+ # Task ID, in linear tasks, tasks with the same ID will be queued, different IDs will be executed directly, the same applies to asynchronous tasks
411
+ line_task,
412
+ # The function to be executed, parameters should not be passed here
413
+ priority_low,
414
+ # The task priority is modified to high when necessary, and the task will start executing directly after submission
415
+ input_info
416
+ # Pass the parameters required by the function, no restrictions
417
+ )
418
+
419
+ # cf478b6e-5e02-49b8-9031-4adc6ff915c2, cf478b6e-5e02-49b8-9031-4adc6ff915c2
420
+ time.sleep(2.0)
421
+ print("Pause")
422
+ io_liner_task.pause_and_resume_task(task_id1, "pause")
423
+ time.sleep(4.0)
424
+ print("Resume")
425
+ io_liner_task.pause_and_resume_task(task_id1, "resume")
426
+
427
+ try:
428
+ while True:
429
+ time.sleep(1.0)
430
+ except KeyboardInterrupt:
431
+ shutdown(True)
432
+
433
+ ```
434
+
356
435
  ### FunctionRunner(self, func: Callable, task_name: str, *args, **kwargs) -> None:
357
436
 
358
437
  A task that detects what type of function is executed. The detected parameters are stored in a file and read directly at
@@ -795,9 +874,11 @@ if __name__ == "__main__":
795
874
  ### force_stop_task(task_id: str) -> bool:
796
875
 
797
876
  All 4 schedulers have this function for terminating a running task. Warning: If the function is executing a
798
- process-blocking function such as `time.sleep`, it will have to wait for the execution to finish before terminating the function
877
+ process-blocking function such as `time.sleep`, it will have to wait for the execution to finish before terminating the
878
+ function
799
879
 
800
880
  Warn!!! `cpu_liner_task.force_stop_task()` is different and requires the addition of additional parameters
881
+
801
882
  ```
802
883
  import asyncio
803
884
  import time
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "task_scheduling"
3
- version = "2.3.0"
3
+ version = "2.3.2"
4
4
  description = "It is mainly used for task scheduling"
5
5
  authors = [
6
6
  { name = "fallingmeteorite", email = "2327667836@qq.com" }
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="task_scheduling",
5
- version="2.3.0",
5
+ version="2.3.2",
6
6
  description="It is mainly used for task scheduling",
7
7
  author="fallingmeteorite",
8
8
  author_email="2327667836@qq.com",
@@ -1,9 +1,9 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # Author: fallingmeteorite
3
3
  from .process_manager import ProcessTaskManager
4
- from .skip_run import skip_on_demand, StopException
4
+ from .terminate_run import ThreadTerminator, StopException
5
5
 
6
6
  from .thread_manager import ThreadTaskManager
7
7
  from .timeout_run import TimeoutException, ThreadingTimeout
8
8
 
9
- from .pause_run import ThreadController
9
+ from .pause_run import ThreadSuspender
@@ -9,7 +9,7 @@ from typing import Dict
9
9
  from ..common import logger
10
10
 
11
11
 
12
- class ThreadController:
12
+ class ThreadSuspender:
13
13
  """Simplified thread controller, fully controlled through context management"""
14
14
 
15
15
  def __init__(self):
@@ -31,7 +31,7 @@ class ThreadController:
31
31
  raise NotImplementedError(f"Unsupported platform: {self.platform}")
32
32
 
33
33
  @contextmanager
34
- def control_context(self):
34
+ def suspend_context(self):
35
35
  """Thread control context manager"""
36
36
  current_thread = threading.current_thread()
37
37
  tid = current_thread.ident
@@ -125,7 +125,7 @@ class ThreadController:
125
125
  class _ThreadControl:
126
126
  """Thread control interface (for internal use only)"""
127
127
 
128
- def __init__(self, controller: ThreadController, tid: int):
128
+ def __init__(self, controller: ThreadSuspender, tid: int):
129
129
  self._controller = controller
130
130
  self._tid = tid
131
131
  self._paused = False
@@ -153,4 +153,4 @@ class _ThreadControl:
153
153
  @property
154
154
  def is_paused(self) -> bool:
155
155
  """Check if thread is paused"""
156
- return self._paused
156
+ return self._paused
@@ -23,10 +23,10 @@ class ProcessTaskManager:
23
23
  self._start: bool = True
24
24
  self._start_monitor_thread() # Start the monitor thread
25
25
 
26
- def add(self, skip_obj: Any, pause_ctx: Any, task_id: str) -> None:
26
+ def add(self, terminate_obj: Any, pause_ctx: Any, task_id: str) -> None:
27
27
  """
28
28
  Add task control objects to the dictionary.
29
- :param skip_obj: An object that has a skip method.
29
+ :param terminate_obj: An object that has a terminate method.
30
30
  :param pause_ctx: An object that has a pause method.
31
31
  :param task_id: Task ID, used as the key in the dictionary.
32
32
  """
@@ -34,7 +34,7 @@ class ProcessTaskManager:
34
34
  if task_id in self._tasks:
35
35
  logger.warning(f"Task with task_id '{task_id}' already exists, overwriting")
36
36
  self._tasks[task_id] = {
37
- 'skip': skip_obj,
37
+ 'terminate': terminate_obj,
38
38
  'pause': pause_ctx
39
39
  }
40
40
 
@@ -59,17 +59,17 @@ class ProcessTaskManager:
59
59
  with self._operation_lock: # Lock for thread-safe dictionary access
60
60
  return task_id in self._tasks
61
61
 
62
- def skip_task(self, task_id: str) -> None:
62
+ def terminate_task(self, task_id: str) -> None:
63
63
  """
64
- Skip the task based on task_id.
64
+ Terminate the task based on task_id.
65
65
  :param task_id: Task ID.
66
66
  """
67
67
  with self._operation_lock: # Lock for thread-safe dictionary access
68
68
  if task_id in self._tasks:
69
69
  try:
70
- self._tasks[task_id]['skip'].skip() # Perform the skip operation outside the lock
70
+ self._tasks[task_id]['terminate'].terminate() # Perform the terminate operation outside the lock
71
71
  except Exception as error:
72
- logger.error(f"Error skipping task '{task_id}': {error}")
72
+ logger.error(f"Error terminating task '{task_id}': {error}")
73
73
 
74
74
  else:
75
75
  logger.warning(f"No task found with task_id '{task_id}', operation invalid")
@@ -118,7 +118,7 @@ class ProcessTaskManager:
118
118
 
119
119
  if self.check(task_id): # Check if the task_id exists in the dictionary
120
120
  if target == "kill":
121
- self.skip_task(task_id) # Skip the task if it exists
121
+ self.terminate_task(task_id) # Terminate the task if it exists
122
122
  if target == "pause":
123
123
  self.pause_task(task_id) # Pause the task if it exists
124
124
  if target == "resume":
@@ -130,7 +130,7 @@ class ProcessTaskManager:
130
130
  break # Stop the loop if tasks dictionary is empty
131
131
 
132
132
  else:
133
- self._task_queue.put(task_id)
133
+ self._task_queue.put((task_id, target))
134
134
  except queue.Empty:
135
135
  pass # Ignore empty queue exceptions
136
136
  except Exception as error:
@@ -0,0 +1,109 @@
1
+ import threading
2
+ import ctypes
3
+ import platform
4
+ from contextlib import contextmanager
5
+ from typing import Dict
6
+
7
+
8
+ class StopException(Exception):
9
+ """Custom exception for thread termination"""
10
+ pass
11
+
12
+
13
+ class ThreadTerminator:
14
+ """Thread terminator using context manager that raises StopException in target thread"""
15
+
16
+ def __init__(self):
17
+ self._handles: Dict[int, int] = {}
18
+ self._lock = threading.Lock()
19
+ self._setup_platform()
20
+
21
+ def _setup_platform(self):
22
+ self.platform = platform.system()
23
+ if self.platform == "Windows":
24
+ self._kernel32 = ctypes.windll.kernel32
25
+ self.THREAD_ACCESS = 0x0001 | 0x0002 # TERMINATE + SUSPEND
26
+ elif self.platform in ("Linux", "Darwin"):
27
+ lib_name = "libc.so.6" if self.platform == "Linux" else "libSystem.dylib"
28
+ self._libc = ctypes.CDLL(lib_name)
29
+ else:
30
+ raise NotImplementedError(f"Unsupported platform: {self.platform}")
31
+
32
+ @contextmanager
33
+ def terminate_control(self):
34
+ """Context manager for thread termination"""
35
+ tid = threading.current_thread().ident
36
+ if not tid:
37
+ raise RuntimeError("Thread not running")
38
+
39
+ if not self._register_thread(tid):
40
+ raise RuntimeError("Thread registration failed")
41
+
42
+ controller = TerminateController(self, tid)
43
+ try:
44
+ yield controller
45
+ finally:
46
+ self._unregister_thread(tid)
47
+
48
+ def _register_thread(self, tid: int) -> bool:
49
+ """Register thread with OS"""
50
+ with self._lock:
51
+ if tid in self._handles:
52
+ return True
53
+
54
+ try:
55
+ if self.platform == "Windows":
56
+ handle = self._kernel32.OpenThread(self.THREAD_ACCESS, False, tid)
57
+ if not handle:
58
+ raise ctypes.WinError()
59
+ self._handles[tid] = handle
60
+ else:
61
+ self._handles[tid] = tid
62
+ return True
63
+ except Exception:
64
+ return False
65
+
66
+ def _unregister_thread(self, tid: int) -> bool:
67
+ """Unregister thread from OS"""
68
+ with self._lock:
69
+ if tid not in self._handles:
70
+ return False
71
+
72
+ try:
73
+ if self.platform == "Windows":
74
+ self._kernel32.CloseHandle(self._handles[tid])
75
+ del self._handles[tid]
76
+ return True
77
+ except Exception:
78
+ return False
79
+
80
+ def _raise_stop_exception(self, tid: int):
81
+ """Raise StopException in target thread"""
82
+ if self.platform == "Windows":
83
+ # Use async exception on Windows
84
+ ret = ctypes.pythonapi.PyThreadState_SetAsyncExc(
85
+ ctypes.c_long(tid),
86
+ ctypes.py_object(StopException))
87
+
88
+ if ret == 0:
89
+ raise ValueError("Invalid thread ID")
90
+ elif ret > 1:
91
+ ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None)
92
+ raise SystemError("Async exception failed")
93
+ else:
94
+ # Use pthread_cancel on POSIX
95
+ if self._libc.pthread_cancel(tid) != 0:
96
+ raise RuntimeError("Failed to cancel thread")
97
+
98
+
99
+ class TerminateController:
100
+ """Controller for terminating the current thread"""
101
+
102
+ def __init__(self, terminator: ThreadTerminator, tid: int):
103
+ self._terminator = terminator
104
+ self._tid = tid
105
+
106
+ def terminate(self):
107
+ """Terminate the current thread by raising StopException in it"""
108
+ self._terminator._raise_stop_exception(self._tid)
109
+ # This line won't be reached as the thread will be terminated
@@ -12,12 +12,12 @@ class ThreadTaskManager:
12
12
  self._tasks: Dict[str, Dict[str, Any]] = {}
13
13
  self._lock = threading.Lock() # Use a lock to control concurrent operations
14
14
 
15
- def add(self, cancel_obj: Any, skip_obj: Any, pause_ctx: Any, task_id: str) -> None:
15
+ def add(self, cancel_obj: Any, terminate_obj: Any, pause_ctx: Any, task_id: str) -> None:
16
16
  """
17
17
  Add task control objects to the dictionary.
18
18
 
19
19
  :param cancel_obj: An object that has a cancel method.
20
- :param skip_obj: An object that has a skip method.
20
+ :param terminate_obj: An object that has a terminate method.
21
21
  :param pause_ctx: An object that has a pause method.
22
22
  :param task_id: Task ID, used as the key in the dictionary.
23
23
  """
@@ -26,7 +26,7 @@ class ThreadTaskManager:
26
26
  logger.warning(f"Task with task_id '{task_id}' already exists, overwriting")
27
27
  self._tasks[task_id] = {
28
28
  'cancel': cancel_obj,
29
- 'skip': skip_obj,
29
+ 'terminate': terminate_obj,
30
30
  'pause': pause_ctx
31
31
  }
32
32
 
@@ -74,28 +74,28 @@ class ThreadTaskManager:
74
74
  self._tasks.keys()): # Use list(self._tasks.keys()) to avoid errors due to dictionary size changes
75
75
  self.cancel_task(task_id)
76
76
 
77
- def skip_task(self, task_id: str) -> None:
77
+ def terminate_task(self, task_id: str) -> None:
78
78
  """
79
- Skip the task based on task_id.
79
+ Terminate the task based on task_id.
80
80
 
81
81
  :param task_id: Task ID.
82
82
  """
83
83
  with self._lock: # Acquire the lock to ensure thread-safe operations
84
84
  if task_id in self._tasks:
85
85
  try:
86
- self._tasks[task_id]['skip'].skip()
86
+ self._tasks[task_id]['terminate'].terminate()
87
87
  except Exception as error:
88
88
  logger.error(error)
89
89
  else:
90
90
  logger.warning(f"No task found with task_id '{task_id}', operation invalid")
91
91
 
92
- def skip_all_tasks(self) -> None:
92
+ def terminate_all_tasks(self) -> None:
93
93
  """
94
- Skip all tasks in the dictionary.
94
+ Terminate all tasks in the dictionary.
95
95
  """
96
96
  for task_id in list(
97
97
  self._tasks.keys()): # Use list(self._tasks.keys()) to avoid errors due to dictionary size changes
98
- self.skip_task(task_id)
98
+ self.terminate_task(task_id)
99
99
 
100
100
  def pause_task(self, task_id: str) -> None:
101
101
  """
@@ -12,9 +12,11 @@ from typing import Callable, Dict, Tuple, Optional, Any
12
12
  from ..common import logger
13
13
  from ..config import config
14
14
  from ..manager import task_status_manager
15
- from ..control import ProcessTaskManager, skip_on_demand, StopException
15
+ from ..control import ProcessTaskManager, ThreadTerminator, StopException
16
16
  from ..utils import worker_initializer_asyncio
17
17
 
18
+ _threadterminator = ThreadTerminator()
19
+
18
20
 
19
21
  async def _execute_task_async(task: Tuple[bool, str, str, Callable, Tuple, Dict],
20
22
  task_status_queue: queue.Queue, task_manager: Any) -> Any:
@@ -36,8 +38,8 @@ async def _execute_task_async(task: Tuple[bool, str, str, Callable, Tuple, Dict]
36
38
  """
37
39
  timeout_processing, task_name, task_id, func, args, kwargs = task
38
40
 
39
- with skip_on_demand() as skip_ctx:
40
- task_manager.add(skip_ctx, None, task_id)
41
+ with _threadterminator.terminate_control() as terminate_ctx:
42
+ task_manager.add(terminate_ctx, None, task_id)
41
43
 
42
44
  task_status_queue.put(("running", task_id, None, time.time(), None, None, None))
43
45
  logger.debug(f"Start running cpu asyncio task, task ID: {task_id}")
@@ -12,13 +12,14 @@ from typing import Callable, Dict, Tuple, Optional, Any
12
12
  from ..common import logger
13
13
  from ..config import config
14
14
  from ..manager import task_status_manager
15
- from ..control import skip_on_demand, ProcessTaskManager, StopException, ThreadingTimeout, TimeoutException, \
16
- ThreadController
15
+ from ..control import ThreadTerminator, ProcessTaskManager, StopException, ThreadingTimeout, TimeoutException, \
16
+ ThreadSuspender
17
17
  from ..utils import worker_initializer_liner
18
18
  from ..scheduler_tools import TaskCounter
19
19
 
20
20
  _task_counter = TaskCounter("cpu_liner_task")
21
- _controller = ThreadController()
21
+ _threadsuspender = ThreadSuspender()
22
+ _threadterminator = ThreadTerminator()
22
23
 
23
24
 
24
25
  def _execute_task(task: Tuple[bool, str, str, Callable, Tuple, Dict],
@@ -47,9 +48,9 @@ def _execute_task(task: Tuple[bool, str, str, Callable, Tuple, Dict],
47
48
  return_results = None
48
49
 
49
50
  try:
50
- with skip_on_demand() as skip_ctx:
51
- with _controller.control_context() as ctrlp_ctx:
52
- task_manager.add(skip_ctx, ctrlp_ctx, task_id)
51
+ with _threadterminator.terminate_control() as terminate_ctx:
52
+ with _threadsuspender.suspend_context() as pause_ctx:
53
+ task_manager.add(terminate_ctx, pause_ctx, task_id)
53
54
 
54
55
  task_status_queue.put(("running", task_id, None, time.time(), None, None, None))
55
56
  logger.debug(f"Start running cpu linear task, task ID: {task_id}")
@@ -386,7 +387,7 @@ class CpuLinerTask:
386
387
  if not future.running():
387
388
  future.cancel()
388
389
  else:
389
- self._task_signal_transmission.put(task_id, "kill")
390
+ self._task_signal_transmission.put((task_id, "kill"))
390
391
 
391
392
  self._task_status_queue.put(("cancelled", task_id, None, None, None, None, None))
392
393
  with self._lock:
@@ -410,10 +411,10 @@ class CpuLinerTask:
410
411
 
411
412
  else:
412
413
  if action == "pause":
413
- self._task_signal_transmission.put(task_id, "pause")
414
+ self._task_signal_transmission.put((task_id, "pause"))
414
415
  self._task_status_queue.put(("waiting", task_id, None, None, None, None, None))
415
416
  elif action == "resume":
416
- self._task_signal_transmission.put(task_id, "resume")
417
+ self._task_signal_transmission.put((task_id, "resume"))
417
418
  self._task_status_queue.put(("running", task_id, None, None, None, None, None))
418
419
 
419
420
  return True
@@ -402,7 +402,7 @@ class IoAsyncioTask:
402
402
  if not future.running():
403
403
  future.cancel()
404
404
  else:
405
- _task_manager.skip_task(task_id)
405
+ _task_manager.terminate_task(task_id)
406
406
 
407
407
  with self._condition:
408
408
  if task_id in self._task_results:
@@ -11,14 +11,15 @@ from typing import Callable, Dict, Tuple, Optional, Any
11
11
  from ..common import logger
12
12
  from ..config import config
13
13
  from ..manager import task_status_manager
14
- from ..control import ThreadTaskManager, skip_on_demand, StopException, ThreadingTimeout, TimeoutException, \
15
- ThreadController
14
+ from ..control import ThreadTaskManager, ThreadTerminator, StopException, ThreadingTimeout, TimeoutException, \
15
+ ThreadSuspender
16
16
  from ..scheduler_tools import TaskCounter
17
17
 
18
18
  # Create Manager instance
19
19
  _task_manager = ThreadTaskManager()
20
20
  _task_counter = TaskCounter("io_liner_task")
21
- _controller = ThreadController()
21
+ _threadsuspender = ThreadSuspender()
22
+ _threadterminator = ThreadTerminator()
22
23
 
23
24
 
24
25
  def _execute_task(task: Tuple[bool, str, str, Callable, Tuple, Dict]) -> Any:
@@ -41,13 +42,14 @@ def _execute_task(task: Tuple[bool, str, str, Callable, Tuple, Dict]) -> Any:
41
42
 
42
43
  return_results = None
43
44
  try:
44
- with skip_on_demand() as skip_ctx:
45
- with _controller.control_context() as ctrlp_ctx:
45
+ with _threadterminator.terminate_control() as terminate_ctx:
46
+ with _threadsuspender.suspend_context() as pause_ctx:
46
47
 
47
- _task_manager.add(None, skip_ctx, ctrlp_ctx, task_id)
48
+ _task_manager.add(None, terminate_ctx, pause_ctx, task_id)
48
49
 
49
50
  task_status_manager.add_task_status(task_id, None, "running", time.time(), None, None, None, None)
50
51
  logger.debug(f"Start running io linear task, task ID: {task_id}")
52
+
51
53
  if timeout_processing:
52
54
  with ThreadingTimeout(seconds=config["watch_dog_time"], swallow_exc=False):
53
55
  return_results = func(*args, **kwargs)
@@ -196,7 +198,7 @@ class IoLinerTask:
196
198
  if force_cleanup:
197
199
  logger.debug("Force stopping scheduler and cleaning up tasks")
198
200
  # Force stop all running tasks
199
- _task_manager.skip_all_tasks()
201
+ _task_manager.terminate_all_tasks()
200
202
  self._scheduler_stop_event.set()
201
203
  else:
202
204
  self._scheduler_stop_event.set()
@@ -334,7 +336,7 @@ class IoLinerTask:
334
336
  if not future.running():
335
337
  future.cancel()
336
338
  else:
337
- _task_manager.skip_task(task_id)
339
+ _task_manager.terminate_task(task_id)
338
340
 
339
341
  task_status_manager.add_task_status(task_id, None, "cancelled", None, None, None, None, None)
340
342
  with self._lock:
@@ -11,10 +11,11 @@ from typing import Callable, Dict, List, Tuple, Optional, Any
11
11
  from ..common import logger
12
12
  from ..config import config
13
13
  from ..manager import task_status_manager
14
- from ..control import ThreadTaskManager, skip_on_demand, StopException, ThreadingTimeout, TimeoutException
14
+ from ..control import ThreadTaskManager, ThreadTerminator, StopException, ThreadingTimeout, TimeoutException
15
15
 
16
16
  # Create Manager instance
17
17
  _task_manager = ThreadTaskManager()
18
+ _threadterminator = ThreadTerminator()
18
19
 
19
20
 
20
21
  def _execute_task(task: Tuple[bool, str, str, Callable, Tuple, Dict]) -> Any:
@@ -37,20 +38,22 @@ def _execute_task(task: Tuple[bool, str, str, Callable, Tuple, Dict]) -> Any:
37
38
 
38
39
  return_results = None
39
40
  try:
40
- task_status_manager.add_task_status(task_id, None, "running", time.time(), None, None,
41
- None, None)
41
+ with _threadterminator.terminate_control() as terminate_ctx:
42
+ _task_manager.add(None, terminate_ctx, None, task_id)
43
+ task_status_manager.add_task_status(task_id, None, "running", time.time(), None, None,
44
+ None, None)
45
+
46
+ logger.debug(f"Start running timer task, task ID: {task_id}")
47
+
48
+ if timeout_processing:
49
+ with ThreadingTimeout(seconds=config["watch_dog_time"], swallow_exc=False):
42
50
 
43
- logger.debug(f"Start running timer task, task ID: {task_id}")
44
- if timeout_processing:
45
- with ThreadingTimeout(seconds=config["watch_dog_time"], swallow_exc=False):
46
- with skip_on_demand() as skip_ctx:
47
- _task_manager.add(None, skip_ctx, None, task_id)
48
51
  return_results = func(*args, **kwargs)
49
- else:
50
- with skip_on_demand() as skip_ctx:
51
- _task_manager.add(None, skip_ctx, task_id)
52
+ else:
52
53
  return_results = func(*args, **kwargs)
53
- _task_manager.remove(task_id)
54
+
55
+ _task_manager.remove(task_id)
56
+
54
57
  except TimeoutException:
55
58
  logger.debug(f"Timer task | {task_id} | timed out, forced termination")
56
59
  task_status_manager.add_task_status(task_id, None, "timeout", None, None, None,
@@ -200,7 +203,7 @@ class TimerTask:
200
203
  if force_cleanup:
201
204
  logger.debug("Force stopping scheduler and cleaning up tasks")
202
205
  # Force stop all running tasks
203
- _task_manager.skip_all_tasks()
206
+ _task_manager.terminate_all_tasks()
204
207
  self._scheduler_stop_event.set()
205
208
  else:
206
209
  self._scheduler_stop_event.set()
@@ -358,7 +361,7 @@ class TimerTask:
358
361
  if not future.running():
359
362
  future.cancel()
360
363
  else:
361
- _task_manager.skip_task(task_id)
364
+ _task_manager.terminate_task(task_id)
362
365
 
363
366
  task_status_manager.add_task_status(task_id, None, "cancelled", None, None, None, None, None)
364
367
  with self._lock:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: task_scheduling
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: It is mainly used for task scheduling
5
5
  Home-page: https://github.com/fallingmeteorite/task_scheduling
6
6
  Author: fallingmeteorite
@@ -81,7 +81,9 @@ pip install --upgrade task_scheduling
81
81
  ```
82
82
 
83
83
  # Interface UI
84
+
84
85
  Use the function in code
86
+
85
87
  ```
86
88
 
87
89
  from task_scheduling.task_info import start_task_status_ui
@@ -95,13 +97,16 @@ Task status UI available at http://localhost:8000
95
97
  # Function introduction
96
98
 
97
99
  ## Tips
100
+
98
101
  1.Detailed information requires changing the log level to `debug`.
99
102
 
100
- 2.This scheduler does not support terminating blocking tasks due to various factors, so it only supports terminating tasks by injecting exceptions.
103
+ 2.This scheduler does not support terminating blocking tasks due to various factors, so it only supports terminating
104
+ tasks by injecting exceptions.
101
105
 
102
106
  3.Most of the API calls for this task scheduling can be added to the scheduler for execution as tasks.
103
107
 
104
108
  4.The log display level is available
109
+
105
110
  ```
106
111
  from task_scheduling.common import set_log_level
107
112
 
@@ -110,7 +115,9 @@ set_log_level("DEBUG") # INFO, DEBUG, ERROR, WARNING
110
115
  if __name__ == "__main__":
111
116
  ......
112
117
  ```
113
- 5.The task priority is modified to high when necessary, and the task will start executing directly after submission. However, this can also cause some running tasks to pause to free up resources to run high-priority tasks
118
+
119
+ 5.The task priority is modified to high when necessary, and the task will start executing directly after submission.
120
+ However, this can also cause some running tasks to pause to free up resources to run high-priority tasks
114
121
 
115
122
  ### Function: task_creation(delay: int or None, daily_time: str or None, function_type: str, timeout_processing: bool, task_name: str, func: Callable, *args, **kwargs) -> str or None:
116
123
 
@@ -376,6 +383,78 @@ if __name__ == "__main__":
376
383
 
377
384
  ```
378
385
 
386
+ ### pause_and_resume_task(self, task_id: str, action: str) -> bool:
387
+
388
+ A function is used to pause a task and resume running at a specified time.
389
+
390
+ ```
391
+ import asyncio
392
+ import time
393
+
394
+ from task_scheduling.utils import interruptible_sleep
395
+
396
+
397
+ def line_task(input_info):
398
+ for i in range(10):
399
+ interruptible_sleep(1)
400
+ print(input_info)
401
+
402
+
403
+ async def asyncio_task(input_info):
404
+ for i in range(10):
405
+ await asyncio.sleep(1)
406
+ print(input_info)
407
+
408
+
409
+ input_info = "test"
410
+
411
+ from task_scheduling.common import set_log_level
412
+
413
+ set_log_level("DEBUG")
414
+ if __name__ == "__main__":
415
+ from task_scheduling.task_creation import task_creation, shutdown
416
+ from task_scheduling.task_info import start_task_status_ui
417
+ from task_scheduling.scheduler import io_liner_task
418
+
419
+ start_task_status_ui()
420
+ from task_scheduling.variable import *
421
+
422
+ task_id1 = task_creation(None,
423
+ # This is how long the delay is executed (in seconds)
424
+ # This parameter is required when the function_type is "timer",if this parameter is used, the daily_time is not required
425
+ None,
426
+ # This is to be performed at what point (24-hour clock)
427
+ # This parameter is required when the function_type is "timer",if this parameter is used, the delay is not required
428
+ scheduler_io,
429
+ # Running function type, there are "io, cpu, timer"
430
+ True,
431
+ # Set to True to enable timeout detection, tasks that do not finish within the runtime will be forcibly terminated
432
+ "task2",
433
+ # Task ID, in linear tasks, tasks with the same ID will be queued, different IDs will be executed directly, the same applies to asynchronous tasks
434
+ line_task,
435
+ # The function to be executed, parameters should not be passed here
436
+ priority_low,
437
+ # The task priority is modified to high when necessary, and the task will start executing directly after submission
438
+ input_info
439
+ # Pass the parameters required by the function, no restrictions
440
+ )
441
+
442
+ # cf478b6e-5e02-49b8-9031-4adc6ff915c2, cf478b6e-5e02-49b8-9031-4adc6ff915c2
443
+ time.sleep(2.0)
444
+ print("Pause")
445
+ io_liner_task.pause_and_resume_task(task_id1, "pause")
446
+ time.sleep(4.0)
447
+ print("Resume")
448
+ io_liner_task.pause_and_resume_task(task_id1, "resume")
449
+
450
+ try:
451
+ while True:
452
+ time.sleep(1.0)
453
+ except KeyboardInterrupt:
454
+ shutdown(True)
455
+
456
+ ```
457
+
379
458
  ### FunctionRunner(self, func: Callable, task_name: str, *args, **kwargs) -> None:
380
459
 
381
460
  A task that detects what type of function is executed. The detected parameters are stored in a file and read directly at
@@ -818,9 +897,11 @@ if __name__ == "__main__":
818
897
  ### force_stop_task(task_id: str) -> bool:
819
898
 
820
899
  All 4 schedulers have this function for terminating a running task. Warning: If the function is executing a
821
- process-blocking function such as `time.sleep`, it will have to wait for the execution to finish before terminating the function
900
+ process-blocking function such as `time.sleep`, it will have to wait for the execution to finish before terminating the
901
+ function
822
902
 
823
903
  Warn!!! `cpu_liner_task.force_stop_task()` is different and requires the addition of additional parameters
904
+
824
905
  ```
825
906
  import asyncio
826
907
  import time
@@ -20,7 +20,7 @@ task_scheduling/config/config.yaml
20
20
  task_scheduling/control/__init__.py
21
21
  task_scheduling/control/pause_run.py
22
22
  task_scheduling/control/process_manager.py
23
- task_scheduling/control/skip_run.py
23
+ task_scheduling/control/terminate_run.py
24
24
  task_scheduling/control/thread_manager.py
25
25
  task_scheduling/control/timeout_run.py
26
26
  task_scheduling/control/utils.py
@@ -1,39 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Author: fallingmeteorite
3
-
4
- import ctypes
5
- import threading
6
- from contextlib import contextmanager
7
-
8
-
9
- class StopException(Exception):
10
- """Custom timeout exception"""
11
- pass
12
-
13
-
14
- def _async_raise(target_tid, exception):
15
- """Raise an asynchronous exception in the target thread"""
16
- ret = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(target_tid), ctypes.py_object(exception))
17
- if ret == 0:
18
- raise ValueError("Invalid thread ID {}".format(target_tid))
19
- elif ret > 1:
20
- ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(target_tid), None)
21
- raise SystemError("PyThreadState_SetAsyncExc failed")
22
-
23
-
24
- class _SkipContext:
25
- """Context object for skipping the current line"""
26
-
27
- def __init__(self):
28
- self._target_tid = threading.current_thread().ident
29
-
30
- def skip(self):
31
- """Skip the current line"""
32
- _async_raise(self._target_tid, StopException)
33
-
34
-
35
- @contextmanager
36
- def skip_on_demand():
37
- """Context manager that supports manual skipping of the current line"""
38
- _skip_ctx = _SkipContext()
39
- yield _skip_ctx # Provide _skip_ctx to external code
File without changes