fix: restrict memory from halting system - #474
Draft
ctrlaltf24 wants to merge 4 commits into
Draft
Conversation
Two-layer memory protection prevents a runaway Wine/Logos process (e.g. the indexer) from exhausting the host. Primary – systemd-run cgroup enforcement: When systemd-run is available, wine commands are wrapped in a transient user scope with MemoryMax set to 90% of currently-available RAM. The kernel enforces the limit at the cgroup level with no polling overhead. Availability is probed once at runtime and cached. Fallback – Python RSS watchdog: A background thread (LogosManager._memory_watchdog) polls the resident set size of the entire Wine process tree every two seconds. The cap is 90% of the RAM that would be free if the monitored process were not running (available + process_rss), so the process's own growing footprint is never counted against its budget — only other processes can move the ceiling. If the cap is exceeded, the process group is SIGTERM'd with a 10-second grace period before escalating to SIGKILL. The watchdog starts when Logos launches or indexing begins, and is stopped cleanly by end_processes(). Also fixes the AppImage download gate to skip the download when the wine binary is set to a recommended or beta sigil. Tested: - systemd-run cgroup path: Logos launched successfully inside a run-*.scope with MemoryMax=1612958515 (~90% of available RAM at launch). Scope appeared in `systemctl --user list-units --type=scope`. - Fallback path: after removing the systemd-run binary, no scope was created and Wine launched unwrapped; watchdog remains the sole enforcer. - Unit tests (13/13): all TestMemoryLimits and TestSystemdRunPrefix cases pass. - Polling watchdog kill path: NOT yet tested end-to-end (no spike test run against a live process). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves _memory_watchdog, _hard_kill, and related state out of LogosManager into a standalone MemoryWatchdog class (ou_dedetai/watchdog.py) with no App dependency — LogosManager wraps run() and calls app.exit() if kill_reason is set after the loop returns. Adds tests/test_watchdog.py covering: real-subprocess kill with a dynamically-measured baseline RSS cap, dead-process/NoSuchProcess/non-Popen skip paths, clean stop, reset, and SIGTERM→SIGKILL escalation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This comment was marked as outdated.
This comment was marked as outdated.
ctrlaltf24
commented
Jun 16, 2026
I was able to trigger a full system OOM freeze by opening enough Logos tabs to nearly fill system memory and then opening another Electron app that presumably triggered an overcommit. I've also been able to cause the OOM killer to terminate other apps by opening enough Logos resources. Do either of those cases sound like what you're seeking live testing for on this PR? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two-layer memory protection prevents a runaway Wine/Logos process (e.g.
the indexer) from exhausting the host.
Primary – systemd-run cgroup enforcement:
When systemd-run is available, wine commands are wrapped in a transient
user scope with MemoryMax set to 90% of currently-available RAM. The
kernel enforces the limit at the cgroup level with no polling overhead.
Availability is probed once at runtime and cached.
Fallback – Python RSS watchdog:
A background thread (LogosManager._memory_watchdog) polls the resident
set size of the entire Wine process tree every two seconds. The cap is
90% of the RAM that would be free if the monitored process were not
running (available + process_rss), so the process's own growing
footprint is never counted against its budget — only other processes
can move the ceiling. If the cap is exceeded, the process group is
SIGTERM'd with a 10-second grace period before escalating to SIGKILL.
The watchdog starts when Logos launches or indexing begins, and is
stopped cleanly by end_processes(). Also fixes the AppImage download
gate to skip the download when the wine binary is set to a recommended
or beta sigil.
Tested:
run-*.scope with MemoryMax=1612958515 (~90% of available RAM at
launch). Scope appeared in
systemctl --user list-units --type=scope.created and Wine launched unwrapped; watchdog remains the sole enforcer.
cases pass.
run against a live process).
Have not tested with a real Logos installation - only mock tests. Mostly AI written tests. Would be wise to test manually if we know how to make the memory footprint trigger a crash
Fixes: #457