feat: extend sched with reclaimOpenFds and threadReap
This commit is contained in:
1 parent
48b66a86f7
commit
3376f44f5e
1 file changed
+203
-1
+203
-1
@@ -1010,6 +1010,119 @@ static void reclaimUserSlot(uint32 id, const Thread* thread)
|
||||
}
|
||||
|
||||
/* Every open fd a thread still holds when it dies represents a live
|
||||
reference into some other subsystem's own refcounted table -- fd 0's
|
||||
console reader queue (keyboard.h, allocated unconditionally by vfsFdInit
|
||||
regardless of whether the thread ever reads stdin, see vfs.c) and any
|
||||
fdPipeRead/fdPipeWrite fd (pipe.h's readers/writers counts on the
|
||||
underlying handle, from vfsPipe/vfsPipeAttach/a named fifo's vfsOpen).
|
||||
vfsClose already decrements both correctly, but only for a LIVE thread
|
||||
closing its OWN fd through currentThread() -- a thread that dies
|
||||
(threadKill, or simply returns/threadExit) while still holding either
|
||||
open skips vfsClose entirely, so without this the memset below would
|
||||
just drop the handle on the floor.
|
||||
For the console reader this was caught live (FALA 13): the reader table
|
||||
is a small fixed pool (maxConsoleReaders), and schedTest.c alone creates
|
||||
and tears down dozens of throwaway threads while proving the sched table
|
||||
itself reuses freed slots (threadCreateExhaustsThenThreadDiscardFrees...),
|
||||
which permanently exhausted the ENTIRELY SEPARATE console reader pool
|
||||
before boot ever reached kmain.c's real spawnUser calls -- every
|
||||
boot-time process's fd 0 silently went non-blocking (keyboardOpenReader
|
||||
returning consoleReaderInvalid), so consolereader1.c/consolereader2.c's
|
||||
userRead(0, ...) returned -1 immediately instead of blocking for the
|
||||
synthetic keystroke.
|
||||
The pipe/fifo case is the same shape of leak, just against pipe.c's own
|
||||
readers/writers counters instead of a fixed pool: the OTHER end of a
|
||||
shared pipe (a second, independent process, e.g. fifomaster.c's
|
||||
children) would never see the true EOF/broken-pipe pipeRead/pipeWrite
|
||||
report once every attached end on this side is gone, and would instead
|
||||
retry "try again" forever -- exactly the resource-exhaustion shape
|
||||
bug#2 above had, just not yet observed breaking anything live because
|
||||
nothing today loses a pipe/fifo fd without an orderly close() first (see
|
||||
schedTest.c's reclaimOpenFds tests, which prove the leak by fault
|
||||
injection rather than waiting for a live symptom). */
|
||||
static void reclaimOpenFds(const Thread* thread)
|
||||
{
|
||||
for (uint32 i = 0; i < maxOpenFiles; i++) {
|
||||
const FileDescriptor* fd = &thread->fds[i];
|
||||
if (fd->kind == fdConsole && fd->pipeHandle >= 0)
|
||||
keyboardCloseReader(fd->pipeHandle);
|
||||
else if (fd->kind == fdPipeRead)
|
||||
pipeCloseRead(fd->pipeHandle);
|
||||
else if (fd->kind == fdPipeWrite)
|
||||
pipeCloseWrite(fd->pipeHandle);
|
||||
else if (fd->kind == fdPidfd)
|
||||
pidfdClose(fd->pipeHandle);
|
||||
}
|
||||
}
|
||||
|
||||
bool threadReap(uint32 id)
|
||||
{
|
||||
if (id == 0 || id >= maxThreads)
|
||||
return false;
|
||||
if (threads[id].state != threadDead)
|
||||
return false;
|
||||
|
||||
reclaimUserSlot(id, &threads[id]);
|
||||
reclaimOpenFds(&threads[id]);
|
||||
if (threads[id].stackBase)
|
||||
kfree((void*)threads[id].stackBase);
|
||||
memset(&threads[id], 0, sizeof(Thread));
|
||||
/* memset leaves state at 0, which is threadUnused -- exactly the value
|
||||
allocSlot() scans for when looking for a slot to reuse. */
|
||||
return true;
|
||||
}
|
||||
|
||||
bool threadDiscard(uint32 id)
|
||||
{
|
||||
if (id == 0 || id >= maxThreads)
|
||||
return false;
|
||||
if (threads[id].state != threadRunnable || threads[id].everRan)
|
||||
return false;
|
||||
|
||||
if (threads[id].userSlotBase != 0)
|
||||
pagingUnmapUserRange(threads[id].userSlotBase, elfSlotSize);
|
||||
reclaimOpenFds(&threads[id]);
|
||||
if (threads[id].stackBase)
|
||||
kfree((void*)threads[id].stackBase);
|
||||
memset(&threads[id], 0, sizeof(Thread));
|
||||
return true;
|
||||
}
|
||||
|
||||
int64 threadJoin(uint32 id)
|
||||
{
|
||||
if (id == 0 || id >= maxThreads)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
ThreadState state = threads[id].state;
|
||||
if (state == threadUnused)
|
||||
return -1;
|
||||
if (state == threadDead)
|
||||
break;
|
||||
schedBlockCurrent(&threads[id]);
|
||||
halt();
|
||||
}
|
||||
|
||||
uint64 code = threads[id].exitCode;
|
||||
threadReap(id);
|
||||
return (int64)code;
|
||||
}
|
||||
|
||||
WaitScanResult threadJoinAnyChild(uint32 parentId, uint32* outId, int64* outCode)
|
||||
{
|
||||
bool hasChild = false;
|
||||
for (uint32 i = 1; i < threadTotal; i++) {
|
||||
if (threads[i].state == threadUnused || threads[i].parentId != parentId)
|
||||
continue;
|
||||
hasChild = true;
|
||||
if (threads[i].state == threadDead) {
|
||||
*outId = i;
|
||||
*outCode = (int64)threads[i].exitCode;
|
||||
threadReap(i);
|
||||
return waitScanFoundDead;
|
||||
}
|
||||
}
|
||||
return hasChild ? waitScanNoneDead : waitScanNoChildren;
|
||||
}
|
||||
|
||||
void threadSleep(uint64 milliseconds)
|
||||
@@ -1021,18 +1134,107 @@ void threadSleep(uint64 milliseconds)
|
||||
threads[currentIndex].state = threadSleeping;
|
||||
}
|
||||
|
||||
uint64 threadSetAlarm(uint64 seconds)
|
||||
{
|
||||
Thread* self = &threads[currentIndex];
|
||||
uint64 now = pitTicks();
|
||||
|
||||
uint64 previousRemaining = 0;
|
||||
if (self->alarmAt != 0 && self->alarmAt > now) {
|
||||
uint64 remainingTicks = self->alarmAt - now;
|
||||
uint32 freq = pitFrequency();
|
||||
/* Ceiling division: a deadline anywhere inside the current second
|
||||
still counts as "1 second remaining", never "0" -- matching real
|
||||
alarm()'s own contract that a not-yet-fired alarm never reports
|
||||
0 remaining (0 is reserved for "none pending" everywhere in this
|
||||
API, see the header comment). */
|
||||
previousRemaining = (remainingTicks + freq - 1) / freq;
|
||||
}
|
||||
|
||||
if (seconds == 0)
|
||||
self->alarmAt = 0;
|
||||
else
|
||||
self->alarmAt = now + seconds * (uint64)pitFrequency();
|
||||
|
||||
return previousRemaining;
|
||||
}
|
||||
|
||||
void schedBlockCurrent(const void* channel)
|
||||
{
|
||||
threads[currentIndex].waitChannel = channel;
|
||||
threads[currentIndex].state = threadBlocked;
|
||||
}
|
||||
|
||||
const void* schedPollChannel(void)
|
||||
{
|
||||
static const int pollChannelToken;
|
||||
return (const void*)&pollChannelToken;
|
||||
}
|
||||
|
||||
void schedWakeChannel(const void* channel)
|
||||
{
|
||||
for (uint32 i = 0; i < threadTotal; i++) {
|
||||
if (threads[i].state == threadBlocked && threads[i].waitChannel == channel) {
|
||||
threads[i].waitChannel = nullptr;
|
||||
threads[i].state = threadRunnable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void threadSetBrk(uint32 id, uint64 brk)
|
||||
{
|
||||
if (id >= maxThreads)
|
||||
return;
|
||||
threads[id].brk = brk;
|
||||
}
|
||||
|
||||
uint64 threadUserSlotBase(uint32 id)
|
||||
{
|
||||
if (id >= maxThreads)
|
||||
return 0;
|
||||
return threads[id].userSlotBase;
|
||||
}
|
||||
|
||||
void threadSetUserSlotBase(uint32 id, uint64 base)
|
||||
{
|
||||
if (id >= maxThreads)
|
||||
return;
|
||||
threads[id].userSlotBase = base;
|
||||
}
|
||||
|
||||
bool threadSetPriority(uint32 id, uint32 weight)
|
||||
{
|
||||
if (id == 0 || id >= maxThreads || weight == 0)
|
||||
return false;
|
||||
ThreadState state = threads[id].state;
|
||||
if (state == threadDead || state == threadUnused)
|
||||
return false;
|
||||
|
||||
threads[id].weight = weight;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 threadPriority(uint32 id)
|
||||
{
|
||||
if (id >= maxThreads || threads[id].state == threadUnused)
|
||||
return 0;
|
||||
return threads[id].weight;
|
||||
}
|
||||
|
||||
uint64 threadVruntime(uint32 id)
|
||||
{
|
||||
if (id >= threadTotal)
|
||||
return 0;
|
||||
return threads[id].vruntime;
|
||||
}
|
||||
|
||||
uint64 currentThreadSbrk(int64 increment)
|
||||
{
|
||||
Thread* thread = &threads[currentIndex];
|
||||
uint64 previous = thread->brk;
|
||||
|
||||
if (increment > 0)
|
||||
pagingMapUserRange(previous, (uint64)increment);
|
||||
pagingMapUserRangeFlags(previous, (uint64)increment, pageNoExecute);
|
||||
|
||||
thread->brk = previous + (uint64)increment;
|
||||
return previous;
|
||||
|
||||
Reference in new issue
Block a user