Files
2026-07-28 03:21:34 +08:00

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