test: test more of schedTest
This commit is contained in:
1 parent
faaf04fc8c
commit
c31ac87199
1 file changed
+158
+158
@@ -784,3 +784,161 @@ KTEST(threadStopAcceptsSleepingBlockedAndRunningStates)
|
|||||||
uint32 id = threadCreate("stopFromEveryState", noopThreadEntry);
|
uint32 id = threadCreate("stopFromEveryState", noopThreadEntry);
|
||||||
KT_NE(t, id, threadInvalidId);
|
KT_NE(t, id, threadInvalidId);
|
||||||
|
|
||||||
|
/* Fabricated states, the same way threadReapClosesOpenWritePipeFdSoReaderSeesEof
|
||||||
|
below pokes threadDead directly: this thread is never actually
|
||||||
|
scheduled in this pre-schedStart() harness, so its `state` field
|
||||||
|
stands in for "genuinely Sleeping/Blocked/Running" -- exactly the
|
||||||
|
states a real SIGSTOP target could be caught in. */
|
||||||
|
threadAt(id)->state = threadSleeping;
|
||||||
|
KT_TRUE(t, threadStop(id));
|
||||||
|
KT_EQ(t, threadAt(id)->state, threadStopped);
|
||||||
|
KT_TRUE(t, threadContinue(id));
|
||||||
|
|
||||||
|
threadAt(id)->state = threadBlocked;
|
||||||
|
KT_TRUE(t, threadStop(id));
|
||||||
|
KT_EQ(t, threadAt(id)->state, threadStopped);
|
||||||
|
KT_TRUE(t, threadContinue(id));
|
||||||
|
|
||||||
|
/* Running -- the self-stop case (sysSigSend targeting the caller's own
|
||||||
|
pid, syscall.c): threadStop does not special-case "am I the one
|
||||||
|
calling this" the way threadKill implicitly does (Running is simply
|
||||||
|
absent from threadKill's accepted set); a thread can stop itself. */
|
||||||
|
threadAt(id)->state = threadRunning;
|
||||||
|
KT_TRUE(t, threadStop(id));
|
||||||
|
KT_EQ(t, threadAt(id)->state, threadStopped);
|
||||||
|
|
||||||
|
KT_TRUE(t, threadContinue(id));
|
||||||
|
KT_TRUE(t, threadDiscard(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
KTEST(threadStopOnDeadOrUnusedSlotFails)
|
||||||
|
{
|
||||||
|
uint32 id = threadCreate("stopDead", noopThreadEntry);
|
||||||
|
KT_NE(t, id, threadInvalidId);
|
||||||
|
|
||||||
|
threadAt(id)->state = threadDead;
|
||||||
|
KT_FALSE(t, threadStop(id));
|
||||||
|
KT_EQ(t, threadAt(id)->state, threadDead);
|
||||||
|
KT_TRUE(t, threadReap(id));
|
||||||
|
|
||||||
|
/* threadUnused again after reap: still rejected. */
|
||||||
|
KT_FALSE(t, threadStop(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
KTEST(threadContinueRejectsOutOfRangeAndNonStoppedThreads)
|
||||||
|
{
|
||||||
|
KT_FALSE(t, threadContinue(threadCapacity()));
|
||||||
|
KT_FALSE(t, threadContinue(threadCapacity() + 1000));
|
||||||
|
|
||||||
|
uint32 id = threadCreate("notStopped", noopThreadEntry);
|
||||||
|
KT_NE(t, id, threadInvalidId);
|
||||||
|
|
||||||
|
/* Runnable, never stopped: SIGCONT against a process that was never
|
||||||
|
stopped is a harmless no-op at this level (see signal.c's
|
||||||
|
sigRaise, which still delivers the signal itself regardless). */
|
||||||
|
KT_FALSE(t, threadContinue(id));
|
||||||
|
KT_EQ(t, threadAt(id)->state, threadRunnable);
|
||||||
|
|
||||||
|
KT_TRUE(t, threadDiscard(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
KTEST(threadStopExcludesTargetFromUnrelatedWaitChannelWake)
|
||||||
|
{
|
||||||
|
int channel;
|
||||||
|
|
||||||
|
uint32 id = threadCreate("stoppedWhileWaiting", noopThreadEntry);
|
||||||
|
KT_NE(t, id, threadInvalidId);
|
||||||
|
|
||||||
|
/* Fabricate exactly what schedBlockCurrent would have left behind had
|
||||||
|
this thread been the one calling it (see
|
||||||
|
threadDiscardClosesOpenReadPipeFdSoWriterSeesBrokenPipe below for
|
||||||
|
the same fabricate-then-assert convention). */
|
||||||
|
threadAt(id)->state = threadBlocked;
|
||||||
|
threadAt(id)->waitChannel = &channel;
|
||||||
|
|
||||||
|
KT_TRUE(t, threadStop(id));
|
||||||
|
KT_EQ(t, threadAt(id)->state, threadStopped);
|
||||||
|
|
||||||
|
/* The defining difference from threadBlocked: schedWakeChannel must
|
||||||
|
NOT match a Stopped thread even though its waitChannel field still
|
||||||
|
names this exact channel -- only threadContinue (an explicit
|
||||||
|
SIGCONT) may move it back to threadRunnable, never "the resource it
|
||||||
|
was originally waiting on became available". */
|
||||||
|
schedWakeChannel(&channel);
|
||||||
|
KT_EQ(t, threadAt(id)->state, threadStopped);
|
||||||
|
|
||||||
|
KT_TRUE(t, threadContinue(id));
|
||||||
|
KT_EQ(t, threadAt(id)->state, threadRunnable);
|
||||||
|
|
||||||
|
KT_TRUE(t, threadDiscard(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* threadStop/threadContinue's own new SIGCHLD-on-stop/SIGCHLD-on-continue
|
||||||
|
* (sched.c, mirroring threadExit/threadKill's parentId/sigChld block
|
||||||
|
* above): real job control notifies the parent asynchronously the instant
|
||||||
|
* a child stops or resumes, not only when it eventually dies. The live
|
||||||
|
* ring-3 proof that this reaches a genuinely blocked-mid-syscall target
|
||||||
|
* and a real installed handler is user/stopchldtest.c spawning
|
||||||
|
* user/stopchldtarget.c (needle "stopchldtest: job-control-sigchld-ok" in
|
||||||
|
* tools/ci-test.sh); what is deterministic here is the same plumbing shape
|
||||||
|
* threadKillRaisesSigChldAgainstParentId/
|
||||||
|
* threadKillEarlyWakesAParentGenuinelyBlockedWaitingForSigChld above
|
||||||
|
* already establish for the death case.
|
||||||
|
*/
|
||||||
|
|
||||||
|
KTEST(threadStopRaisesSigChldAgainstParentIdOnTheActualStopTransition)
|
||||||
|
{
|
||||||
|
uint32 parent = threadCreate("stopChldParent", noopThreadEntry);
|
||||||
|
KT_NE(t, parent, threadInvalidId);
|
||||||
|
uint32 child = threadCreate("stopChldVictim", noopThreadEntry);
|
||||||
|
KT_NE(t, child, threadInvalidId);
|
||||||
|
threadAt(child)->parentId = parent;
|
||||||
|
|
||||||
|
KT_EQU(t, threadAt(parent)->sigPending & (1ull << sigChld), 0ull);
|
||||||
|
KT_TRUE(t, threadStop(child));
|
||||||
|
KT_EQU(t, threadAt(parent)->sigPending & (1ull << sigChld), (1ull << sigChld));
|
||||||
|
|
||||||
|
KT_TRUE(t, threadContinue(child));
|
||||||
|
KT_TRUE(t, threadDiscard(child));
|
||||||
|
KT_TRUE(t, threadDiscard(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
KTEST(threadStopDoesNotReRaiseSigChldAgainstAnAlreadyStoppedTarget)
|
||||||
|
{
|
||||||
|
uint32 parent = threadCreate("stopChldParent2", noopThreadEntry);
|
||||||
|
KT_NE(t, parent, threadInvalidId);
|
||||||
|
uint32 child = threadCreate("stopChldVictim2", noopThreadEntry);
|
||||||
|
KT_NE(t, child, threadInvalidId);
|
||||||
|
threadAt(child)->parentId = parent;
|
||||||
|
|
||||||
|
KT_TRUE(t, threadStop(child));
|
||||||
|
KT_EQU(t, threadAt(parent)->sigPending & (1ull << sigChld), (1ull << sigChld));
|
||||||
|
|
||||||
|
/* Simulate the parent's own handler already having consumed that one
|
||||||
|
notification (sigConsumePending, signal.c, clears the bit on
|
||||||
|
delivery) -- exactly the "next natural ring-3 return" a real
|
||||||
|
sigchldtest.c-style handler would have already run through before a
|
||||||
|
second, redundant sigStop against the same still-Stopped target
|
||||||
|
could possibly arrive. */
|
||||||
|
threadAt(parent)->sigPending &= ~(1ull << sigChld);
|
||||||
|
|
||||||
|
KT_TRUE(t, threadStop(child));
|
||||||
|
KT_EQ(t, threadAt(child)->state, threadStopped);
|
||||||
|
KT_EQU(t, threadAt(parent)->sigPending & (1ull << sigChld), 0ull);
|
||||||
|
|
||||||
|
KT_TRUE(t, threadContinue(child));
|
||||||
|
KT_TRUE(t, threadDiscard(child));
|
||||||
|
KT_TRUE(t, threadDiscard(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
KTEST(threadStopEarlyWakesAParentGenuinelyBlockedWaitingForSigChldOnStop)
|
||||||
|
{
|
||||||
|
int channel;
|
||||||
|
|
||||||
|
uint32 parent = threadCreate("stopChldBlockedParent", noopThreadEntry);
|
||||||
|
KT_NE(t, parent, threadInvalidId);
|
||||||
|
uint32 child = threadCreate("stopChldVictim3", noopThreadEntry);
|
||||||
|
KT_NE(t, child, threadInvalidId);
|
||||||
|
threadAt(child)->parentId = parent;
|
||||||
|
|
||||||
Reference in new issue
Block a user