337 lines
14 KiB
Python
337 lines
14 KiB
Python
# -*- Config : UTF-8 -*-
|
|
# Project Name: Termination Generally Use Cyber Virus Puppet Weapons - TLib/T_Process
|
|
# Version: 4.2.0
|
|
# Build Version: ./TConfig/version.ini
|
|
# Author: Veyra
|
|
# Description:
|
|
|
|
# Due to the particularity of this project,
|
|
# This project needs to execute multiple asynchronous tasks at the same time and needs appropriate management,
|
|
# This file is the communication between multiple threads, mounting, starting, stopping, etc.,
|
|
# Using ctypes to report an error winerror to force stop the thread.
|
|
|
|
|
|
from .T_imports import *
|
|
from . import T_Log
|
|
from . import T_Std
|
|
|
|
|
|
def Synchronized(LockAttr: str):
|
|
def Decorator(func: typing.Callable):
|
|
def Wrapper(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
|
lock: threading.Lock = getattr(self, LockAttr)
|
|
with lock:
|
|
return func(self, *args, **kwargs)
|
|
return Wrapper
|
|
return Decorator
|
|
|
|
class CallbackManager:
|
|
def __init__(self) -> None:
|
|
self._CallBacks: dict = {}
|
|
self._CallBackIDCounter: int = 0
|
|
self._Lock = threading.Lock()
|
|
@Synchronized('_Lock')
|
|
def AddCallBack(self, CallBack: typing.Callable) -> int:
|
|
self._CallBackIDCounter += 1
|
|
CallBackID: int = self._CallBackIDCounter
|
|
self._CallBacks[CallBackID] = CallBack
|
|
return CallBackID
|
|
@Synchronized('_Lock')
|
|
def RemoveCallBack(self, CallBackID: int) -> bool:
|
|
if CallBackID in self._CallBacks:
|
|
del self._CallBacks[CallBackID]
|
|
return True
|
|
return False
|
|
@Synchronized('_Lock')
|
|
def ClearCallBacks(self) -> None:
|
|
self._CallBacks.clear()
|
|
@Synchronized('_Lock')
|
|
def ExecuteCallBacks(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
for CallBack in self._CallBacks.values():
|
|
try: CallBack(*args, **kwargs)
|
|
except Exception as e: print(T_Std.ErrorFormat(e, "Execute Callbacks Error"))
|
|
|
|
class TProcess(threading.Thread):
|
|
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
super().__init__()
|
|
self.Args: tuple = args
|
|
self.Kwargs: dict = kwargs
|
|
self._StopEvent: threading.Event = threading.Event()
|
|
self._Manager: TProcessManager | None = None
|
|
self.desc: str = ""
|
|
self.progress: int = 0
|
|
self.editor: list[dict] = []
|
|
def AddEditor(self, name: str, key: str, type: typing.Literal["str", "int", "float", "radio", "multiple", "bool"], choices: list=[]) -> None:
|
|
self.editor.append({"name": name, "key": key, "type": type, "choices": choices})
|
|
def SetManager(self, Manager: 'TProcessManager') -> None:
|
|
self._Manager: TProcessManager | None = Manager
|
|
def Run(self) -> None:
|
|
raise NotImplementedError("Subclasses must implement Run method")
|
|
def run(self) -> None:
|
|
self.logger = T_Log.LoggerChild(self._Manager.master.Log, self.name) if self._Manager else None # 报错
|
|
try:
|
|
if self._Manager: self._Manager._OnThreadStart(self.name)
|
|
self.Run(*self.Args, **self.Kwargs)
|
|
except SystemExit: pass
|
|
except Exception as e: print(T_Std.ErrorFormat(e, "Thread Run Error"))
|
|
finally: self._CleanUp()
|
|
def join(self, timeout: int | None = None):
|
|
super().join(timeout)
|
|
self._CleanUp()
|
|
def Stop(self) -> None:
|
|
self._StopEvent.set()
|
|
self.join()
|
|
def ShouldStop(self) -> bool:
|
|
return self._StopEvent.is_set()
|
|
def _CleanUp(self):
|
|
if self._Manager: # and not self.is_alive():
|
|
self._Manager._OnThreadEnd(self.name)
|
|
self._Manager.RemoveThread(self.name)
|
|
|
|
class TProcessManager:
|
|
def __init__(self, master: 'Termination4.Termination') -> None:
|
|
self._Threads: typing.Dict[str, TProcess] = {}
|
|
self._Lock = threading.Lock()
|
|
self._StartCallBacks: CallbackManager = CallbackManager()
|
|
self._EndCallBacks: CallbackManager = CallbackManager()
|
|
self.master: 'Termination4.Termination' = master
|
|
def _OnThreadStart(self, ThreadName: str) -> None:
|
|
self._StartCallBacks.ExecuteCallBacks(ThreadName)
|
|
def _OnThreadEnd(self, ThreadName: str) -> None:
|
|
self._EndCallBacks.ExecuteCallBacks(ThreadName)
|
|
def AddStartCallBack(self, CallBack: typing.Callable[[str], None]) -> int:
|
|
return self._StartCallBacks.AddCallBack(CallBack)
|
|
def AddEndCallBack(self, CallBack: typing.Callable[[str], None]) -> int:
|
|
return self._EndCallBacks.AddCallBack(CallBack)
|
|
def RemoveStartCallBack(self, CallBackID: int) -> bool:
|
|
return self._StartCallBacks.RemoveCallBack(CallBackID)
|
|
def RemoveEndCallBack(self, CallBackID: int) -> bool:
|
|
return self._EndCallBacks.RemoveCallBack(CallBackID)
|
|
def ClearStartCallBacks(self):
|
|
self._StartCallBacks.ClearCallBacks()
|
|
def ClearEndCallBacks(self):
|
|
self._EndCallBacks.ClearCallBacks()
|
|
@Synchronized('_Lock')
|
|
def AddThread(self, Thread: TProcess, Name: typing.Optional[str] = None) -> bool:
|
|
ThreadName = Name if Name else Thread.name
|
|
if ThreadName in self._Threads: return False
|
|
Thread.name = ThreadName
|
|
Thread.SetManager(self)
|
|
self._Threads[ThreadName] = Thread
|
|
return True
|
|
@Synchronized('_Lock')
|
|
def RemoveThread(self, Name: str) -> bool:
|
|
if Name not in self._Threads: return False
|
|
if self._Threads[Name]._Manager == self: self._Threads[Name]._Manager = None
|
|
del self._Threads[Name]
|
|
return True
|
|
@Synchronized('_Lock')
|
|
def GetThread(self, Name: str) -> typing.Optional[TProcess]:
|
|
return self._Threads.get(Name)
|
|
@Synchronized('_Lock')
|
|
def GetAllThreads(self) -> typing.Dict[str, TProcess]:
|
|
return self._Threads.copy()
|
|
def StopThread(self, Name: str) -> bool:
|
|
Thread = self.GetThread(Name)
|
|
if Thread:
|
|
Thread.Stop()
|
|
return True
|
|
return False
|
|
def SafeCallWithTimeout(self, TargetFunc: typing.Callable, timeout: float = 1.0) -> tuple[bool, typing.Optional[Exception]]:
|
|
result = {"success": False, "exception": None}
|
|
def _thread_wrapper():
|
|
try:
|
|
TargetFunc()
|
|
result["success"] = True
|
|
except Exception as e: result["exception"] = e
|
|
ExecThread = threading.Thread(target=_thread_wrapper, daemon=True)
|
|
ExecThread.start()
|
|
ExecThread.join(timeout=timeout)
|
|
if ExecThread.is_alive():
|
|
return False, None
|
|
return result["success"], result["exception"]
|
|
def KillThread(self, Name: str) -> bool:
|
|
Thread = self.GetThread(Name)
|
|
if not Thread: return False
|
|
Thread._StopEvent.set()
|
|
try:
|
|
if getattr(Thread, "_Handle_Stop", None):
|
|
self.SafeCallWithTimeout(Thread._Handle_Stop, 5)
|
|
except: pass
|
|
self._OnThreadEnd(Name)
|
|
if not Thread.is_alive():
|
|
self.RemoveThread(Name)
|
|
return True
|
|
ThreadID: int | None = Thread.ident
|
|
if not ThreadID: return False
|
|
res: int | bool = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(ThreadID), ctypes.py_object(SystemExit))
|
|
if res == 0: return False
|
|
elif res != 1:
|
|
ctypes.pythonapi.PyThreadState_SetAsyncExc(ThreadID, None)
|
|
self.TerminateThreadUseSystemAPI(Thread)
|
|
return False
|
|
self.RemoveThread(Name)
|
|
return True
|
|
def StopAllThreads(self) -> None:
|
|
for Name in list(self.GetAllThreads().keys()):
|
|
self.StopThread(Name)
|
|
def KillAllThreads(self) -> None:
|
|
for Name in list(self.GetAllThreads().keys()):
|
|
try: self.KillThread(Name)
|
|
except: print(f"EKill: [{Name}]")
|
|
def TerminateThreadUseSystemAPI(self, Thread: threading.Thread) -> bool:
|
|
if not Thread.is_alive():
|
|
return True
|
|
if os.name == "nt":
|
|
ThreadHandle = ctypes.windll.kernel32.OpenThread(
|
|
1,
|
|
False,
|
|
Thread.ident
|
|
)
|
|
if ThreadHandle == 0: return False
|
|
Result = ctypes.windll.kernel32.TerminateThread(ThreadHandle, 0)
|
|
ctypes.windll.kernel32.CloseHandle(ThreadHandle)
|
|
return Result != 0
|
|
elif os.name == "posix":
|
|
LibpThread = ctypes.CDLL("libpthread.so.0")
|
|
Result = LibpThread.pthread_kill(Thread.ident, 15)
|
|
return Result == 0
|
|
return False
|
|
|
|
# Description:
|
|
# Use For Process (No Threading)
|
|
|
|
def Mp_Synchronized(lockAttr: str) -> typing.Callable:
|
|
def Decorator(func: typing.Callable):
|
|
def Wrapper(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
|
lock = getattr(self, lockAttr)
|
|
with lock:
|
|
return func(self, *args, **kwargs)
|
|
return Wrapper
|
|
return Decorator
|
|
|
|
class Mp_TProcess(mp.Process):
|
|
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
super().__init__()
|
|
self.Args: tuple = args
|
|
self.Kwargs: dict = kwargs
|
|
self._StopEvent = mp.Event()
|
|
self.desc: str = ""
|
|
self.progress: int = 0
|
|
self.editor: list[dict] = []
|
|
self._LogQueue = mp.Queue() if 'master' in kwargs else None
|
|
def AddEditor(self, name: str, key: str, type: typing.Literal["str", "int", "float", "radio", "multiple", "bool"], choices: list=[]) -> None:
|
|
self.editor.append({"name": name, "key": key, "type": type, "choices": choices})
|
|
def Run(self) -> None:
|
|
raise NotImplementedError("Subclasses must implement Run method")
|
|
def run(self) -> None:
|
|
self.logger = T_Log.Mp_LoggerChild(self._LogQueue, self.name) if self._LogQueue else None
|
|
try:
|
|
self.Run(*self.Args, **self.Kwargs)
|
|
except SystemExit: pass
|
|
except Exception as e: print(T_Std.ErrorFormat(e, "Process Run Error"))
|
|
finally: self._CleanUp()
|
|
def join(self, timeout: int | None = None):
|
|
super().join(timeout)
|
|
self._CleanUp()
|
|
def Stop(self) -> None:
|
|
self._StopEvent.set()
|
|
self.join()
|
|
def ShouldStop(self) -> bool:
|
|
return self._StopEvent.is_set()
|
|
def _CleanUp(self):
|
|
pass
|
|
|
|
class Mp_TProcessManager:
|
|
def __init__(self, master: 'Termination4.Termination') -> None:
|
|
self._Processes: typing.Dict[str, Mp_TProcess] = {}
|
|
self._Lock = mp.Lock()
|
|
self.master: 'Termination4.Termination' = master
|
|
self._LogQueue = mp.Queue()
|
|
@Mp_Synchronized('_Lock')
|
|
def AddProcess(self, Process: Mp_TProcess, Name: typing.Optional[str] = None) -> bool:
|
|
ProcessName = Name if Name else Process.name
|
|
if ProcessName in self._Processes: return False
|
|
Process.name = ProcessName
|
|
Process._LogQueue = self._LogQueue
|
|
self._Processes[ProcessName] = Process
|
|
return True
|
|
@Mp_Synchronized('_Lock')
|
|
def RemoveProcess(self, Name: str) -> bool:
|
|
if Name not in self._Processes: return False
|
|
del self._Processes[Name]
|
|
return True
|
|
@Mp_Synchronized('_Lock')
|
|
def GetProcess(self, Name: str) -> typing.Optional[Mp_TProcess]:
|
|
return self._Processes.get(Name)
|
|
@Mp_Synchronized('_Lock')
|
|
def GetAllProcesses(self) -> typing.Dict[str, Mp_TProcess]:
|
|
return self._Processes.copy()
|
|
def StopProcess(self, Name: str) -> bool:
|
|
process = self.GetProcess(Name)
|
|
if process:
|
|
process.Stop()
|
|
return True
|
|
return False
|
|
def SafeCallWithTimeout(self, TargetFunc: typing.Callable, timeout: float = 1.0) -> tuple[bool, typing.Optional[Exception]]:
|
|
result = {"success": False, "exception": None}
|
|
def _process_wrapper():
|
|
try:
|
|
TargetFunc()
|
|
result["success"] = True
|
|
except Exception as e: result["exception"] = e
|
|
execProcess = mp.Process(target=_process_wrapper, daemon=True)
|
|
execProcess.start()
|
|
execProcess.join(timeout=timeout)
|
|
if execProcess.is_alive():
|
|
self.KillProcess(execProcess.name)
|
|
return False, None
|
|
return result["success"], result["exception"]
|
|
def KillProcess(self, Name: str) -> bool:
|
|
process = self.GetProcess(Name)
|
|
if not process: return False
|
|
process._StopEvent.set()
|
|
try:
|
|
if getattr(process, "_Handle_Stop", None):
|
|
self.SafeCallWithTimeout(process._Handle_Stop, 5)
|
|
except: pass
|
|
if not process.is_alive():
|
|
self.RemoveProcess(Name)
|
|
return True
|
|
pid = process.pid
|
|
if not pid: return False
|
|
try:
|
|
os.kill(pid, 9)
|
|
self.RemoveProcess(Name)
|
|
return True
|
|
except Exception:
|
|
return self.TerminateProcessUseSystemAPI(process)
|
|
def StopAllProcesses(self) -> None:
|
|
for Name in list(self.GetAllProcesses().keys()):
|
|
self.StopProcess(Name)
|
|
def KillAllProcesses(self) -> None:
|
|
for Name in list(self.GetAllProcesses().keys()):
|
|
try: self.KillProcess(Name)
|
|
except: print(f"EKill: [{Name}]")
|
|
def TerminateProcessUseSystemAPI(self, process: Mp_TProcess) -> bool:
|
|
if not process.is_alive():
|
|
return True
|
|
if os.name == "nt":
|
|
processHandle = ctypes.windll.kernel32.OpenProcess(
|
|
1,
|
|
False,
|
|
process.pid
|
|
)
|
|
if processHandle == 0:
|
|
return False
|
|
result = ctypes.windll.kernel32.TerminateProcess(processHandle, 0)
|
|
ctypes.windll.kernel32.CloseHandle(processHandle)
|
|
return result != 0
|
|
elif os.name == "posix":
|
|
try:
|
|
os.kill(process.pid, 9)
|
|
return True
|
|
except:
|
|
return False
|
|
return False
|