mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
After the clients quit, the session kept Darling running: Riot and League crash handlers never exit under Darling, and the idle check counted them as clients. Ctrl+C or closing the terminal killed bubblewrap, whose --die-with-parent then killed nix-lifecycle.py before it could shut Darling down, which left a live server marker behind. - nix-idle.py ignores crash handlers when deciding whether a client still uses the prefix. - The launcher starts bubblewrap with INT, QUIT, HUP and TERM ignored, and env restores them inside. The supervisor turns them into a stop request. - nix-lifecycle.py polls for a signal or stop request and then shuts Darling down without waiting for clients. It stops retrying a failed shutdown once the server is gone, and waits for or kills its own command afterwards. - darling-lol --stop makes the stop request and waits for the session. If the session does not exit within 30 seconds, or no session holds the lock, it kills the prefix's darlingserver and launchd like darling shutdown, after checking that the marker PID is this user's darlingserver for this prefix. Co-Authored-By: Claude Opus 5.5 <[email protected]>
105 lines
3.4 KiB
Python
105 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""darling-lol --stop: end the workspace's session and its Darling server.
|
|
|
|
The session shuts Darling down in its own namespace when it finds the stop
|
|
request. Only if it does not finish in time, or if no session holds the lock
|
|
but the prefix server is still alive, is that server killed from here. The
|
|
marker PID alone is not trusted: the process must be a darlingserver of this
|
|
user for this prefix.
|
|
"""
|
|
import fcntl
|
|
import os
|
|
from pathlib import Path
|
|
import signal
|
|
import sys
|
|
import time
|
|
|
|
WAIT_SECONDS = 30
|
|
|
|
|
|
def session_running(lock):
|
|
try:
|
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
except BlockingIOError:
|
|
return True
|
|
fcntl.flock(lock, fcntl.LOCK_UN)
|
|
return False
|
|
|
|
|
|
def wait_for_session(lock):
|
|
deadline = time.monotonic() + WAIT_SECONDS
|
|
while session_running(lock):
|
|
if time.monotonic() > deadline:
|
|
return False
|
|
time.sleep(0.5)
|
|
return True
|
|
|
|
|
|
def prefix_server(prefix):
|
|
"""PID of this prefix's darlingserver, or 0 if none is running."""
|
|
try:
|
|
pid = int((prefix / '.init.pid').read_text().strip())
|
|
except (FileNotFoundError, ValueError):
|
|
return 0
|
|
process = Path('/proc') / str(pid)
|
|
try:
|
|
argv = (process / 'cmdline').read_bytes().split(b'\0')
|
|
owner = process.stat().st_uid
|
|
except (FileNotFoundError, ProcessLookupError):
|
|
return 0
|
|
if (pid <= 0 or owner != os.getuid() or len(argv) < 2
|
|
or os.path.basename(argv[0]) != b'darlingserver'
|
|
or Path(os.fsdecode(argv[1])).resolve() != prefix):
|
|
return 0
|
|
return pid
|
|
|
|
|
|
def kill_server(pid):
|
|
"""Same as darling shutdown: kill launchd, which ends its PID namespace, then the server."""
|
|
children = []
|
|
for task in (Path('/proc') / str(pid) / 'task').glob('*/children'):
|
|
try:
|
|
children += [int(child) for child in task.read_text().split()]
|
|
except (FileNotFoundError, ProcessLookupError):
|
|
pass
|
|
for target in [*children, pid]:
|
|
try:
|
|
os.kill(target, signal.SIGKILL)
|
|
except ProcessLookupError:
|
|
pass
|
|
|
|
|
|
def main():
|
|
workspace, prefix = Path(sys.argv[1]), Path(sys.argv[2]).resolve()
|
|
request = workspace / '.stop-request'
|
|
workspace.mkdir(parents=True, exist_ok=True)
|
|
with open(workspace / '.launcher.lock', 'a+') as lock:
|
|
if session_running(lock):
|
|
print('Asking the darling-lol session to shut Darling down...', flush=True)
|
|
request.touch()
|
|
if wait_for_session(lock):
|
|
request.unlink(missing_ok=True)
|
|
print('Stopped.')
|
|
return 0
|
|
print(f'The session did not stop within {WAIT_SECONDS} seconds.', flush=True)
|
|
server = prefix_server(prefix)
|
|
if server:
|
|
print(f'Killing the Darling server of {prefix} (PID {server}).', flush=True)
|
|
kill_server(server)
|
|
elif not session_running(lock):
|
|
print('No darling-lol session is running.')
|
|
return 0
|
|
if wait_for_session(lock):
|
|
request.unlink(missing_ok=True)
|
|
print('Stopped.')
|
|
return 0
|
|
# Do not leave a request that would stop the next session at once.
|
|
request.unlink(missing_ok=True)
|
|
print('Darling is stopped, but the session that started it is still running. '
|
|
'Close its terminal.', file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|