From eac8feb62cd89022fe9ab6cc7672f1fad9013f1d Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Thu, 14 May 2026 11:52:28 +0200 Subject: [PATCH] fix: fix periodic freeze in print stack --- programs/include/print/print.hpp | 92 +++++++++++++++++--------------- programs/src/printd/main.cpp | 29 ++++++++-- programs/src/printers/main.cpp | 57 +++++++++++++++++--- 3 files changed, 126 insertions(+), 52 deletions(-) diff --git a/programs/include/print/print.hpp b/programs/include/print/print.hpp index 52590f0..a9d61c8 100644 --- a/programs/include/print/print.hpp +++ b/programs/include/print/print.hpp @@ -188,6 +188,34 @@ inline void sanitize_field(char* s) { } } +inline bool read_text_file_into_buffer(const char* path, char* out, int out_len) { + if (path == nullptr || out == nullptr || out_len <= 0) return false; + FILE* f = fopen(path, "rb"); + if (!f) return false; + + size_t n = fread(out, 1, (size_t)out_len - 1, f); + fclose(f); + out[n] = '\0'; + return n > 0; +} + +template +inline void parse_key_value_lines(char* text, Fn fn) { + if (text == nullptr) return; + char* cursor = text; + while (*cursor) { + char* line = cursor; + while (*cursor && *cursor != '\n') cursor++; + if (*cursor == '\n') *cursor++ = '\0'; + + trim_line(line); + char* eq = strchr(line, '='); + if (!eq) continue; + *eq++ = '\0'; + fn(line, eq); + } +} + inline void now_string(char* out, int out_len) { if (out == nullptr || out_len <= 0) return; Montauk::DateTime dt = {}; @@ -264,13 +292,15 @@ inline void make_doc_file_path(const char* id, char* out, int out_len) { inline bool read_text_file_line(const char* path, char* out, int out_len) { if (path == nullptr || out == nullptr || out_len <= 0) return false; - FILE* f = fopen(path, "rb"); - if (!f) return false; - out[0] = '\0'; - bool ok = fgets(out, out_len, f) != nullptr; - fclose(f); - if (ok) trim_line(out); - return ok; + if (!read_text_file_into_buffer(path, out, out_len)) return false; + for (int i = 0; out[i] != '\0'; i++) { + if (out[i] == '\r' || out[i] == '\n') { + out[i] = '\0'; + break; + } + } + trim_line(out); + return true; } inline bool write_text_file_atomic(const char* path, const char* text) { @@ -430,23 +460,15 @@ inline bool load_known_printer_from_path(const char* path, KnownPrinter* printer if (path == nullptr || printer == nullptr) return false; zero_known_printer(printer); - FILE* f = fopen(path, "rb"); - if (!f) return false; - - char line[384]; - while (fgets(line, sizeof(line), f) != nullptr) { - trim_line(line); - char* eq = strchr(line, '='); - if (!eq) continue; - *eq++ = '\0'; - + char text[1024]; + if (!read_text_file_into_buffer(path, text, sizeof(text))) return false; + parse_key_value_lines(text, [&](const char* line, const char* eq) { if (strcmp(line, "uri") == 0) safe_copy(printer->uri, sizeof(printer->uri), eq); else if (strcmp(line, "display_name") == 0) safe_copy(printer->display_name, sizeof(printer->display_name), eq); else if (strcmp(line, "last_seen") == 0) safe_copy(printer->last_seen, sizeof(printer->last_seen), eq); else if (strcmp(line, "status_message") == 0) safe_copy(printer->status_message, sizeof(printer->status_message), eq); else if (strcmp(line, "ok") == 0) printer->ok = atoi(eq) != 0; - } - fclose(f); + }); return printer->uri[0] != '\0'; } @@ -826,16 +848,9 @@ inline bool load_job_from_path(const char* path, JobMeta* job) { if (path == nullptr || job == nullptr) return false; zero_job(job); - FILE* f = fopen(path, "rb"); - if (!f) return false; - - char line[384]; - while (fgets(line, sizeof(line), f) != nullptr) { - trim_line(line); - char* eq = strchr(line, '='); - if (!eq) continue; - *eq++ = '\0'; - + char text[4096]; + if (!read_text_file_into_buffer(path, text, sizeof(text))) return false; + parse_key_value_lines(text, [&](const char* line, const char* eq) { if (strcmp(line, "id") == 0) safe_copy(job->id, sizeof(job->id), eq); else if (strcmp(line, "state") == 0) safe_copy(job->state, sizeof(job->state), eq); else if (strcmp(line, "source_kind") == 0) safe_copy(job->source_kind, sizeof(job->source_kind), eq); @@ -852,8 +867,7 @@ inline bool load_job_from_path(const char* path, JobMeta* job) { else if (strcmp(line, "remote_job_id") == 0) job->remote_job_id = atoi(eq); else if (strcmp(line, "size_bytes") == 0) job->size_bytes = atoi(eq); else if (strcmp(line, "copies") == 0) job->copies = atoi(eq); - } - fclose(f); + }); if (job->copies <= 0) job->copies = 1; return job->id[0] != '\0'; } @@ -914,16 +928,9 @@ inline bool load_probe_state(const char* path, ProbeState* state) { if (path == nullptr || state == nullptr) return false; zero_probe_state(state); - FILE* f = fopen(path, "rb"); - if (!f) return false; - - char line[512]; - while (fgets(line, sizeof(line), f) != nullptr) { - trim_line(line); - char* eq = strchr(line, '='); - if (!eq) continue; - *eq++ = '\0'; - + char text[4096]; + if (!read_text_file_into_buffer(path, text, sizeof(text))) return false; + parse_key_value_lines(text, [&](const char* line, const char* eq) { if (strcmp(line, "ok") == 0) state->ok = atoi(eq) != 0; else if (strcmp(line, "printer_uri") == 0) safe_copy(state->printer_uri, sizeof(state->printer_uri), eq); else if (strcmp(line, "probed_at") == 0) safe_copy(state->probed_at, sizeof(state->probed_at), eq); @@ -946,8 +953,7 @@ inline bool load_probe_state(const char* path, ProbeState* state) { else if (strcmp(line, "caps_supported_formats") == 0) safe_copy(state->caps.supported_formats, sizeof(state->caps.supported_formats), eq); else if (strcmp(line, "caps_default_format") == 0) safe_copy(state->caps.default_format, sizeof(state->caps.default_format), eq); else if (strcmp(line, "caps_preferred_format") == 0) safe_copy(state->caps.preferred_format, sizeof(state->caps.preferred_format), eq); - } - fclose(f); + }); return state->printer_uri[0] != '\0' || state->message[0] != '\0'; } diff --git a/programs/src/printd/main.cpp b/programs/src/printd/main.cpp index 05f7bc4..9573626 100644 --- a/programs/src/printd/main.cpp +++ b/programs/src/printd/main.cpp @@ -16,6 +16,12 @@ extern "C" { using namespace print; static constexpr bool ENABLE_BACKGROUND_PROBE = false; +static constexpr uint64_t DAEMON_STATE_HEARTBEAT_MS = 15000; + +static char g_last_state_phase[32] = {}; +static char g_last_state_subject[128] = {}; +static char g_last_state_detail[256] = {}; +static uint64_t g_last_state_write_ms = 0; enum class ClaimResult { None, @@ -25,6 +31,23 @@ enum class ClaimResult { static void write_daemon_state(const char* phase, const char* subject = nullptr, const char* detail = nullptr) { + const char* safe_phase = phase ? phase : ""; + const char* safe_subject = subject ? subject : ""; + const char* safe_detail = detail ? detail : ""; + uint64_t now_ms = montauk::get_milliseconds(); + + if (g_last_state_write_ms != 0 + && strcmp(g_last_state_phase, safe_phase) == 0 + && strcmp(g_last_state_subject, safe_subject) == 0 + && strcmp(g_last_state_detail, safe_detail) == 0 + && now_ms - g_last_state_write_ms < DAEMON_STATE_HEARTBEAT_MS) + return; + + safe_copy(g_last_state_phase, sizeof(g_last_state_phase), safe_phase); + safe_copy(g_last_state_subject, sizeof(g_last_state_subject), safe_subject); + safe_copy(g_last_state_detail, sizeof(g_last_state_detail), safe_detail); + g_last_state_write_ms = now_ms; + char now[32] = {}; now_string(now, sizeof(now)); @@ -33,9 +56,9 @@ static void write_daemon_state(const char* phase, const char* subject = nullptr, "time=%s\npid=%d\nphase=%s\nsubject=%s\ndetail=%s\n", now, montauk::getpid(), - phase ? phase : "", - subject ? subject : "", - detail ? detail : ""); + safe_phase, + safe_subject, + safe_detail); if (n < 0) return; write_text_file_atomic(SPOOL_DAEMON_STATE_PATH, buf); } diff --git a/programs/src/printers/main.cpp b/programs/src/printers/main.cpp index 77ea938..c8130db 100644 --- a/programs/src/printers/main.cpp +++ b/programs/src/printers/main.cpp @@ -423,11 +423,35 @@ static void compute_config_layout(ConfigLayout* lo) { lo->cancel_btn = {pad + 88, g_config.height - BTN_H - 16, 80, BTN_H}; } -static void refresh_state(bool force = false) { +static bool refresh_state(bool force = false) { uint64_t now = montauk::get_milliseconds(); - if (!force && g_app.printers_loaded && now - g_app.last_refresh_ms < 1000) return; + if (!force && g_app.printers_loaded && now - g_app.last_refresh_ms < 1000) return false; - ensure_spool_dirs(); + bool was_loaded = g_app.printers_loaded; + char old_printer_uri[MAX_PATH_LEN]; + safe_copy(old_printer_uri, sizeof(old_printer_uri), g_app.printer_uri); + bool old_daemon_running = g_app.daemon_running; + int old_daemon_pid = g_app.daemon_pid; + int old_queue_count = g_app.queue_count; + int old_active_count = g_app.active_count; + int old_done_count = g_app.done_count; + int old_failed_count = g_app.failed_count; + JobMeta old_active = g_app.last_active; + JobMeta old_failed = g_app.last_failed; + bool old_has_active = g_app.has_active; + bool old_has_failed = g_app.has_failed; + IppCapabilities old_probe_caps = g_app.probe_caps; + bool old_probe_valid = g_app.probe_valid; + bool old_probe_ok = g_app.probe_ok; + char old_probe_message[sizeof(g_app.probe_message)]; + char old_probe_time[sizeof(g_app.probe_time)]; + char old_probe_detail[sizeof(g_app.probe_detail)]; + safe_copy(old_probe_message, sizeof(old_probe_message), g_app.probe_message); + safe_copy(old_probe_time, sizeof(old_probe_time), g_app.probe_time); + safe_copy(old_probe_detail, sizeof(old_probe_detail), g_app.probe_detail); + + if (force || !g_app.printers_loaded) + ensure_spool_dirs(); if (!read_default_printer_uri(g_app.printer_uri, sizeof(g_app.printer_uri))) g_app.printer_uri[0] = '\0'; @@ -449,6 +473,25 @@ static void refresh_state(bool force = false) { g_app.has_failed = load_latest_job(SPOOL_FAILED_DIR, &g_app.last_failed); g_app.printers_loaded = true; g_app.last_refresh_ms = now; + + return !was_loaded + || !montauk::streq(old_printer_uri, g_app.printer_uri) + || old_daemon_running != g_app.daemon_running + || old_daemon_pid != g_app.daemon_pid + || old_queue_count != g_app.queue_count + || old_active_count != g_app.active_count + || old_done_count != g_app.done_count + || old_failed_count != g_app.failed_count + || old_has_active != g_app.has_active + || (g_app.has_active && memcmp(&old_active, &g_app.last_active, sizeof(g_app.last_active)) != 0) + || old_has_failed != g_app.has_failed + || (g_app.has_failed && memcmp(&old_failed, &g_app.last_failed, sizeof(g_app.last_failed)) != 0) + || old_probe_valid != g_app.probe_valid + || old_probe_ok != g_app.probe_ok + || !montauk::streq(old_probe_message, g_app.probe_message) + || !montauk::streq(old_probe_time, g_app.probe_time) + || !montauk::streq(old_probe_detail, g_app.probe_detail) + || memcmp(&old_probe_caps, &g_app.probe_caps, sizeof(g_app.probe_caps)) != 0; } static void probe_printer() { @@ -1207,10 +1250,12 @@ extern "C" void _start() { int r = g_win.poll(&ev); if (r < 0) break; if (r == 0) { - uint64_t last_refresh = g_app.last_refresh_ms; - refresh_state(false); - if (g_app.last_refresh_ms != last_refresh) + if (refresh_state(false)) redraw_main = true; + if (g_app.status_msg[0] && !status_visible()) { + g_app.status_msg[0] = '\0'; + redraw_main = true; + } if (redraw_main) render();