Skip to content

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

Merged
mtwebster merged 2 commits into
linuxmint:masterfrom
Cigydd:fix/asynctask-teardown-double-unref
Nov 13, 2026
Merged

mtwebster merged 2 commits into
linuxmint:masterfrom
Cigydd:fix/asynctask-teardown-double-unref

Conversation

@Cigydd

@Cigydd Cigydd commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

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

Cigydd and others added 2 commits June 16, 2026 09:51
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>
- Finish synchronously if the waiter thread can't be created.
- Close reader streams in a finally block so they're released on error.
- Mark the task finished with exit code -1 if the script fails to spawn,
  so callers polling the status don't hang.
@mtwebster
mtwebster merged commit c112e86 into linuxmint:master Nov 13, 2026
@mtwebster

Copy link
Copy Markdown
Member

I updated the final commit with what turned out to be the real explanation of this after running some trials. Thanks

brahm pushed a commit to brahm/linux-para-todos-os-roles that referenced this pull request Nov 13, 2026
…ida na 26.11.0

O Timeshift até a 26.11.0 tem uma corrida entre as threads que leem a
saída do rsync: as duas podiam chamar finish() juntas e soltar o mesmo
objeto duas vezes (SIGSEGV em g_object_unref). A correção entrou em
linuxmint/timeshift#552. A segunda tentativa continua, porque o Ubuntu
24.04 traz a 24.01.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NiPLVTYxtUeTJLTpe2p517
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

2 participants