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:
co-authored by
Claude Opus 5.5
parent
940f63e0e5
commit
3855989029
@@ -682,11 +682,11 @@ class Library(Gtk.Window):
|
||||
would make the buttons unclickable.
|
||||
"""
|
||||
return tuple(
|
||||
(p.id, p.state, p.pages, p.thumbnail_page, p.title)
|
||||
(p.id, p.state, p.pages, p.thumbnail_page, p.title, p.failure)
|
||||
for p in presentations
|
||||
)
|
||||
|
||||
def _fit_window(self, count):
|
||||
def _fit_window(self, count, tall=False):
|
||||
"""Size to the cards while staying inside the panel's work area."""
|
||||
workarea = monitor_workarea(self)
|
||||
maximum_width = max(420, int(workarea.width * 0.92))
|
||||
@@ -705,7 +705,10 @@ class Library(Gtk.Window):
|
||||
columns = min(count, available, 4)
|
||||
rows = (count + columns - 1) // columns
|
||||
width = min(maximum_width, max(420, 40 + columns * card_with_gap))
|
||||
height = min(maximum_height, 130 + rows * 340)
|
||||
# A card explaining a memory failure carries three lines of
|
||||
# words above its buttons, not one; measured, at 340 its Try
|
||||
# again was cut off below the window's edge.
|
||||
height = min(maximum_height, 130 + rows * (400 if tall else 340))
|
||||
|
||||
self.resize(width, height)
|
||||
|
||||
@@ -735,14 +738,19 @@ class Library(Gtk.Window):
|
||||
# something is "being prepared" when it never will be is how
|
||||
# somebody ends up waiting through a Bible study for it.
|
||||
ready = sum(1 for p in presentations if p.ready)
|
||||
broken = sum(1 for p in presentations if p.failed)
|
||||
working = len(presentations) - ready - broken
|
||||
again = sum(1 for p in presentations if p.failure == "memory")
|
||||
broken = sum(1 for p in presentations
|
||||
if p.failed and p.failure != "memory")
|
||||
working = len(presentations) - ready - broken - again
|
||||
|
||||
parts = [f"{ready} ready to present"]
|
||||
|
||||
if working:
|
||||
parts.append(f"{working} still being prepared")
|
||||
|
||||
if again:
|
||||
parts.append(f"{again} to try again")
|
||||
|
||||
if broken:
|
||||
parts.append(f"{broken} could not be opened")
|
||||
|
||||
@@ -752,7 +760,8 @@ class Library(Gtk.Window):
|
||||
self.grid.add(self.card(presentation))
|
||||
|
||||
self.grid.show_all()
|
||||
self._fit_window(len(presentations))
|
||||
self._fit_window(len(presentations),
|
||||
tall=any(p.failure == "memory" for p in presentations))
|
||||
|
||||
def card(self, presentation):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
||||
@@ -802,6 +811,20 @@ class Library(Gtk.Window):
|
||||
look = Gtk.Button(label="Quick Look")
|
||||
look.connect("clicked", lambda *_: self.quick_look(presentation))
|
||||
buttons.pack_start(look, True, True, 0)
|
||||
elif presentation.failure == "memory":
|
||||
# The computer ran short of memory getting it ready, or the
|
||||
# importer stopped part way. Worth another go with less open;
|
||||
# never a converter's message, a path or a terminal.
|
||||
note.set_text("This needed more memory than the computer had "
|
||||
"free. Close other programs, then choose "
|
||||
"Try again.")
|
||||
note.set_line_wrap(True)
|
||||
note.set_max_width_chars(34)
|
||||
|
||||
again = Gtk.Button(label="Try again")
|
||||
again.get_style_context().add_class("primary")
|
||||
again.connect("clicked", lambda *_: self.try_again(presentation))
|
||||
buttons.pack_start(again, True, True, 0)
|
||||
elif presentation.failed:
|
||||
# SHORT, AND IN WORDS A TEACHER CAN ACT ON.
|
||||
#
|
||||
@@ -878,6 +901,12 @@ class Library(Gtk.Window):
|
||||
self._signature = None
|
||||
self.refresh()
|
||||
|
||||
def try_again(self, presentation):
|
||||
# The importer takes it up within a few seconds; the card says
|
||||
# "Getting this ready" again at the next refresh.
|
||||
if library.request_retry(presentation):
|
||||
self.refresh()
|
||||
|
||||
def remove(self, presentation):
|
||||
dialog = Gtk.MessageDialog(
|
||||
transient_for=self,
|
||||
|
||||
Reference in New Issue
Block a user