test: cover onChld
This commit is contained in:
1 parent
d76be2fcac
commit
277ac2ae34
1 file changed
+193
@@ -0,0 +1,193 @@
|
||||
#include "ulib.h"
|
||||
|
||||
/*
|
||||
* Boot-time driver closing the one gap left open by jobtest.c/jobtarget.c
|
||||
* (job control's OTHER live demo): a process stopping ITSELF (self-directed
|
||||
* SIGSTOP through sysSigSend, syscall.c) rather than being stopped by a
|
||||
* different one. Reasoned about but deliberately left without a live demo
|
||||
* for many falas (see include/signal.h's own file comment) because proving
|
||||
* it needs a second, independent process to race-freely learn "the target
|
||||
* has now actually stopped" before sending SIGCONT -- exactly the kind of
|
||||
* guess-free rendezvous sigtarget.c's own pipe-rendezvous comment already
|
||||
* had to solve once for a different race. The rendezvous this file uses is
|
||||
* sched.c's threadStop already raising a real SIGCHLD (siCodeChldStopped)
|
||||
* against the PARENT the instant a child (self- or otherwise) actually
|
||||
* transitions to threadStopped -- an event the kernel can only ever raise
|
||||
* if threadStop genuinely ran, so a real SigInfo handler catching it here
|
||||
* is a zero-guess, race-free signal that selfstoptarget.c's self-stop
|
||||
* syscall did not silently no-op.
|
||||
*
|
||||
* That alone would only prove the STATE flipped, not that execution
|
||||
* actually paused -- syscall.c's own comment describes exactly the bug
|
||||
* class this demo also has to rule out (a self-stopped thread that falls
|
||||
* through to its ordinary return-to-ring3 path instead of explicitly
|
||||
* rescheduling would keep running, unnoticed, until the next timer tick).
|
||||
* So this file adds a second check the moment the SIGCHLD confirms the
|
||||
* stop: a non-blocking (timeoutMs 0) userPoll on the proof pipe
|
||||
* selfstoptarget.c writes to only AFTER it resumes -- if that byte were
|
||||
* already there, the self-stop never actually paused execution and
|
||||
* selfstoptarget raced all the way to writing it before this process's own
|
||||
* bounded SIGCHLD-wait loop even noticed. Only once BOTH checks pass does
|
||||
* this send the real sigCont.
|
||||
*/
|
||||
|
||||
static volatile int stopFired = 0;
|
||||
static volatile int stopPid = -1;
|
||||
static volatile int stopStatus = -1;
|
||||
|
||||
static void onChld(int signum, SigInfo* info, void* context)
|
||||
{
|
||||
(void)signum;
|
||||
(void)context;
|
||||
if (info->siCode != siCodeChldStopped)
|
||||
return;
|
||||
stopPid = info->siPid;
|
||||
stopStatus = info->siStatus;
|
||||
stopFired = 1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
print("selfstoptest: start, pid=");
|
||||
printDec(userGetpid());
|
||||
print("\n");
|
||||
|
||||
/* Installed BEFORE selfstoptarget even exists -- threadStop can raise
|
||||
the real SIGCHLD the instant selfstoptarget's self-stop syscall
|
||||
runs, which could be very soon after userSpawn returns below. */
|
||||
if (userSigActionInfo(sigChld, onChld, 0, sigFlagSigInfo) == (long)sigSetFailed) {
|
||||
print("selfstoptest: FAIL install siginfo sigchld handler\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
long proofHandle = userPipeCreate();
|
||||
if (proofHandle < 0) {
|
||||
print("selfstoptest: FAIL create proof pipe\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Attach as reader before selfstoptarget exists, the same "attach
|
||||
first" reason sigtest.c/jobtest.c's own comments already document at
|
||||
length -- whichever side attaches second risks the pipe's slot
|
||||
already being freed by pipeReleaseIfUnused. */
|
||||
long proofFd = userPipeAttach(proofHandle, 0);
|
||||
if (proofFd < 0) {
|
||||
print("selfstoptest: FAIL attach proof pipe as reader\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char proofHandleStr[24];
|
||||
decToString(proofHandle, proofHandleStr);
|
||||
|
||||
char* targetArgv[] = { "/bin/selfstoptarget", proofHandleStr, 0 };
|
||||
long target = userSpawn("/bin/selfstoptarget", targetArgv);
|
||||
if (target < 0) {
|
||||
print("selfstoptest: FAIL spawn selfstoptarget\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Bounded poll for the real, kernel-raised SIGCHLD, same idiom
|
||||
siginfotest.c/sigchldtest.c already use for the same reason (no
|
||||
timing guess, just enough sleep-wake cycles for the return-to-ring3
|
||||
pending-signal scan, isr.c, to notice it whenever it actually
|
||||
lands). */
|
||||
for (int i = 0; i < 80 && !stopFired; i++)
|
||||
userSleep(20);
|
||||
|
||||
if (!stopFired) {
|
||||
print("selfstoptest: FAIL sigchld-on-self-stop handler never fired\n");
|
||||
return 1;
|
||||
}
|
||||
if (stopPid != (int)target) {
|
||||
print("selfstoptest: FAIL si_pid=");
|
||||
printDec(stopPid);
|
||||
print(" does not match real child pid=");
|
||||
printDec(target);
|
||||
print("\n");
|
||||
return 1;
|
||||
}
|
||||
if (stopStatus != sigStop) {
|
||||
print("selfstoptest: FAIL si_status did not report sigStop\n");
|
||||
return 1;
|
||||
}
|
||||
print("selfstoptest: self-stop-confirmed-via-sigchld-ok\n");
|
||||
|
||||
/* Non-blocking: must return immediately either way (timeoutMs 0), and
|
||||
must report the proof pipe as NOT ready -- selfstoptarget.c only
|
||||
ever writes to it AFTER resuming from its own self-stop syscall,
|
||||
which has not happened yet (sigCont is not sent until after this
|
||||
check). A self-stop that silently failed to actually pause execution
|
||||
would have let selfstoptarget race ahead and write this byte well
|
||||
before this process's own 80-iteration SIGCHLD-wait loop above even
|
||||
finished, making this the one check that would catch that exact
|
||||
regression. */
|
||||
PollFd proofPoll;
|
||||
proofPoll.fd = (int)proofFd;
|
||||
proofPoll.events = pollIn;
|
||||
proofPoll.revents = 12345;
|
||||
long ready = userPoll(&proofPoll, 1, 0);
|
||||
if (ready != 0 || proofPoll.revents != 0) {
|
||||
print("selfstoptest: FAIL proof pipe already had data before sigCont "
|
||||
"(self-stop did not actually pause execution)\n");
|
||||
return 1;
|
||||
}
|
||||
print("selfstoptest: still-stopped-before-cont-ok\n");
|
||||
|
||||
if (userSigSend(target, sigCont) != 0) {
|
||||
print("selfstoptest: FAIL send sigCont to selfstoptarget\n");
|
||||
return 1;
|
||||
}
|
||||
print("selfstoptest: sent sigCont to selfstoptarget\n");
|
||||
|
||||
/* onChld is installed WITHOUT sigFlagRestart (it needs to fire once,
|
||||
immediately, for the stop notification above -- a restarting handler
|
||||
would transparently retry, hiding the exact event this file is
|
||||
trying to observe). That means it is ALSO live for a second, later
|
||||
SIGCHLD: selfstoptarget's own exit, which can land at almost any
|
||||
real time after sigCont resumes it. If that exit's SIGCHLD arrives
|
||||
while this thread is genuinely parked mid the blocking userRead just
|
||||
below, sigConsumePending (signal.c) rewrites the interrupted
|
||||
context to return errInterrupted (-4) instead of letting the
|
||||
retry-on-block loop run and find the byte selfstoptarget already
|
||||
wrote -- the exact same self-signal-vs-own-blocking-syscall race
|
||||
stopchldtest.c's own comment documents and retries past (see that
|
||||
file for the full reasoning); a correctly written caller with a
|
||||
non-restarting handler for the same signal its own blocking syscall
|
||||
is waiting on must loop past EINTR the same way. */
|
||||
char proofByte = 0;
|
||||
long readStatus;
|
||||
for (;;) {
|
||||
readStatus = userRead((int)proofFd, &proofByte, 1);
|
||||
if (readStatus != errInterrupted)
|
||||
break;
|
||||
}
|
||||
if (readStatus != 1) {
|
||||
print("selfstoptest: FAIL read proof byte\n");
|
||||
return 1;
|
||||
}
|
||||
userClose((int)proofFd);
|
||||
if (proofByte != 'p') {
|
||||
print("selfstoptest: FAIL wrong proof byte\n");
|
||||
return 1;
|
||||
}
|
||||
print("selfstoptest: post-cont-proof-received-ok\n");
|
||||
|
||||
/* Same EINTR hazard as the read above, in case the exit SIGCHLD is
|
||||
still unconsumed by the time this runs instead. */
|
||||
long status;
|
||||
for (;;) {
|
||||
status = userWait(target);
|
||||
if (status != errInterrupted)
|
||||
break;
|
||||
}
|
||||
print("selfstoptest: selfstoptarget exit status=");
|
||||
printDec(status);
|
||||
print("\n");
|
||||
if (status != 66) {
|
||||
print("selfstoptest: FAIL expected selfstoptarget exit status 66\n");
|
||||
return 1;
|
||||
}
|
||||
print("selfstoptest: self-stop-continue-ok\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user