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