Skip to content

Commit 64ef078

Browse files
Copilotafxgroup
andcommitted
Fix pthread_kill implementation and add test suite
Co-authored-by: afxgroup <484672+afxgroup@users.noreply.github.com>
1 parent 53463e9 commit 64ef078

2 files changed

Lines changed: 389 additions & 1 deletion

File tree

library/pthread/pthread_kill.c

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,78 @@
3939

4040
int
4141
pthread_kill(pthread_t thread, int sig) {
42-
Signal((struct Task *)thread, 1 << sig);
42+
ThreadInfo *inf;
43+
struct _clib4 *target_clib4;
44+
45+
/* Validate signal number; sig == 0 is special (existence check) */
46+
if (sig < 0 || sig >= NSIG)
47+
return EINVAL;
48+
49+
/* Look up the target thread */
50+
inf = GetThreadInfo(thread);
51+
if (inf == NULL || inf->task == NULL)
52+
return ESRCH;
53+
54+
/* sig == 0: just check that the thread exists */
55+
if (sig == 0)
56+
return 0;
57+
58+
/*
59+
* AmigaOS4 does not have memory protection and cannot safely deliver
60+
* POSIX signals to another task's context. For terminal signals we
61+
* map to SIGBREAKF_CTRL_C, which is the conventional AmigaOS way to
62+
* ask a task to abort. For other signals we attempt a best-effort
63+
* delivery by marking the signal as pending in the target thread's
64+
* clib4 context and waking it up so that it can process the signal
65+
* at a safe point.
66+
*/
67+
68+
/* Terminal signals: map to the AmigaOS CTRL-C break signal */
69+
if (sig == SIGTERM || sig == SIGKILL || sig == SIGINT || sig == SIGQUIT) {
70+
Signal((struct Task *) inf->task, SIGBREAKF_CTRL_C);
71+
return 0;
72+
}
73+
74+
/* For all other signals, access the target thread's clib4 context */
75+
target_clib4 = (struct _clib4 *) inf->task->pr_UID;
76+
if (target_clib4 == NULL)
77+
return EINVAL;
78+
79+
/*
80+
* If the signal is blocked in the target thread, mark it as pending.
81+
* __signals_blocked is the persistent mask set by sigprocmask/sigblock,
82+
* while local_signals_blocked is a temporary per-signal block used
83+
* during handler execution to prevent recursive delivery.
84+
*/
85+
if (FLAG_IS_SET(target_clib4->__signals_blocked, sigmask(sig)) ||
86+
FLAG_IS_SET(target_clib4->local_signals_blocked, sigmask(sig))) {
87+
SET_FLAG(target_clib4->local_raised_signals_blocked, sigmask(sig));
88+
return 0;
89+
}
90+
91+
/* Signal is not blocked: check whether the target has a custom handler.
92+
* The table is indexed as sig - SIGHUP; the top-level check (sig < NSIG)
93+
* and the SIGHUP lower-bound guard together guarantee that the index
94+
* stays within the __signal_handler_table[NSIG] array bounds. */
95+
if (sig >= SIGHUP && sig < NSIG) {
96+
signal_handler_t handler = target_clib4->__signal_handler_table[sig - SIGHUP];
97+
98+
if (handler == SIG_IGN)
99+
return 0;
100+
101+
if (handler != SIG_DFL && handler != NULL) {
102+
/*
103+
* A custom handler is installed. We cannot call it in the
104+
* target thread's context on AmigaOS4, so we mark the signal
105+
* as pending and wake the thread via SIGBREAKF_CTRL_C so that
106+
* it will process the pending signal at its next cancellation
107+
* point or signal-check opportunity.
108+
*/
109+
SET_FLAG(target_clib4->local_raised_signals_blocked, sigmask(sig));
110+
Signal((struct Task *) inf->task, SIGBREAKF_CTRL_C);
111+
return 0;
112+
}
113+
}
114+
43115
return EINVAL;
44116
}
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
/*
2+
* pthread_kill test suite for AmigaOS4 / clib4
3+
*
4+
* Tests various aspects of pthread_kill behaviour, documenting both the
5+
* expected POSIX semantics and the AmigaOS4-specific constraints.
6+
*
7+
* Compile:
8+
* ppc-amigaos-gcc -mcrt=clib4 -std=c11 pthread_kill.c -o pthread_kill -lpthread
9+
*/
10+
11+
#include <pthread.h>
12+
#include <signal.h>
13+
#include <stdio.h>
14+
#include <stdlib.h>
15+
#include <string.h>
16+
#include <unistd.h>
17+
#include <errno.h>
18+
19+
/* --------------------------------------------------------------------------
20+
* Simple pass/fail accounting
21+
* -------------------------------------------------------------------------- */
22+
static int tests_passed = 0;
23+
static int tests_failed = 0;
24+
25+
static void check(const char *desc, int condition) {
26+
if (condition) {
27+
printf(" PASS: %s\n", desc);
28+
tests_passed++;
29+
} else {
30+
printf(" FAIL: %s\n", desc);
31+
tests_failed++;
32+
}
33+
}
34+
35+
/* --------------------------------------------------------------------------
36+
* Shared state for signal-reception tests
37+
* -------------------------------------------------------------------------- */
38+
static volatile int sigusr1_received = 0;
39+
static volatile int sigusr2_received = 0;
40+
41+
static void handler_sigusr1(int sig) {
42+
(void)sig;
43+
sigusr1_received = 1;
44+
}
45+
46+
static void handler_sigusr2(int sig) {
47+
(void)sig;
48+
sigusr2_received = 1;
49+
}
50+
51+
/* --------------------------------------------------------------------------
52+
* Thread helpers
53+
* -------------------------------------------------------------------------- */
54+
55+
/* A thread that simply sleeps for a long time. Used as a live target. */
56+
static void *sleeper_thread(void *arg) {
57+
(void)arg;
58+
sleep(60);
59+
return NULL;
60+
}
61+
62+
/* A thread that loops briefly then exits cleanly. */
63+
static void *quick_thread(void *arg) {
64+
(void)arg;
65+
usleep(50000); /* 50 ms */
66+
return NULL;
67+
}
68+
69+
/* A thread that installs SIGUSR1/SIGUSR2 handlers and then sleeps. */
70+
static void *signal_thread(void *arg) {
71+
(void)arg;
72+
signal(SIGUSR1, handler_sigusr1);
73+
signal(SIGUSR2, handler_sigusr2);
74+
/* Sleep long enough for the main thread to send a signal */
75+
sleep(60);
76+
return NULL;
77+
}
78+
79+
/* --------------------------------------------------------------------------
80+
* Test 1 – sig == 0: valid thread existence check
81+
* -------------------------------------------------------------------------- */
82+
static void test_sig0_valid_thread(void) {
83+
puts("\n[Test 1] sig=0 on a live thread (existence check)");
84+
pthread_t tid;
85+
pthread_create(&tid, NULL, sleeper_thread, NULL);
86+
87+
int rc = pthread_kill(tid, 0);
88+
check("pthread_kill(live_thread, 0) returns 0", rc == 0);
89+
90+
/* Clean up: terminate the sleeper */
91+
pthread_cancel(tid);
92+
pthread_join(tid, NULL);
93+
}
94+
95+
/* --------------------------------------------------------------------------
96+
* Test 2 – sig == 0: non-existent thread should return ESRCH
97+
* -------------------------------------------------------------------------- */
98+
static void test_sig0_invalid_thread(void) {
99+
puts("\n[Test 2] sig=0 on a non-existent thread (should return ESRCH)");
100+
101+
pthread_t tid;
102+
pthread_create(&tid, NULL, quick_thread, NULL);
103+
pthread_join(tid, NULL); /* wait until it is fully gone */
104+
105+
int rc = pthread_kill(tid, 0);
106+
check("pthread_kill(dead_thread, 0) returns ESRCH", rc == ESRCH);
107+
}
108+
109+
/* --------------------------------------------------------------------------
110+
* Test 3 – invalid signal number should return EINVAL
111+
* -------------------------------------------------------------------------- */
112+
static void test_invalid_signal(void) {
113+
puts("\n[Test 3] invalid signal number (should return EINVAL)");
114+
115+
pthread_t tid;
116+
pthread_create(&tid, NULL, sleeper_thread, NULL);
117+
118+
int rc = pthread_kill(tid, -1);
119+
check("pthread_kill(thread, -1) returns EINVAL", rc == EINVAL);
120+
121+
rc = pthread_kill(tid, NSIG);
122+
check("pthread_kill(thread, NSIG) returns EINVAL", rc == EINVAL);
123+
124+
rc = pthread_kill(tid, 999);
125+
check("pthread_kill(thread, 999) returns EINVAL", rc == EINVAL);
126+
127+
pthread_cancel(tid);
128+
pthread_join(tid, NULL);
129+
}
130+
131+
/* --------------------------------------------------------------------------
132+
* Test 4 – SIGTERM to a live thread
133+
*
134+
* On AmigaOS4 SIGTERM is mapped to SIGBREAKF_CTRL_C. The target thread
135+
* will not necessarily exit immediately because AmigaOS4 lacks hardware-
136+
* enforced signal delivery, but the call itself must succeed (return 0).
137+
* -------------------------------------------------------------------------- */
138+
static void test_sigterm(void) {
139+
puts("\n[Test 4] SIGTERM to a live thread (AmigaOS4: mapped to CTRL-C)");
140+
141+
pthread_t tid;
142+
pthread_create(&tid, NULL, sleeper_thread, NULL);
143+
144+
int rc = pthread_kill(tid, SIGTERM);
145+
check("pthread_kill(thread, SIGTERM) returns 0", rc == 0);
146+
147+
/*
148+
* The thread may or may not have acted on the signal yet; give it a
149+
* moment then clean up regardless.
150+
*/
151+
usleep(100000);
152+
pthread_cancel(tid);
153+
pthread_join(tid, NULL);
154+
}
155+
156+
/* --------------------------------------------------------------------------
157+
* Test 5 – SIGKILL to a live thread
158+
* -------------------------------------------------------------------------- */
159+
static void test_sigkill(void) {
160+
puts("\n[Test 5] SIGKILL to a live thread (AmigaOS4: mapped to CTRL-C)");
161+
162+
pthread_t tid;
163+
pthread_create(&tid, NULL, sleeper_thread, NULL);
164+
165+
int rc = pthread_kill(tid, SIGKILL);
166+
check("pthread_kill(thread, SIGKILL) returns 0", rc == 0);
167+
168+
usleep(100000);
169+
pthread_cancel(tid);
170+
pthread_join(tid, NULL);
171+
}
172+
173+
/* --------------------------------------------------------------------------
174+
* Test 6 – SIGINT to a live thread
175+
* -------------------------------------------------------------------------- */
176+
static void test_sigint(void) {
177+
puts("\n[Test 6] SIGINT to a live thread (AmigaOS4: mapped to CTRL-C)");
178+
179+
pthread_t tid;
180+
pthread_create(&tid, NULL, sleeper_thread, NULL);
181+
182+
int rc = pthread_kill(tid, SIGINT);
183+
check("pthread_kill(thread, SIGINT) returns 0", rc == 0);
184+
185+
usleep(100000);
186+
pthread_cancel(tid);
187+
pthread_join(tid, NULL);
188+
}
189+
190+
/* --------------------------------------------------------------------------
191+
* Test 7 – sending a signal to self (pthread_kill on the calling thread)
192+
*
193+
* SIGUSR1 is used with a custom handler so that the signal is handled
194+
* rather than invoking the default termination behaviour.
195+
* -------------------------------------------------------------------------- */
196+
static volatile int self_sigusr1 = 0;
197+
static void handler_self(int sig) {
198+
(void)sig;
199+
self_sigusr1 = 1;
200+
}
201+
202+
static void test_signal_to_self(void) {
203+
puts("\n[Test 7] pthread_kill to self with SIGUSR1");
204+
205+
signal(SIGUSR1, handler_self);
206+
self_sigusr1 = 0;
207+
208+
int rc = pthread_kill(pthread_self(), SIGUSR1);
209+
/*
210+
* On AmigaOS4, signal delivery to self goes through the raise() path.
211+
* The return value must be 0.
212+
*/
213+
check("pthread_kill(self, SIGUSR1) returns 0", rc == 0);
214+
215+
/* The handler may or may not fire synchronously on this platform */
216+
usleep(10000);
217+
printf(" INFO: self_sigusr1 = %d (1 means handler fired)\n", self_sigusr1);
218+
219+
signal(SIGUSR1, SIG_DFL);
220+
}
221+
222+
/* --------------------------------------------------------------------------
223+
* Test 8 – SIGUSR1/SIGUSR2 with a custom handler in the target thread
224+
*
225+
* On a POSIX system the handler would run inside the target thread. On
226+
* AmigaOS4 the best we can do is mark the signal as pending and wake the
227+
* thread. The test verifies that the call returns 0 (no error).
228+
* -------------------------------------------------------------------------- */
229+
static void test_sigusr_custom_handler(void) {
230+
puts("\n[Test 8] SIGUSR1 to a thread with a custom handler");
231+
232+
sigusr1_received = 0;
233+
234+
pthread_t tid;
235+
pthread_create(&tid, NULL, signal_thread, NULL);
236+
237+
/* Give the thread time to install its handler */
238+
usleep(100000);
239+
240+
int rc = pthread_kill(tid, SIGUSR1);
241+
check("pthread_kill(thread, SIGUSR1) with custom handler returns 0", rc == 0);
242+
243+
usleep(200000);
244+
printf(" INFO: sigusr1_received = %d (best-effort delivery on AmigaOS4)\n",
245+
sigusr1_received);
246+
247+
pthread_cancel(tid);
248+
pthread_join(tid, NULL);
249+
}
250+
251+
/* --------------------------------------------------------------------------
252+
* Test 9 – SIGUSR2 with SIG_IGN in the target thread
253+
*
254+
* When the handler is SIG_IGN, pthread_kill should silently succeed (0).
255+
* -------------------------------------------------------------------------- */
256+
static void *ignore_thread(void *arg) {
257+
(void)arg;
258+
signal(SIGUSR2, SIG_IGN);
259+
sleep(60);
260+
return NULL;
261+
}
262+
263+
static void test_sigusr_ignored(void) {
264+
puts("\n[Test 9] SIGUSR2 to a thread with SIG_IGN (should return 0)");
265+
266+
pthread_t tid;
267+
pthread_create(&tid, NULL, ignore_thread, NULL);
268+
269+
usleep(100000);
270+
271+
int rc = pthread_kill(tid, SIGUSR2);
272+
check("pthread_kill(thread, SIGUSR2) with SIG_IGN returns 0", rc == 0);
273+
274+
pthread_cancel(tid);
275+
pthread_join(tid, NULL);
276+
}
277+
278+
/* --------------------------------------------------------------------------
279+
* Test 10 – send signal to thread that has already been joined
280+
* (should return ESRCH)
281+
* -------------------------------------------------------------------------- */
282+
static void test_signal_after_join(void) {
283+
puts("\n[Test 10] pthread_kill after thread has been joined (should return ESRCH)");
284+
285+
pthread_t tid;
286+
pthread_create(&tid, NULL, quick_thread, NULL);
287+
pthread_join(tid, NULL);
288+
289+
int rc = pthread_kill(tid, SIGUSR1);
290+
check("pthread_kill on joined thread returns ESRCH", rc == ESRCH);
291+
}
292+
293+
/* --------------------------------------------------------------------------
294+
* main
295+
* -------------------------------------------------------------------------- */
296+
int main(void) {
297+
puts("=== pthread_kill test suite ===");
298+
puts("Note: AmigaOS4 has no memory protection; POSIX signal delivery");
299+
puts("to other threads is best-effort (see comments in each test).");
300+
301+
test_sig0_valid_thread();
302+
test_sig0_invalid_thread();
303+
test_invalid_signal();
304+
test_sigterm();
305+
test_sigkill();
306+
test_sigint();
307+
test_signal_to_self();
308+
test_sigusr_custom_handler();
309+
test_sigusr_ignored();
310+
test_signal_after_join();
311+
312+
printf("\n=== Results: %d passed, %d failed ===\n",
313+
tests_passed, tests_failed);
314+
315+
return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
316+
}

0 commit comments

Comments
 (0)