fix: a presentation that runs out of memory says so, and the service survives it

On a 4 GB VM the kernel killed the import service mid-conversion. Measured:
LibreOffice needs 0.2 GB for a text deck and 1.4-2.1 GB for decks of many
different photographs, for about twenty seconds — a spike, not a leak.
What made it a problem was the rest: OOMPolicy=stop took the watcher down
with LibreOffice, it restarted and resumed the same conversion with no
limit, and the library said "Getting this ready" throughout.

The service is capped at 40% of memory, is first in line when memory runs
short everywhere, and survives a killed LibreOffice (OOMPolicy=continue).
A killed conversion is a failed import of kind "memory"; an import
interrupted twice is not tried a third time; the card says the computer
needed more memory, to close other programs and choose Try again; an
import nobody has worked on for 20 minutes shows the same card; and an
exclusive lock keeps it to one import at a time.

Lab, the real service and LibreOffice: at a 4 GB-machine ceiling the
2.1 GB deck still imported; with no swap and a 300 MB ceiling LibreOffice
was killed, the watcher kept its PID and the card offered Try again,
which worked; the whole service killed during two attempts gave up after
the second; three decks at once never ran two LibreOffices together.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-09-23 22:16:50 -05:00
co-authored by Claude Opus 5.5
parent 940f63e0e5
commit 3855989029
8 changed files with 394 additions and 18 deletions
+54
View File
@@ -128,6 +128,60 @@ A file that cannot be converted gets a card saying so, offering only
**Remove**. Its managed original remains recoverable even when PDF or page
generation fails.
## Memory (0.2.6.2)
On a 4 GB Plasma VM the kernel killed `ekklesia-classroom-presentations.service`
mid-import and Plasma said *Memory Shortage Avoided*. Measured afterwards,
LibreOffice converting one deck with the importer's own code:
| Deck | PPTX | LibreOffice peak | Rendering peak |
| --- | --- | --- | --- |
| 10 slides of text | 20 KB | 203 MB, 0.8 s | 38 MB |
| 30 slides, 12 photos shared | 23.5 MB | 738 MB, 16 s | 70 MB |
| 60 slides, the same 12 photos | 23.6 MB | 754 MB, 28 s | 70 MB |
| 30 slides, 30 different 12 MP photos | 58.7 MB | **1.43 GB**, 22 s | 69 MB |
| 20 slides, 20 different 27 MP photos | 143 MB | **2.08 GB**, 24 s | 70 MB |
It grows with the photographs, not the slides, and it is a spike rather
than a leak. PDF export with images reduced to 200 dpi saved only 5%,
because LibreOffice holds the full images regardless. So the failure was a
photo-heavy deck meeting a 4 GB machine with the room's other programs
open — and what made it a problem was what happened next. systemd's
default `OOMPolicy=stop` took the whole service down with the killed
LibreOffice; it restarted, `sweep()` resumed the same conversion with no
limit, and the library said *Getting this ready…* throughout.
Now:
* **`MemoryMax=40%`** — 1.6 GB on a 4 GB machine, 3.2 GB on 8 GB. Measured
in the lab at 1.6 GB, the 2.1 GB deck still imported in 33 s: much of
that peak is file-backed and reclaimable, and on a machine with swap the
rest is swapped rather than killed (1.4 GB of it at a 200 MB ceiling).
* **`OOMScoreAdjust=800`** — under pressure everywhere, LibreOffice goes
before the lesson on the screen, the browser or the panel.
* **`OOMPolicy=continue`** — a killed LibreOffice is a failed import, not a
dead service. Measured with no swap and a 300 MB ceiling: the kernel
killed LibreOffice, the watcher kept its PID with no restart, and the
import was recorded as `failed`, kind `memory`.
* **Two attempts, then stop.** Each attempt is counted before it starts.
Measured by killing the whole service during attempt 1 and again during
attempt 2: it resumed once, then said "giving up … the teacher can try
again" and did not start a third.
* **The card says so**, and offers **Try again**: *This needed more memory
than the computer had free. Close other programs, then choose Try
again.* Try again was pressed in the lab and the deck became ready.
A file that simply will not convert still says *This file could not be
opened* and offers no Try again, because trying again will not help.
* **Never stuck.** An import still "getting ready" 20 minutes after it
began — longer than any attempt can take — is shown as the same Try
again card, because nothing is working on it.
* **One import at a time**, by an exclusive lock as well as the one loop;
measured, three decks downloaded together converted one after another
with never more than one LibreOffice running.
What the lab cannot give is a real 4 GB machine's mix of programs; the
ceilings were emulated per service.
## The three things kept apart
This is the part most worth not breaking.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB