Skip to content

!pthread: implement cancellation points - #524

Draft
adamgreloch wants to merge 1 commit into
masterfrom
adamgreloch/RTOS-1356
Draft

adamgreloch wants to merge 1 commit into
masterfrom
adamgreloch/RTOS-1356

Conversation

@adamgreloch

@adamgreloch adamgreloch commented Sep 7, 2026

Copy link
Copy Markdown
Member

Assisted-by: claude-opus-5
Fixes: phoenix-rtos/phoenix-rtos-project#1645

TASK: RTOS-1356

Description

Motivation and Context

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Chore (refactoring, style fixes, git/CI config, submodule management, no code logic changes)

How Has This Been Tested?

  • Already covered by automatic testing.
  • New test added: (add PR link here).
  • Tested by hand on: (list targets here).

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing linter checks and tests passed.
  • My changes generate no new compilation warnings for any of the targets.

Special treatment

  • This PR needs additional PRs to work (list the PRs, preferably in merge-order).
  • I will merge this PR by myself when appropriate.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements POSIX thread cancellation support in libphoenix, introducing cancellation points, state management, and wrapping various blocking system calls. While the addition of cancellation handling is a significant step, the current implementation has several critical issues. Most notably, executing cleanup handlers and thread-specific data destructors on the canceller thread instead of the victim thread violates POSIX and risks severe context corruption. Furthermore, failed signal posting can leave victim threads in a corrupted state, cancelled detached threads permanently leak resources, and several system call wrappers (such as open and sendmsg) risk leaking heap-allocated memory if cancelled before freeing. Addressing these concurrency, resource management, and POSIX compliance issues is essential.

Comment thread pthread/pthread.c Outdated
Comment on lines +668 to +673
_pthread_do_cleanup(ctx);
ctx->retval = (void *)PTHREAD_CANCELED;
id = ctx->id;
mutexUnlock(pthread_common.pthread_list_lock);

pthread_key_cleanup(ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

In pthread_cancel, the cleanup handlers (_pthread_do_cleanup(ctx)) and thread-specific data destructors (pthread_key_cleanup(ctx)) are executed directly by the canceller thread instead of the victim thread. This violates POSIX requirements and introduces severe concurrency and correctness issues. Specifically, any cleanup handler or destructor that accesses thread-local storage (TLS via __thread or pthread_getspecific), or calls pthread_self(), will operate on the canceller thread's context rather than the victim's. This can lead to silent data corruption, resource leaks, or crashes.

Comment thread pthread/pthread.c
Comment on lines +675 to 679
err = signalPost(getpid(), id, signal_cancel);
if (err != 0) {
/* Nothing was destroyed, so withdraw the destroy claim. */
(void)_pthread_cancel_clear(ctx, CANCEL_INPROGRESS_BIT);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

If signalPost(getpid(), id, signal_cancel) fails, the canceller thread withdraws the destroy claim by clearing CANCEL_INPROGRESS_BIT. The victim thread (which was waiting in _pthread_wait_for_cancel) will then resume execution. However, the canceller thread has already executed _pthread_do_cleanup(ctx) and pthread_key_cleanup(ctx). This leaves the victim thread running in a corrupted state where its cleanup handlers and thread-specific data have been prematurely destroyed/cleared, leading to undefined behavior.

Comment thread pthread/pthread.c

if (err == EOK) {
err = -condWait(cond->condh, mutex->mutexh, 0);
err = -CANCELLATION_POINT(int, condWait, (cond->condh, mutex->mutexh, 0));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

When a thread blocked in pthread_cond_wait is cancelled, POSIX requires that the thread re-acquire the associated mutex before calling any cancellation cleanup handlers. However, because the victim thread is terminated directly by signal_cancel and the cleanup handlers are executed by the canceller thread, the mutex is never re-acquired. If a cleanup handler expects the mutex to be locked (which is standard practice to allow safe unlocking/cleanup), calling pthread_mutex_unlock will fail or cause undefined behavior.

Comment thread pthread/pthread.c Outdated
Comment on lines +668 to +681
_pthread_do_cleanup(ctx);
ctx->retval = (void *)PTHREAD_CANCELED;
id = ctx->id;
mutexUnlock(pthread_common.pthread_list_lock);

pthread_key_cleanup(ctx);

err = signalPost(getpid(), id, signal_cancel);
if (err != 0) {
/* Nothing was destroyed, so withdraw the destroy claim. */
(void)_pthread_cancel_clear(ctx, CANCEL_INPROGRESS_BIT);
}

pthread_ctx_put(ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

When a detached thread is cancelled, it is terminated by the kernel via signal_cancel. Because it is terminated abruptly, it never reaches pthread_do_exit where _pthread_release(ctx, 1) would normally be called to unmap its stack and free its pthread_ctx. Since detached threads cannot be joined, their resources (stack and context) are permanently leaked. A mechanism (such as a deferred reclamation queue or a reaper) is needed to safely reclaim resources of cancelled detached threads.

Comment thread unistd/file.c
Comment on lines +366 to 370
do {
err = CANCELLATION_POINT(int, sys_open, (canonical, oflag, mode));
} while (err == -EINTR);

free(canonical);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Several wrapper functions (such as open, select, sendmsg, and recvmsg) allocate heap memory (e.g., canonical, pfd, buf) immediately before calling a cancellation point, and free it afterwards. If the cancellation point is triggered, the thread terminates without executing the subsequent free() calls, resulting in memory leaks. To prevent this, these functions should use pthread_cleanup_push and pthread_cleanup_pop to ensure that allocated resources are freed even if the thread is cancelled.

	pthread_cleanup_push(free, canonical);
	do {
		err = CANCELLATION_POINT(int, sys_open, (canonical, oflag, mode));
	} while (err == -EINTR);
	pthread_cleanup_pop(1);

Comment thread sys/socket.c
Comment on lines +156 to 157
len = CANCELLATION_POINT(ssize_t, sys_sendmsg, (socket, &_msg, flags));
free(buf);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If the cancellation point is triggered during sys_sendmsg, the temporary buffer buf allocated for scatter-gather copying will be leaked. Use pthread_cleanup_push and pthread_cleanup_pop to ensure the buffer is freed upon cancellation.

				pthread_cleanup_push(free, buf);
				len = CANCELLATION_POINT(ssize_t, sys_sendmsg, (socket, &_msg, flags));
				pthread_cleanup_pop(1);

@adamgreloch
adamgreloch force-pushed the adamgreloch/RTOS-1356 branch from 011c80a to 7516a25 Compare September 7, 2026 14:06
Assisted-by: claude-opus-5
Fixes: phoenix-rtos/phoenix-rtos-project#1645

TASK: RTOS-1356
@adamgreloch
adamgreloch force-pushed the adamgreloch/RTOS-1356 branch from 7516a25 to aea8c0a Compare September 7, 2026 14:20
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

Unit Test Results

13 383 tests  ±0   12 565 ✅ ±0   56m 43s ⏱️ -38s
   780 suites ±0      818 💤 ±0 
     1 files   ±0        0 ❌ ±0 

Results for commit aea8c0a. ± Comparison against base commit 067ea81.

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.

pthread_cancel() does not take effect after pthread_setcancelstate() re-enables cancellation

1 participant