summaryrefslogtreecommitdiff
path: root/backend/decky_loader/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'backend/decky_loader/plugin')
-rw-r--r--backend/decky_loader/plugin/plugin.py35
-rw-r--r--backend/decky_loader/plugin/sandboxed_plugin.py19
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: