Skip to content

After a caught native fault the app keeps using the damaged engine - #168

Merged
xroche merged 8 commits into
masterfrom
fix-161-native-fault-stop
Aug 24, 2026
Merged

After a caught native fault the app keeps using the damaged engine#168
xroche merged 8 commits into
masterfrom
fix-161-native-fault-stop

Conversation

@xroche

@xroche xroche commented Aug 24, 2026

Copy link
Copy Markdown
Owner

A recovered native fault is a siglongjmp: nothing unwinds. The engine's process-wide mutexes and the heap stay as the faulting frame left them, and the lock the crawl held is never released. We reported the fault and went on using that engine.

The fault is now latched where it is caught, and every entry point back into the engine refuses from then on. The ones that cannot throw refuse quietly and without taking that lock: stop() (click handler), free() (finalizer) and init() (constructor). A crawl is refused before it starts, the panel says why, and the process ends when the activity finishes, after onDestroy() has saved the profile.

NativeFaultPolicy holds the three decisions this puts on the UI, so a truth table can reach them without a device. One pre-existing bug came along: stop() threw "null context" without returning, then dereferenced it on the next line.

Closes #161

xroche and others added 8 commits August 24, 2026 21:29
coffeecatch recovers a SIGSEGV with siglongjmp, which unwinds nothing:
hts_init() ran, hts_uninit() never will, and the cache file, the sockets
and any lock the faulting frame held stay as the fault left them. We
reported the fault and carried on using that engine.

Latch the fault where it is caught, and refuse every entry point that
reaches the engine from then on. stop(), abortCode(), wasStopped() and
free() refuse without taking the engine lock, which the faulting frame
may still hold, and without throwing into a click handler; buildTopIndex()
refuses without throwing, since its callers dump what they catch over the
dump describing the fault. The crawl is refused before it starts, the
error panel says so, and leaving that panel ends the process, so the next
mirror runs on a fresh one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
It runs from the Runner's field initializer, so an exception there takes
the activity down before the crawl that faulted ever reports it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Android hands a relaunch whatever process it kept, latch and all, which
would leave the engine refused with nothing on screen to explain it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Neither catch tolerates a fault to spare the process any more.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Review found the exit lost data: finish() only schedules the destroy, so
System.exit(0) on the next line beat onDestroy() and its serialize(). The
two new call sites now finish() and let onDestroy() end the process once
it has saved. The root-deleted path had the same shape and is folded into
the same flag.

COFFEE_CATCH also runs when coffeecatch_setup() fails, with no signal
delivered and the engine intact, so the latch now asks for the signal.
stop() threw "null context" without returning, then dereferenced it.

The decisions the activity makes move into NativeFaultPolicy, where a
truth table can reach them: the source-text tests could only see the
vocabulary of a guard, so an inverted test, a missing return or a
commented-out latch all passed. Tests now read the C with comments and
literals blanked, and the brace scanner is shared rather than a third copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Folding it into the deferred exit let onDestroy() serialize on the way
out, and that mkdirs the project and cache directories the user had just
deleted. It has to end the process itself, so a test now says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche
xroche merged commit f9f68bb into master Aug 24, 2026
6 checks passed
@xroche
xroche deleted the fix-161-native-fault-stop branch August 24, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

After a caught native fault the app keeps using the damaged engine

1 participant