Skip to content

Commit

Permalink
use signals to shut down plugins instead of sending a socket message
Browse files Browse the repository at this point in the history
should reduce or outright prevent shutdown stalls
  • Loading branch information
AAGaming00 committed Sep 12, 2024
1 parent ef4ca20 commit 508408a
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 16 deletions.
27 changes: 15 additions & 12 deletions backend/decky_loader/plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,37 +120,40 @@ async def stop(self, uninstall: bool = False):
start_time = time()
if self.passive:
return
self.log.info(f"Shutting down {self.name}")

_, pending = await wait([
create_task(self._socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False)))
], timeout=1)
pending: set[Task[None]] | None = None;

if uninstall:
_, pending = await wait([
create_task(self._socket.write_single_line(dumps({ "uninstall": uninstall }, ensure_ascii=False)))
], timeout=1)

self.terminate() # the plugin process will handle SIGTERM and shut down cleanly without a socket message

if hasattr(self, "_listener_task"):
self._listener_task.cancel()

await self.kill_if_still_running()

for pending_task in pending:
pending_task.cancel()
if pending:
for pending_task in pending:
pending_task.cancel()

self.log.info(f"Plugin {self.name} has been stopped in {time() - start_time:.1f}s")
except Exception as e:
self.log.error(f"Error during shutdown for plugin {self.name}: {str(e)}\n{format_exc()}")

async def kill_if_still_running(self):
start_time = time()
sigtermed = False
while self.proc and self.proc.is_alive():
elapsed_time = time() - start_time
if elapsed_time >= 5 and not sigtermed:
sigtermed = True
self.log.warning(f"Plugin {self.name} still alive 5 seconds after stop request! Sending SIGTERM!")
self.terminate()
elif elapsed_time >= 10:
self.log.warning(f"Plugin {self.name} still alive 10 seconds after stop request! Sending SIGKILL!")
if elapsed_time >= 5:
self.log.warning(f"Plugin {self.name} still alive 5 seconds after stop request! Sending SIGKILL!")
self.terminate(True)
await sleep(0.1)


def terminate(self, kill: bool = False):
if self.proc and self.proc.is_alive():
if kill:
Expand Down
9 changes: 5 additions & 4 deletions backend/decky_loader/plugin/sandboxed_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def __init__(self,
self.author = author
self.api_version = api_version
self.shutdown_running = False
self.uninstalling = False

self.log = getLogger("sandboxed_plugin")

Expand Down Expand Up @@ -161,13 +162,13 @@ async def _uninstall(self):
self.log.error("Failed to uninstall " + self.name + "!\n" + format_exc())
pass

async def shutdown(self, uninstall: bool = False):
async def shutdown(self):
if not self.shutdown_running:
self.shutdown_running = True
self.log.info(f"Calling Loader unload function for {self.name}.")
await self._unload()

if uninstall:
if self.uninstalling:
self.log.info("Calling Loader uninstall function.")
await self._uninstall()

Expand All @@ -180,8 +181,8 @@ async def shutdown(self, uninstall: bool = False):
async def on_new_message(self, message : str) -> str|None:
data = loads(message)

if "stop" in data:
await self.shutdown(data.get('uninstall'))
if "uninstall" in data:
self.uninstalling = data.get("uninstall")

d: SocketResponseDict = {"type": SocketMessageType.RESPONSE, "res": None, "success": True, "id": data["id"]}
try:
Expand Down

0 comments on commit 508408a

Please sign in to comment.