-
-
Notifications
You must be signed in to change notification settings - Fork 128
feat: add shell -r/--restart for race-free shell restarts #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,11 @@ | ||||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||
| import signal | ||||||||||||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||||||||||||
| import time | ||||||||||||||||||||||||||||||||||||||||
| from argparse import Namespace | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| from caelestia.utils.io import warn | ||||||||||||||||||||||||||||||||||||||||
| from caelestia.utils.paths import c_cache_dir | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -24,6 +29,11 @@ def run(self) -> None: | |||||||||||||||||||||||||||||||||||||||
| # Send a message | ||||||||||||||||||||||||||||||||||||||||
| self.message(*self.args.message) | ||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||
| # Kill any running instance and wait for it to exit, otherwise `-n` | ||||||||||||||||||||||||||||||||||||||||
| # will silently skip the relaunch | ||||||||||||||||||||||||||||||||||||||||
| if self.args.restart: | ||||||||||||||||||||||||||||||||||||||||
| self.stop_instances() | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Start the shell | ||||||||||||||||||||||||||||||||||||||||
| args = ["qs", "-c", "caelestia", "-n"] | ||||||||||||||||||||||||||||||||||||||||
| if self.args.log_rules: | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -43,6 +53,43 @@ def run(self) -> None: | |||||||||||||||||||||||||||||||||||||||
| def shell(self, *args: str) -> str: | ||||||||||||||||||||||||||||||||||||||||
| return subprocess.check_output(["qs", "-c", "caelestia", *args], text=True) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| def list_instances(self) -> list[dict]: | ||||||||||||||||||||||||||||||||||||||||
| proc = subprocess.run(["qs", "-c", "caelestia", "list", "-j"], capture_output=True, text=True) | ||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||
| return json.loads(proc.stdout) if proc.returncode == 0 else [] | ||||||||||||||||||||||||||||||||||||||||
| except json.JSONDecodeError: | ||||||||||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+56
to
+61
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Currently the following three results all mean "no instances":
These should be differently sincfe Quickshell also does not emit a JSON array when no instances are running: This means an empty stdout needs to mean |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| def wait_for_exit(self, timeout: float) -> bool: | ||||||||||||||||||||||||||||||||||||||||
| end = time.monotonic() + timeout | ||||||||||||||||||||||||||||||||||||||||
| while time.monotonic() < end: | ||||||||||||||||||||||||||||||||||||||||
| if not self.list_instances(): | ||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||
| time.sleep(0.1) | ||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| def stop_instances(self) -> None: | ||||||||||||||||||||||||||||||||||||||||
| instances = self.list_instances() | ||||||||||||||||||||||||||||||||||||||||
| if not instances: | ||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| subprocess.run(["qs", "-c", "caelestia", "kill"], capture_output=True) | ||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Satisfies linting from Ruff, PLW1510. |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Teardown is not instant (and slowest while a session lock is up) | ||||||||||||||||||||||||||||||||||||||||
| if self.wait_for_exit(5): | ||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # The instance is stuck; force kill it so the restart still happens | ||||||||||||||||||||||||||||||||||||||||
| warn("shell did not exit gracefully, killing") | ||||||||||||||||||||||||||||||||||||||||
| for instance in instances: | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+83
to
+84
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The instance snapshot iterated here is stale after the 5s |
||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||
| os.kill(instance["pid"], signal.SIGKILL) | ||||||||||||||||||||||||||||||||||||||||
| except (KeyError, ProcessLookupError): | ||||||||||||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if not self.wait_for_exit(2): | ||||||||||||||||||||||||||||||||||||||||
| warn("an instance of the shell is still running") | ||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
If an instance is still present after SIGKILL, the restart hasn't actually fulfilled the precondition of killing the running instance. This could proceed onto |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| def filter_log(self, line: str) -> bool: | ||||||||||||||||||||||||||||||||||||||||
| return f"Cannot open: file://{c_cache_dir}/imagecache/" not in line | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Necessary for change on L91