diff options
Diffstat (limited to 'backend/decky_loader/plugin')
| -rw-r--r-- | backend/decky_loader/plugin/plugin.py | 35 | ||||
| -rw-r--r-- | backend/decky_loader/plugin/sandboxed_plugin.py | 19 |
2 files changed, 42 insertions, 12 deletions
diff --git a/backend/decky_loader/plugin/plugin.py b/backend/decky_loader/plugin/plugin.py index 94e411bc..f4b01ad3 100644 --- a/backend/decky_loader/plugin/plugin.py +++ b/backend/decky_loader/plugin/plugin.py @@ -1,4 +1,4 @@ -from asyncio import Task, create_task +from asyncio import CancelledError, Task, create_task, sleep from json import dumps, load, loads from logging import getLogger from os import path @@ -41,6 +41,7 @@ class PluginWrapper: self.log = getLogger("plugin") self.sandboxed_plugin = SandboxedPlugin(self.name, self.passive, self.flags, self.file, self.plugin_directory, self.plugin_path, self.version, self.author, self.api_version) + self.proc: Process | None = None # TODO: Maybe make LocalSocket not require on_new_message to make this cleaner self._socket = LocalSocket(self.sandboxed_plugin.on_new_message) self._listener_task: Task[Any] @@ -73,6 +74,10 @@ class PluginWrapper: create_task(self.emitted_event_callback(res["event"], res["args"])) elif res["type"] == SocketMessageType.RESPONSE.value: self._method_call_requests.pop(res["id"]).set_result(res) + except CancelledError: + self.log.info(f"Stopping response listener for {self.name}") + await self._socket.close_socket_connection() + raise except: pass @@ -104,13 +109,37 @@ class PluginWrapper: def start(self): if self.passive: return self - Process(target=self.sandboxed_plugin.initialize, args=[self._socket]).start() + self.proc = Process(target=self.sandboxed_plugin.initialize, args=[self._socket], daemon=True) + self.proc.start() self._listener_task = create_task(self._response_listener()) return self async def stop(self, uninstall: bool = False): + self.log.info(f"Stopping plugin {self.name}") + if self.passive: + return if hasattr(self, "_socket"): await self._socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False)) await self._socket.close_socket_connection() if hasattr(self, "_listener_task"): - self._listener_task.cancel()
\ No newline at end of file + self._listener_task.cancel() + await self.kill_if_still_running() + + async def kill_if_still_running(self): + time = 0 + while self.proc and self.proc.is_alive(): + await sleep(0.1) + time += 1 + if time == 100: + self.log.warn(f"Plugin {self.name} still alive 10 seconds after stop request! Sending SIGTERM!") + self.terminate() + elif time == 200: + self.log.warn(f"Plugin {self.name} still alive 20 seconds after stop request! Sending SIGKILL!") + self.terminate(True) + + def terminate(self, kill: bool = False): + if self.proc and self.proc.is_alive(): + if kill: + self.proc.kill() + else: + self.proc.terminate()
\ No newline at end of file diff --git a/backend/decky_loader/plugin/sandboxed_plugin.py b/backend/decky_loader/plugin/sandboxed_plugin.py index 7e618590..083a6749 100644 --- a/backend/decky_loader/plugin/sandboxed_plugin.py +++ b/backend/decky_loader/plugin/sandboxed_plugin.py @@ -1,5 +1,5 @@ from os import path, environ -from signal import SIGINT, signal +from signal import SIG_IGN, SIGINT, SIGTERM, signal from importlib.util import module_from_spec, spec_from_file_location from json import dumps, loads from logging import getLogger @@ -39,13 +39,14 @@ class SandboxedPlugin: self.author = author self.api_version = api_version - self.log = getLogger("plugin") + self.log = getLogger("sandboxed_plugin") def initialize(self, socket: LocalSocket): self._socket = socket try: - signal(SIGINT, lambda s, f: exit(0)) + signal(SIGINT, SIG_IGN) + signal(SIGTERM, SIG_IGN) set_event_loop(new_event_loop()) if self.passive: @@ -112,10 +113,10 @@ class SandboxedPlugin: else: get_event_loop().create_task(self.Plugin._main(self.Plugin)) get_event_loop().create_task(socket.setup_server()) - get_event_loop().run_forever() except: self.log.error("Failed to start " + self.name + "!\n" + format_exc()) exit(0) + get_event_loop().run_forever() async def _unload(self): try: @@ -130,7 +131,7 @@ class SandboxedPlugin: self.log.info("Could not find \"_unload\" in " + self.name + "'s main.py" + "\n") except: self.log.error("Failed to unload " + self.name + "!\n" + format_exc()) - exit(0) + pass async def _uninstall(self): try: @@ -145,13 +146,13 @@ class SandboxedPlugin: self.log.info("Could not find \"_uninstall\" in " + self.name + "'s main.py" + "\n") except: self.log.error("Failed to uninstall " + self.name + "!\n" + format_exc()) - exit(0) + pass async def on_new_message(self, message : str) -> str|None: data = loads(message) if "stop" in data: - self.log.info("Calling Loader unload function.") + self.log.info(f"Calling Loader unload function for {self.name}.") await self._unload() if data.get('uninstall'): @@ -160,9 +161,9 @@ class SandboxedPlugin: get_event_loop().stop() while get_event_loop().is_running(): - await sleep(0) + await sleep(0.1) get_event_loop().close() - raise Exception("Closing message listener") + exit(0) d: SocketResponseDict = {"type": SocketMessageType.RESPONSE, "res": None, "success": True, "id": data["id"]} try: |
