Skip to content

dump: skip POSIX timers targeting dead threads#2996

Open
pojntfx wants to merge 2 commits into
checkpoint-restore:criu-devfrom
loopholelabs:pojntfx/arch-951-upstream-criu-changes
Open

dump: skip POSIX timers targeting dead threads#2996
pojntfx wants to merge 2 commits into
checkpoint-restore:criu-devfrom
loopholelabs:pojntfx/arch-951-upstream-criu-changes

Conversation

@pojntfx
Copy link
Copy Markdown

@pojntfx pojntfx commented Apr 6, 2026

What:

When a POSIX timer uses SIGEV_THREAD_ID and the target thread has exited, dump fails in PID namespaces (Unable to convert the notify thread id) and restore fails without PID namespaces (EINVAL from timer_create()). We noticed this when an application switched from Java 8 to Java 21.

How:

To fix this, this PR detects dead notify threads during dump and skips the timer instead of failing. The kernel already silently drops signals from these timers, so skipping them during dump is safe.

Tests:

  • zdtm/static/dead_thread_posix_timer
    - zdtm/static/dead_thread_posix_timer_h

I added dead_thread_posix_timer_h since I wanted to specifically trigger the restore failure case, which needs host mode. I followed the convention that pthread_timers.c and pthread_timers_h.c uses.

pojntfx referenced this pull request in loopholelabs/criu Apr 7, 2026
…t/dead threads from checkpoint

Signed-off-by: Felicitas Pojtinger <felicitas@pojtinger.com>
@avagin avagin requested a review from Copilot April 16, 2026 15:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts CRIU’s POSIX timer dump logic to tolerate SIGEV_THREAD_ID timers whose notify thread has already exited, avoiding dump/restore failures seen in PID-namespace vs host-mode scenarios, and adds ZDTM coverage for both modes.

Changes:

  • Skip POSIX timers during dump when their SIGEV_THREAD_ID notify thread is detected as dead (instead of failing the dump).
  • Update the POSIX timer dump loop to compact the encoded timer list after skips (tte->n_posix reflects only kept timers).
  • Add two ZDTM static tests (dead_thread_posix_timer and _h host-flavor variant) and wire them into the static test Makefile.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
criu/timer.c Detects dead notify threads and skips affected timers; compacts dumped timer entries.
test/zdtm/static/dead_thread_posix_timer.c New ZDTM test creating a SIGEV_THREAD_ID timer targeting a thread that exits.
test/zdtm/static/dead_thread_posix_timer_h.c Host-flavor variant of the same test scenario.
test/zdtm/static/dead_thread_posix_timer.desc Runs the test with ns_pid feature enabled.
test/zdtm/static/dead_thread_posix_timer_h.desc Runs the test in host flavor ({'flavor': 'h'}).
test/zdtm/static/Makefile Registers the new tests and links them with -lrt -pthread.

Comment thread criu/timer.c
Comment on lines 330 to 341
for (i = 0; i < item->nr_threads; i++) {
if (item->threads[i].real != rtid)
continue;

vtid = item->threads[i].ns[0].virt;
break;
}

