From e0cb433550022d7d616c754e9e7969c977e02735 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Fri, 15 May 2026 16:52:09 +0800 Subject: [PATCH] fix(koplugin): harden cover-download subprocess against Adreno exit crash (#4169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Browsing a folder in the Readest Library spawns a forked child per cloud cover via syncbooks.downloadCover. On Boox / Adreno devices the child crashed with a SIGSEGV (issue #4165): it terminated through the libc exit() path, and __cxa_finalize ran the destructor of the GL driver inherited from the parent, which segfaults on Adreno. Terminate the child with ffi.C._exit(0) instead. _exit() skips libc atexit handlers, so __cxa_finalize — and the Adreno destructor it runs — never execute. The body is also wrapped in pcall so a network error in http.request cannot unwind past that _exit call. This eliminates the child-side crash in the reported tombstone. The parent KOReader exiting is most likely a knock-on effect of the child tearing down GPU state shared across the fork, but that link is not provable from the log alone — so this intentionally does not auto-close #4165 until confirmed on an affected device. No unit test: the fork + network path isn't reproducible in the busted harness, consistent with the other network methods in this file. --- apps/readest.koplugin/library/syncbooks.lua | 44 ++++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/apps/readest.koplugin/library/syncbooks.lua b/apps/readest.koplugin/library/syncbooks.lua index 628135bb..0e93e258 100644 --- a/apps/readest.koplugin/library/syncbooks.lua +++ b/apps/readest.koplugin/library/syncbooks.lua @@ -592,16 +592,32 @@ function M.downloadCover(book, opts, cb) local pid, parent_read_fd = FFIUtil.runInSubProcess( function(_child_pid, child_write_fd) - local socket = require("socket") - local http = require("socket.http") - local socketutil = require("socketutil") - local ltn12 = require("ltn12") - + -- Runs in a forked child. Two hard rules, both to keep + -- KOReader alive on Boox / Adreno devices (issue #4165): + -- + -- 1. No Lua error may escape this function. An uncaught + -- error unwinds back to KOReader's android_main, + -- which terminates the child through the libc exit() + -- path — running __cxa_finalize. + -- 2. Terminate via _exit(), never exit(): __cxa_finalize + -- runs the destructor of the GL driver inherited from + -- the parent, which segfaults on Adreno and takes the + -- whole app down with it. + -- + -- A network failure in http.request is exactly the kind + -- of error rule 1 guards against, so wrap the body. local result - local f, ferr = io.open(dst, "wb") - if not f then - result = "error:open:" .. tostring(ferr) - else + local ok, err = pcall(function() + local socket = require("socket") + local http = require("socket.http") + local socketutil = require("socketutil") + local ltn12 = require("ltn12") + + local f, ferr = io.open(dst, "wb") + if not f then + result = "error:open:" .. tostring(ferr) + return + end socketutil:set_timeout(socketutil.FILE_BLOCK_TIMEOUT, socketutil.FILE_TOTAL_TIMEOUT) local code = socket.skip(1, http.request{ url = url, @@ -616,8 +632,16 @@ function M.downloadCover(book, opts, cb) else result = "error:http:" .. tostring(code) end + end) + if not ok then + result = "error:exception:" .. tostring(err) end - FFIUtil.writeToFD(child_write_fd, result, true) + pcall(FFIUtil.writeToFD, child_write_fd, result or "error:unknown", true) + + -- Hard exit, bypassing libc atexit handlers (rule 2). + local ffi = require("ffi") + pcall(ffi.cdef, "void _exit(int status);") + ffi.C._exit(0) end, true) -- with_pipe = true