test: extend coverage of schedTest
This commit is contained in:
1 parent
6c6c8d8b1a
commit
f7c3e005a1
1 file changed
+159
+159
@@ -1095,3 +1095,162 @@ KTEST(threadKillAcceptsStoppedThreadLikeRealSigkillOnAStoppedProcess)
|
||||
KT_TRUE(t, threadReap(id));
|
||||
}
|
||||
|
||||
/*
|
||||
* threadReap/threadDiscard's own fd cleanup (sched.c's reclaimOpenFds) -- a
|
||||
* thread that dies while still holding an open fdPipeRead/fdPipeWrite fd
|
||||
* must give pipe.c's readers/writers count back exactly like an orderly
|
||||
* vfsClose would, or the pipe's OTHER end (a second, independent process,
|
||||
* e.g. fifomaster.c's children) would wait forever for an EOF/broken-pipe
|
||||
* that can now never come -- the same shape of leak FALA 13's bug#2 found
|
||||
* for console readers, just against pipe.c's own refcounts. These fabricate
|
||||
* the fd the same way the threadCreateUser tests above fabricate a user
|
||||
* slot: poke threadAt(id)->fds[] directly (bypassing vfsPipeAttach's own
|
||||
* allocFd/currentThread(), since the thread under test is never actually
|
||||
* running) with a real pipeCreateDetached handle, so the underlying pipe.c
|
||||
* refcounts driving the assertions are the genuine article, not a mock.
|
||||
*/
|
||||
|
||||
KTEST(threadDiscardClosesOpenReadPipeFdSoWriterSeesBrokenPipe)
|
||||
{
|
||||
int handle = pipeCreateDetached();
|
||||
KT_GE(t, handle, 0);
|
||||
|
||||
uint32 id = threadCreate("pipeReadHolder", noopThreadEntry);
|
||||
KT_NE(t, id, threadInvalidId);
|
||||
|
||||
/* Simulate this (never-run) thread having opened the read end via
|
||||
vfsPipeAttach: a real reader attached to the handle, referenced only
|
||||
from this thread's own fd table (index 3: 0-2 are always fdConsole,
|
||||
see vfsFdInit). */
|
||||
pipeAddReader(handle);
|
||||
threadAt(id)->fds[3].kind = fdPipeRead;
|
||||
threadAt(id)->fds[3].pipeHandle = handle;
|
||||
|
||||
/* A second, independent side attaches the write end -- tracked purely
|
||||
by handle, with no fd of its own, exactly like pipeShareTest.c's
|
||||
coverage of the same primitives. */
|
||||
pipeAddWriter(handle);
|
||||
KT_FALSE(t, pipeWriteBroken(handle));
|
||||
|
||||
/* Runnable, never scheduled: threadDiscard's path (e.g. rolling back a
|
||||
thread that never got to run its own vfsClose). */
|
||||
KT_TRUE(t, threadDiscard(id));
|
||||
|
||||
/* The only reader was this thread's fd -- once reclaimOpenFds has run,
|
||||
the writer must see the pipe as genuinely, permanently broken, not
|
||||
"try again" forever. */
|
||||
KT_TRUE(t, pipeWriteBroken(handle));
|
||||
KT_EQ(t, pipeWrite(handle, "x", 1), -1);
|
||||
|
||||
pipeCloseWrite(handle);
|
||||
}
|
||||
|
||||
KTEST(threadReapClosesOpenWritePipeFdSoReaderSeesEof)
|
||||
{
|
||||
int handle = pipeCreateDetached();
|
||||
KT_GE(t, handle, 0);
|
||||
|
||||
uint32 id = threadCreate("pipeWriteHolder", noopThreadEntry);
|
||||
KT_NE(t, id, threadInvalidId);
|
||||
|
||||
pipeAddWriter(handle);
|
||||
threadAt(id)->fds[3].kind = fdPipeWrite;
|
||||
threadAt(id)->fds[3].pipeHandle = handle;
|
||||
|
||||
pipeAddReader(handle);
|
||||
char buffer[4];
|
||||
/* Ring empty, a writer is still attached: "try again", not EOF yet. */
|
||||
KT_EQ(t, pipeRead(handle, buffer, sizeof(buffer)), -1);
|
||||
|
||||
/* No live scheduler to run threadExit through before schedStart() --
|
||||
poke the state directly, the same workaround
|
||||
threadReapOnDeadUserThreadReclaimsItsMappedPages uses above for the
|
||||
same reason. */
|
||||
threadAt(id)->state = threadDead;
|
||||
KT_TRUE(t, threadReap(id));
|
||||
|
||||
/* The only writer was this thread's fd -- once reclaimOpenFds has run,
|
||||
the reader must see real end-of-file (0), not "try again" (-1)
|
||||
forever. */
|
||||
KT_EQ(t, pipeRead(handle, buffer, sizeof(buffer)), 0);
|
||||
|
||||
pipeCloseRead(handle);
|
||||
}
|
||||
|
||||
/* threadSetAlarm (sched.c) always targets currentThread() -- there is no
|
||||
target-id argument, exactly like real alarm(2) -- so these tests exercise
|
||||
it exactly the way syscall.c's sysAlarm does, against whatever thread is
|
||||
"current" in this pre-schedStart() harness (the permanent idle thread,
|
||||
id 0), rather than a freshly created one the way most other tests here
|
||||
fabricate their own target. That is safe: deliverPendingSignals (isr.c)
|
||||
is a no-op for anything that is not a ring-3 return context (`(regs->cs &
|
||||
3) != 3`), so a pending sigAlrm bit left on the idle thread by an
|
||||
in-progress deadline is never actually consumed, and schedule()'s own
|
||||
alarm-expiry scan (see its file comment) clears alarmAt back to 0 the
|
||||
first tick it notices the deadline has passed either way -- but each test
|
||||
below still explicitly cancels (threadSetAlarm(0)) before returning, so
|
||||
no stray deadline is left for a LATER test or real boot to trip over. */
|
||||
KTEST(threadSetAlarmReturnsZeroWithNoPriorAlarmAndRecordsAFutureDeadline)
|
||||
{
|
||||
Thread* self = threadAt(currentThreadId());
|
||||
KT_EQU(t, self->alarmAt, 0ull);
|
||||
|
||||
uint64 now = pitTicks();
|
||||
KT_EQU(t, threadSetAlarm(5), 0ull);
|
||||
KT_TRUE(t, self->alarmAt > now);
|
||||
KT_EQU(t, self->alarmAt, now + 5ull * pitFrequency());
|
||||
|
||||
threadSetAlarm(0);
|
||||
KT_EQU(t, self->alarmAt, 0ull);
|
||||
}
|
||||
|
||||
KTEST(threadSetAlarmReplacingOrCancelingAPendingAlarmReturnsItsRemainingSecondsRoundedUp)
|
||||
{
|
||||
Thread* self = threadAt(currentThreadId());
|
||||
|
||||
/* Replace a 10s alarm with a 3s one: the return value describes the
|
||||
OLD (10s) deadline, not the new one, and the struct itself now holds
|
||||
the new one -- matching real alarm()'s "at most one pending alarm,
|
||||
setting a new one silently discards whatever was left of the old"
|
||||
contract. No real time passes between these two calls (interrupts
|
||||
are off, there is no scheduler to preempt this function), so the
|
||||
ceiling in threadSetAlarm's own comment reports the full, exact 10,
|
||||
not 9 or 11 -- a flaky "off by the current tick's remainder" result
|
||||
here would itself be a bug in that rounding, not a timing accident. */
|
||||
KT_EQU(t, threadSetAlarm(10), 0ull);
|
||||
KT_EQU(t, threadSetAlarm(3), 10ull);
|
||||
|
||||
/* Canceling (0) reports the 3s alarm's remaining time and clears the
|
||||
deadline back to "none pending" -- the other real alarm(0) case,
|
||||
exercised separately from the plain replace case above. */
|
||||
KT_EQU(t, threadSetAlarm(0), 3ull);
|
||||
KT_EQU(t, self->alarmAt, 0ull);
|
||||
|
||||
/* Canceling an ALREADY-canceled (none pending) alarm reports 0, the
|
||||
same "nothing was pending" value a fresh thread starts with. */
|
||||
KT_EQU(t, threadSetAlarm(0), 0ull);
|
||||
}
|
||||
|
||||
/*
|
||||
* Thread.pgid (sched.h) -- process groups, the scope real job control and
|
||||
* kill(2)/sigSend's pid<0 convention actually target (signal.c's
|
||||
* sigRaiseGroup, sched.c's threadKillGroup, syscall.c's sysKill/sysSigSend,
|
||||
* keyboard.c's real Ctrl-C/Ctrl-Z). ktestRunAll always runs as the
|
||||
* permanent idle thread (id 0) -- exactly the one context initialPgid
|
||||
* (sched.c) treats as "boot-time", per this file's own header comment --
|
||||
* so every plain threadCreate/threadCreateUser call here exercises the
|
||||
* "becomes its own new group leader" default; the OTHER default (a
|
||||
* runtime child inheriting a genuinely running, non-idle parent's pgid)
|
||||
* can only ever be exercised by a real ring-3 userSpawn, which is why
|
||||
* user/jobtest.c's own pgid check (spawning user/jobtarget.c at runtime)
|
||||
* is this feature's live proof instead.
|
||||
*/
|
||||
|
||||
KTEST(threadCreateDefaultsPgidToItsOwnIdWhenCurrentIsTheIdleThread)
|
||||
{
|
||||
uint32 id = threadCreate("pgidDefaultKernel", noopThreadEntry);
|
||||
KT_NE(t, id, threadInvalidId);
|
||||
KT_EQU(t, threadAt(id)->pgid, id);
|
||||
KT_TRUE(t, threadDiscard(id));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user