if (vtid == 0) {
pr_err("Unable to convert the notify thread id %d\n", rtid);
return -1;
pr_warn("Skipping timer because notify thread %d is dead\n", rtid);
return 1;
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the pid-namespace path, vtid==0 is treated as “notify thread is dead” and the timer is skipped. That also covers the case where the thread is found but its ns virt tid is unexpectedly 0 (e.g., a regression in thread nsid collection), which would silently drop a valid timer and hide the underlying bug. Consider tracking whether the thread was found separately: only skip when the notify thread isn’t present in item->threads, and keep vtid==0 as a hard error if the thread was found but conversion failed.

Copilot uses AI. Check for mistakes.
Comment thread criu/timer.c
Comment on lines 299 to +317
static int encode_notify_thread_id(pid_t rtid, struct pstree_item *item, PosixTimerEntry *pte)
{
pid_t vtid = 0;
int i;

if (rtid == 0)
return 0;

if (!(root_ns_mask & CLONE_NEWPID)) {
/* Non-pid-namespace case */
for (i = 0; i < item->nr_threads; i++) {
if (item->threads[i].real == rtid)
break;
}

if (i == item->nr_threads) {
pr_warn("Skipping timer because notify thread %d is dead\n", rtid);
return 1;
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encode_notify_thread_id now uses return value 1 to signal “skip this timer”, while -1 still means error. Since this is a new non-obvious contract, it’s easy for future callers to mishandle. Consider introducing a named constant/enum for the skip return code and documenting it in the function comment (or using a more conventional negative errno for “skip”).

Copilot uses AI. Check for mistakes.
Comment thread test/zdtm/static/Makefile Outdated
pthread_timers_h \
leader_posix_timer \
dead_thread_posix_timer \
dead_thread_posix_timer_nons \
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't required. The previous test will be executed in non-ns, ns and uns envs.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was a review on an older commit pushed as part of this PR? From the PR description:

I added dead_thread_posix_timer_h since I wanted to specifically trigger the restore failure case, which needs host mode. I followed the convention that pthread_timers.c and pthread_timers_h.c uses.

I'll remove it regardless, it was mostly used as a reproducer initially. Resolved via c245960

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 57.23%. Comparing base (f943d4f) to head (17b2913).
⚠️ Report is 52 commits behind head on criu-dev.

Additional details and impacted files
@@             Coverage Diff              @@
##           criu-dev    #2996      +/-   ##
============================================
+ Coverage     57.21%   57.23%   +0.01%     
============================================
  Files           154      154              
  Lines         40400    40413      +13     
  Branches       8857     8859       +2     
============================================
+ Hits          23115    23130      +15     
+ Misses        17021    17019       -2     
  Partials        264      264              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rst0git
Copy link
Copy Markdown
Member

rst0git commented Apr 17, 2026

Comment thread criu/timer.c
@pojntfx pojntfx force-pushed the pojntfx/arch-951-upstream-criu-changes branch 2 times, most recently from 3526179 to f4e3b56 Compare May 11, 2026 23:42
@pojntfx
Copy link
Copy Markdown
Author

pojntfx commented May 11, 2026

@rst0git @avagin Thank you for your reviews. I collapsed it into a single commit and rebased on the latest criu-dev. I already added commit messages to the original commits:

Screenshot showing the commit messages

When collapsing them into a single new commit I also added a body based on the PR description. Let me know if I should make any further changes :)

@3idey
Copy link
Copy Markdown
Contributor

3idey commented May 12, 2026

When collapsing them into a single new commit I also added a body based on the PR description. Let me know if I should make any further changes :)

@pojntfx Thank you!

Could you please separate this into two commits? The PR currently squashes the core fix and the ZDTM test into a single commit. According to the Contribution Guidelines, each logical change should be separated into a distinct patch.

pojntfx added 2 commits May 13, 2026 15:30
When a POSIX timer uses SIGEV_THREAD_ID and the target thread has
exited, dump fails in PID namespaces ("Unable to convert the notify
thread id") and restore fails without PID namespaces (EINVAL from
timer_create()). We noticed this when an application switched from
Java 8 to Java 21.

Detect dead notify threads during dump and skip the timer instead
of failing. The kernel already silently drops signals from these
timers, so skipping them during dump is safe.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Felicitas Pojtinger <felicitas@pojtinger.com>
Add a test that creates a SIGEV_THREAD_ID POSIX timer pointing at a
thread which then exits before checkpoint. Dump and restore must
succeed by skipping the timer rather than failing.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Felicitas Pojtinger <felicitas@pojtinger.com>
@pojntfx pojntfx force-pushed the pojntfx/arch-951-upstream-criu-changes branch from f4e3b56 to 5b30f6d Compare May 13, 2026 22:31
@pojntfx
Copy link
Copy Markdown
Author

pojntfx commented May 13, 2026

@3idey Done!

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.

6 participants