forked from akai/readest
fix(koplugin): harden cover-download subprocess against Adreno exit crash (#4169)
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.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user