linuxmint / linuxmint/timeshift
AsyncTask: SIGSEGV (double g_object_unref) during teardown after a snapshot completes
Nobody has claimed this yet.
- Dominant language
- Vala
- Stars
- 4.3k
- Forks
- 155
- PR merge metrics
- No merged PRs in 30d
Description
Describe the bug
Timeshift sporadically crashes with SIGSEGV during teardown, right after a snapshot has been created successfully. The snapshot itself is fine and no data is lost — only the cleanup crashes. The crash is a double g_object_unref in one of AsyncTask's reader threads while the underlying input stream is being released. It is triggered by the scheduled timeshift --check --scripted run from root cron and recurs over time.
To Reproduce
It is a timing-dependent race and does not reproduce on every run. It happens when:
- A snapshot is actually created (a due
--check --scripted, or--create --scripted). - The rsync child finishes and the two
AsyncTaskreader threads shut down. - Occasionally the process gets SIGSEGV during teardown (in
g_object_unref).
On the affected machine it was captured from cron three times — 2026-05-02, 2026-05-23 and 2026-06-15 (coredumpctl list /usr/bin/timeshift; older ones already rotated out).
Expected behavior
The process should exit cleanly after creating the snapshot, without crashing during teardown.
System:
- Linux Distribution Name and Version: Linux Mint 22.3 (zena)
- Desktop: MATE
- Timeshift Version: 25.12.4 (package
25.12.4+zena) - Timeshift Mode: rsync
- Display Server: N/A — crash occurs in the CLI binary launched headless from root cron
Additional context (root-cause analysis)
Symbolized backtrace (coredump resolved against a local debug build of the same source; the packaged binary is stripped):
#0 g_object_unref (libgobject) <- SIGSEGV
#1 (libgio) GFilterInputStream dispose -> unref of base stream
#2 g_object_unref (libgobject)
#3 async_task_read_stdout src/Utility/AsyncTask.vala (dis_out = null)
#4 _async_task_read_stdout_gthread_func
#5 g_thread_proxy (libglib)
The crash is on a reader thread, not the main thread.
In AsyncTask, read_stdout() / read_stderr() clear their stdout_is_open / stderr_is_open flags before closing and releasing the DataInputStream:
stdout_is_open = false; // flag cleared first
if (dis_out != null) { dis_out.close(); } // closed later (can be slow)
dis_out = null;
if (!stdout_is_open && !stderr_is_open) { finish(); }
So the first reader to finish can observe both flags clear and call finish() — which sets status = FINISHED and emits task_complete() — while the other reader is still inside dis_*.close() / dis_* = null. The owner (Main.create_snapshot_with_rsync, which polls while (task.status == AppStatus.RUNNING)) then proceeds to tear the task down while a reader thread is still unreffing its stream, double-unreffing the underlying UnixInputStream → SIGSEGV in g_object_unref.
Two contributing defects:
- The
*_is_openflags are cleared before the stream is closed, sofinish()can fire prematurely. - The reader threads are created detached (
new Thread<void>.try(...)) and never joined, so a reader can outlive completion signalling. Thefinish_calledguard is also read/written without synchronization.
ThreadSanitizer (meson … -Db_sanitize=thread -Db_pie=true, run via setarch -R, on --create) confirms it directly:
WARNING: ThreadSanitizer: data race (AsyncTask.vala:292 in async_task_finish)
Read of size 4 (finish_called) by thread T2:
async_task_finish AsyncTask.vala:292 // if (finish_called)
async_task_read_stdout AsyncTask.vala:220
Previous write of size 4 by thread T3:
async_task_finish AsyncTask.vala:293 // finish_called = true
async_task_read_stderr AsyncTask.vala:255
After the fix, ThreadSanitizer no longer reports the finish_called / concurrent-finish() race; only pre-existing benign lock-free polling races on the progress counters (progress, percent, prg_count, status) remain — those exist in both versions and don't cause the crash.
A pull request with a fix follows.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/Utility/AsyncTask.vala, focusing on read_stdout(), read_stderr(), finish(), and the detached reader threads. Review how Main.create_snapshot_with_rsync() polls task.status, then reproduce with the documented ThreadSanitizer build and --create or --check --scripted. Done means clean teardown after snapshot creation, no concurrent finish race, and no new sanitizer reports beyond the listed polling races.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- linux
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100