Skip to content

Run crunch_artifacts on a worker thread so the GUI stays responsive - #196

Open
OneSixForensics wants to merge 1 commit into
abrignoni:mainfrom
OneSixForensics:feat/gui-worker-thread
Open

Run crunch_artifacts on a worker thread so the GUI stays responsive#196
OneSixForensics wants to merge 1 commit into
abrignoni:mainfrom
OneSixForensics:feat/gui-worker-thread

Conversation

@OneSixForensics

Copy link
Copy Markdown

The problem

The GUI called crunch_artifacts() directly on the Tk main thread from the Process button handler, so for the whole length of a run the only thing pumping the event loop was log_text.update() inside logfunc (scripts/ilapfuncs.py). Between log lines the window stops answering Windows, which paints "(Not Responding)" over a run that is perfectly healthy. A single slow artifact is enough to trigger it, and an examiner watching a report that looks hung has no way to tell it apart from one that is.

The change

crunch_artifacts runs on a daemon worker thread and reports back over a queue.Queue that an after() poller drains on the main thread.

  • GuiWindow.message_queue is set while a run is in flight. logfunc and SetProgressBar check it and enqueue instead of touching a widget, so the worker never calls into Tk.
  • The poller drains the queue every 50 ms and does one log_text.insert() per poll rather than one per line, which is also less work than the old path.
  • The completion UI moved into finish_crunch(), called by the poller on the main thread.
  • initialize_lava() moved out of process() and into the worker. A sqlite3 connection may only be used by the thread that created it, so LAVA's connection has to be opened on the same thread the artifacts run on. Opening it on the main thread raises SQLite objects created in a thread can only be used in that same thread on the first artifact that writes.

The scripts/ilapfuncs.py half is identical in all five LEAPP cores. The GUI half keeps each core's own crunch_artifacts signature.

Measured

logfunc and SetProgressBar are the real ones, driven through the real message_queue gate; only the workload is simulated (12 artifacts, one of them working 2.5 s without logging). Worst event-loop stall, and how many times the loop was serviced:

before after
worst stall 2.55 s 0.01 s
loop serviced ~300x ~3300x

Windows paints "(Not Responding)" at roughly 5 s, so 2.55 s is the visible symptom's floor rather than its ceiling — a genuinely slow artifact stalls for longer.

Verified on a real run

Full GUI run against a real Berla iVe Ford Sync 3 acquisition (the DCASourceFilesUpload.zip from an iVe case folder): 12 artifacts, 6,730 rows, no errors.

The other four LEAPP cores carry the same change on a branch of this name; each was verified against its own real data, since each keeps its own crunch_artifacts signature and passing one does not clear the others.

The Process button called crunch_artifacts directly on the Tk main thread, so for the
whole run the only thing pumping the event loop was log_text.update() inside logfunc.
Between two log lines the window is unserviced: a slow artifact that works for seconds
without logging leaves Windows painting "(Not Responding)" over a run that is healthy,
and the log and progress bar only move when a line happens to be emitted.

crunch_artifacts now runs on a daemon worker thread and reports back over a queue that
an after() poller drains on the main thread. Tk is not thread-safe, so the worker never
touches a widget: while GuiWindow.message_queue is set, logfunc and SetProgressBar put
messages on the queue instead, and poll_crunch does every insert and config on the main
thread. The queue is cleared when the run ends, so a CLI run and a GUI run that has
finished keep the previous direct-write behaviour.

initialize_lava() moved from process() into the worker with it. sqlite3 objects may only
be used by the thread that created them, so a LAVA connection opened on the main thread
raises ProgrammingError on the first artifact search pattern the worker inserts. Opening
it on the thread that uses it is the fix; nothing on the main thread touches lava_db.

The completion UI moved to finish_crunch() unchanged; process() now ends by starting the
worker. An unhandled exception in the worker is caught and sent as a 'failed' message, so
a crash surfaces the existing error dialog instead of leaving the poller waiting forever.

Log lines are inserted once per poll rather than once per write, which also drops the
per-line update() that made a chatty artifact cost more in the widget than in the parser.

Measured on a synthetic 12-artifact run where one artifact works 2.5s without logging:
worst event-loop stall 2.50s -> 0.17s, and 13 -> 40+ loop slices served over the run.

Ported from the iLEAPP change, including the lava thread-affinity fix a real run there
surfaced; the twin was read rather than copied blind, and the crunch_artifacts call in
this core keeps its own signature. Not yet run against a real image in this core.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant