Skip to content

AsyncTask: fix double-unref crash on teardown after completion#552

Open
Cigydd wants to merge 1 commit into
linuxmint:masterfrom
Cigydd:fix/asynctask-teardown-double-unref
Open

AsyncTask: fix double-unref crash on teardown after completion#552
Cigydd wants to merge 1 commit into
linuxmint:masterfrom
Cigydd:fix/asynctask-teardown-double-unref

Conversation

@Cigydd

@Cigydd Cigydd commented Jun 16, 2026

Copy link
Copy Markdown

Fixes #551.

Problem

AsyncTask's reader threads (read_stdout / read_stderr) clear their stdout_is_open / stderr_is_open flags before closing and releasing their DataInputStream, so the first reader to finish can call finish() (→ status = FINISHED, task_complete()) while the other reader is still inside dis_*.close() / dis_* = null. The owner, polling while (task.status == AppStatus.RUNNING), then tears the task down while a reader is still unreffing its stream → double g_object_unref of the underlying UnixInputStream → SIGSEGV during teardown. The reader threads are also created detached and never joined, and the finish_called guard is unsynchronized. Sporadically observed from --check --scripted cron runs. Full backtrace and ThreadSanitizer evidence are in #551.

Fix

  • Keep handles to both reader threads.
  • Add a small coordinator thread (wait_and_finish) that join()s both readers — guaranteeing their streams are fully closed — before calling finish() exactly once.
  • Remove the racy stdout_is_open / stderr_is_open flags and the finish() calls from the reader threads.

Verification

  • meson compile clean.
  • --create --scripted creates a snapshot successfully with no crash.
  • ThreadSanitizer (-Db_sanitize=thread) before/after: the finish_called / concurrent-finish() data race reported on the old code is gone after the fix; only pre-existing benign lock-free polling races on progress counters remain.

🤖 Generated with Claude Code

The two reader threads (read_stdout/read_stderr) cleared their
stdout_is_open/stderr_is_open flags *before* closing and releasing their
DataInputStream. Whichever reader finished first could therefore see
both flags clear and call finish() — signalling completion (status =
FINISHED, task_complete()) — while the peer thread was still inside
dis_*.close() / dis_* = null. The owner, seeing the task finished, then
tore it down while a reader was still unreffing its stream, double-
unreffing the underlying UnixInputStream and crashing in g_object_unref
(SIGSEGV during teardown, observed sporadically from `--check --scripted`
cron runs).

The reader threads were also created detached and never joined, so a
reader could outlive completion signalling entirely.

Fix: keep handles to both reader threads and add a small coordinator
thread (wait_and_finish) that joins both readers — guaranteeing their
streams are fully closed — before calling finish() exactly once. Remove
the racy stdout_is_open/stderr_is_open flags and the finish() calls from
the reader threads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

AsyncTask: SIGSEGV (double g_object_unref) during teardown after a snapshot completes

1 participant