test: cover schedTest
This commit is contained in:
1 parent
d4cdde3ea0
commit
bf31b363a6
1 file changed
+161
+161
@@ -149,3 +149,164 @@ static void noopThreadEntry(void)
|
|||||||
halt();
|
halt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KTEST(threadCreateExhaustsThenThreadDiscardFreesSlotsBackForReuse)
|
||||||
|
{
|
||||||
|
/* Sized off threadCapacity() itself, not a hardcoded number that would
|
||||||
|
silently stop meaning "the whole table" the next time maxThreads
|
||||||
|
(sched.c) grows -- exactly the staleness this test's own array size
|
||||||
|
once had (fixed at 64, which only ever happened to equal maxThreads
|
||||||
|
by coincidence of when it was written; the very next maxThreads bump
|
||||||
|
after this one otherwise made the while loop below stop at the array
|
||||||
|
bound before threadCreate ever actually failed, so the "must refuse
|
||||||
|
to grow past capacity" check two lines down silently stopped testing
|
||||||
|
anything and leaked one never-discarded thread for the rest of
|
||||||
|
boot). idle already occupies one slot, so successful creates always
|
||||||
|
stay strictly below `capacity`, and the loop below is guaranteed to
|
||||||
|
hit its threadInvalidId break at or before `count == capacity - 1` --
|
||||||
|
this array can never actually overflow. */
|
||||||
|
uint32 capacity = threadCapacity();
|
||||||
|
uint32 created[capacity];
|
||||||
|
uint32 count = 0;
|
||||||
|
|
||||||
|
/* Every thread created here stays Runnable/never-run for its entire
|
||||||
|
life in this test (schedule() never runs before schedStart()), so
|
||||||
|
threadDiscard -- not threadReap, which requires threadDead -- is the
|
||||||
|
correct way to tear them back down. */
|
||||||
|
while (count < capacity) {
|
||||||
|
uint32 id = threadCreate("ktestDummy", noopThreadEntry);
|
||||||
|
if (id == threadInvalidId)
|
||||||
|
break;
|
||||||
|
created[count++] = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The table must have refused to grow past its fixed capacity instead
|
||||||
|
of silently writing past threads[] -- this is the bounds-check the
|
||||||
|
original allocator did not have. */
|
||||||
|
KT_EQU(t, threadCreate("shouldFail", noopThreadEntry), threadInvalidId);
|
||||||
|
|
||||||
|
for (uint32 i = 0; i < count; i++)
|
||||||
|
KT_TRUE(t, threadDiscard(created[i]));
|
||||||
|
|
||||||
|
/* Every discarded slot must be usable again: the very next create
|
||||||
|
should succeed (it would fail immediately if discard had left the
|
||||||
|
table full). */
|
||||||
|
uint32 reused = threadCreate("afterDiscard", noopThreadEntry);
|
||||||
|
KT_NE(t, reused, threadInvalidId);
|
||||||
|
KT_TRUE(t, threadDiscard(reused));
|
||||||
|
}
|
||||||
|
|
||||||
|
KTEST(threadCreateSkipsAFreshlyFreedLowSlotInFavorOfMovingForward)
|
||||||
|
{
|
||||||
|
/* allocSlot (sched.c) used to always hand out the LOWEST currently-free
|
||||||
|
slot -- this test used to assert exactly that (KT_EQ(reused, b)
|
||||||
|
after discarding the middle one of three). That was a real,
|
||||||
|
reproducible bug: this fala's own gate caught killtest.c's and
|
||||||
|
nicetest.c's "a pid I just userWait()-reaped must reject a further
|
||||||
|
kill/nice" checks occasionally failing for real, because some
|
||||||
|
completely unrelated thread elsewhere in the system called
|
||||||
|
threadCreate in the tiny window between the reap and the recheck
|
||||||
|
and got handed that exact just-freed low index straight back. The
|
||||||
|
fix -- allocSlot's own new round-robin cursor -- means the very
|
||||||
|
next create after freeing a LOW slot, with plenty of headroom still
|
||||||
|
available, deliberately does NOT reuse it: it keeps moving forward
|
||||||
|
and grows the high-water mark instead, exactly like a real OS's own
|
||||||
|
pid allocator avoids immediately handing back a pid that just
|
||||||
|
exited. See allocSlot's own doc comment for the full story and
|
||||||
|
why this is a practical mitigation via headroom, not a mathematical
|
||||||
|
impossibility. */
|
||||||
|
uint32 a = threadCreate("rrA", noopThreadEntry);
|
||||||
|
uint32 b = threadCreate("rrB", noopThreadEntry);
|
||||||
|
uint32 c = threadCreate("rrC", noopThreadEntry);
|
||||||
|
KT_LT(t, a, b);
|
||||||
|
KT_LT(t, b, c);
|
||||||
|
|
||||||
|
KT_TRUE(t, threadDiscard(b));
|
||||||
|
|
||||||
|
uint32 afterFree = threadCreate("rrAfterFree", noopThreadEntry);
|
||||||
|
KT_NE(t, afterFree, b);
|
||||||
|
KT_GT(t, afterFree, c);
|
||||||
|
|
||||||
|
KT_TRUE(t, threadDiscard(a));
|
||||||
|
KT_TRUE(t, threadDiscard(afterFree));
|
||||||
|
KT_TRUE(t, threadDiscard(c));
|
||||||
|
|
||||||
|
/* b itself is still sitting there Unused, unreachable until the
|
||||||
|
cursor eventually wraps back around to it -- the very next test
|
||||||
|
below proves it is not leaked forever, just deferred. */
|
||||||
|
KT_EQU(t, threadPgid(b), threadInvalidId);
|
||||||
|
}
|
||||||
|
|
||||||
|
KTEST(threadCreateReusesTheOnlyFreedSlotOnceTheTableIsFull)
|
||||||
|
{
|
||||||
|
/* Same "must reuse freed slots, not just grow the high-water mark
|
||||||
|
forever" property the old lowest-index-specific test above used to
|
||||||
|
cover, restated so it holds regardless of WHERE allocSlot's cursor
|
||||||
|
happens to be: fill the ENTIRE table (sized off threadCapacity()
|
||||||
|
itself, same staleness-proofing reasoning as
|
||||||
|
threadCreateExhaustsThenThreadDiscardFreesSlotsBackForReuse above),
|
||||||
|
free exactly one slot picked from the middle, then prove the very
|
||||||
|
next create can ONLY land on that one slot -- there is nowhere else
|
||||||
|
left to land, so the round-robin scan is forced all the way around
|
||||||
|
to it regardless of its own starting position. */
|
||||||
|
uint32 capacity = threadCapacity();
|
||||||
|
uint32 created[capacity];
|
||||||
|
uint32 count = 0;
|
||||||
|
|
||||||
|
while (count < capacity) {
|
||||||
|
uint32 id = threadCreate("rrFill", noopThreadEntry);
|
||||||
|
if (id == threadInvalidId)
|
||||||
|
break;
|
||||||
|
created[count++] = id;
|
||||||
|
}
|
||||||
|
KT_EQU(t, threadCreate("rrShouldFail", noopThreadEntry), threadInvalidId);
|
||||||
|
|
||||||
|
uint32 freedId = created[count / 2];
|
||||||
|
KT_TRUE(t, threadDiscard(freedId));
|
||||||
|
|
||||||
|
uint32 reused = threadCreate("rrReusesOnlyFree", noopThreadEntry);
|
||||||
|
KT_EQ(t, reused, freedId);
|
||||||
|
|
||||||
|
for (uint32 i = 0; i < count; i++) {
|
||||||
|
if (created[i] != freedId)
|
||||||
|
KT_TRUE(t, threadDiscard(created[i]));
|
||||||
|
}
|
||||||
|
KT_TRUE(t, threadDiscard(reused));
|
||||||
|
}
|
||||||
|
|
||||||
|
KTEST(threadDiscardRejectsIdleOutOfRangeAndDoubleDiscard)
|
||||||
|
{
|
||||||
|
/* id 0 is the permanent idle thread: everRan is true for it from
|
||||||
|
schedInit onward, so discard must refuse it. */
|
||||||
|
KT_FALSE(t, threadDiscard(0));
|
||||||
|
|
||||||
|
/* Past the fixed capacity entirely. */
|
||||||
|
KT_FALSE(t, threadDiscard(threadCapacity()));
|
||||||
|
KT_FALSE(t, threadDiscard(threadCapacity() + 1000));
|
||||||
|
|
||||||
|
uint32 id = threadCreate("discardMe", noopThreadEntry);
|
||||||
|
KT_NE(t, id, threadInvalidId);
|
||||||
|
KT_TRUE(t, threadDiscard(id));
|
||||||
|
|
||||||
|
/* Already threadUnused: a second discard on the same id must fail
|
||||||
|
rather than double-free the (already-freed) stack. */
|
||||||
|
KT_FALSE(t, threadDiscard(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
KTEST(threadReapRejectsIdleOutOfRangeAndNeverCreatedSlots)
|
||||||
|
{
|
||||||
|
/* id 0 is the permanent idle thread: it is always Running/Runnable,
|
||||||
|
never Dead, so reap must refuse it. */
|
||||||
|
KT_FALSE(t, threadReap(0));
|
||||||
|
|
||||||
|
/* Past the fixed capacity entirely. */
|
||||||
|
KT_FALSE(t, threadReap(threadCapacity()));
|
||||||
|
KT_FALSE(t, threadReap(threadCapacity() + 1000));
|
||||||
|
|
||||||
|
/* A slot nobody has ever created a thread in yet is threadUnused, not
|
||||||
|
threadDead. A freshly created, never-run thread (Runnable) is not
|
||||||
|
threadDead either -- reap must refuse it too, distinctly from
|
||||||
|
threadDiscard above. */
|
||||||
|
uint32 neverUsed = threadCount();
|
||||||
|
if (neverUsed < threadCapacity())
|
||||||
|
KT_FALSE(t, threadReap(neverUsed));
|
||||||
|
|
||||||
Reference in new issue
Block a user