static NTSTATUS Poll( IN PCLOCK pClock, IN OUT PLONG64 pllNow, IN int EpollFd, OUT struct epoll_event* pEvents, IN int maxEvents, IN LONG64 llNextDeadline, OUT int* pReady ) { NTSTATUS status = STATUS_SUCCESS; int ready = 0; int timeout = 0; do { if (llNextDeadline >= 0) { /* Convert to timeout in milliseconds */ timeout = (llNextDeadline - *pllNow) / 1000000ll; if (timeout < 0) { timeout = 0; } } else { timeout = -1; } ready = epoll_wait(EpollFd, pEvents, maxEvents, timeout); if (ready < 0 && errno == EINTR) { /* Update current time so the next timeout calculation is correct */ status = ClockGetMonotonicTime(pClock, pllNow); GOTO_ERROR_ON_STATUS(status); } } while (ready < 0 && errno == EINTR); if (ready < 0) { ABORT_ON_FATAL_ERRNO(errno); status = LwErrnoToNtStatus(errno); GOTO_ERROR_ON_STATUS(status); } *pReady = ready; error: return status; }
static NTSTATUS EventLoop( PEPOLL_THREAD pThread ) { NTSTATUS status = STATUS_SUCCESS; RING timed; RING runnable; RING waiting; CLOCK clock = {0}; LONG64 llNow = 0; LONG64 llNextDeadline = 0; struct epoll_event events[MAX_EVENTS]; int ready = 0; BOOLEAN bShutdown = FALSE; BOOLEAN bSignalled = FALSE; RingInit(&runnable); RingInit(&timed); RingInit(&waiting); for (;;) { /* Get current time for this iteration */ status = ClockGetMonotonicTime(&clock, &llNow); GOTO_ERROR_ON_STATUS(status); /* Schedule any timed tasks that have reached their deadline */ ScheduleTimedTasks( &timed, llNow, &runnable); /* Schedule any waiting tasks that epoll indicated are ready and check if the thread received a signal */ ScheduleWaitingTasks( events, ready, llNow, &runnable, &bSignalled); if (bSignalled) { /* Schedule explicitly-signalled tasks and check if we have been told to shut down */ ScheduleSignalled( pThread, &runnable, &bShutdown); } /* Process runnable tasks */ status = ProcessRunnable( pThread, &runnable, &timed, &waiting, llNow); GOTO_ERROR_ON_STATUS(status); if (!RingIsEmpty(&runnable)) { /* If there are still runnable tasks, set the next deadline to now so we can check for other tasks becoming runnable but do not block in Poll() */ llNextDeadline = llNow; } else if (!RingIsEmpty(&timed)) { /* There are timed tasks, so set our next deadline to the deadline of the first task in the queue */ llNextDeadline = LW_STRUCT_FROM_FIELD(timed.pNext, EPOLL_TASK, QueueRing)->llDeadline; } else if (!RingIsEmpty(&waiting) || !bShutdown) { /* There are waiting tasks or we are not shutting down, so poll indefinitely */ llNextDeadline = -1; } else { /* We are shutting down and there are no remaining tasks, so leave */ break; } /* Wait (or check) for activity */ status = Poll( &clock, &llNow, pThread->EpollFd, events, MAX_EVENTS, llNextDeadline, &ready); GOTO_ERROR_ON_STATUS(status); } error: return status; }
static NTSTATUS Sleep( IN PCLOCK pClock, IN OUT PLONG64 pllNow, IN int nfds, IN OUT fd_set* pReadSet, IN OUT fd_set* pWriteSet, IN OUT fd_set* pExceptSet, IN LONG64 llNextDeadline, OUT int* pReady ) { NTSTATUS status = STATUS_SUCCESS; LONG64 llDiff = 0; struct timeval timeout = {0}; int ready = 0; if (llNextDeadline != 0) { /* We have a pending deadline, so set up a timeout for select() */ do { llDiff = llNextDeadline - *pllNow; if (llDiff >= 0) { /* Calculate whole seconds */ timeout.tv_sec = llDiff / 1000000000ll; /* Convert nanosecond remainder to microseconds */ timeout.tv_usec = (llDiff % 1000000000ll) / 1000; } else { timeout.tv_sec = 0; timeout.tv_usec = 0; } ready = select(nfds, pReadSet, pWriteSet, pExceptSet, &timeout); if (ready < 0 && errno == EINTR) { /* Update current time so the next timeout calculation is correct */ GOTO_ERROR_ON_STATUS(status = ClockGetMonotonicTime(pClock, pllNow)); } } while (ready < 0 && errno == EINTR); } else { /* No deadline is pending, so select() indefinitely */ do { ready = select(nfds, pReadSet, pWriteSet, pExceptSet, NULL); } while (ready < 0 && errno == EINTR); } if (ready < 0) { GOTO_ERROR_ON_STATUS(status = LwErrnoToNtStatus(errno)); } *pReady = ready; error: return status; }
static NTSTATUS EventLoop( PSELECT_THREAD pThread ) { NTSTATUS status = STATUS_SUCCESS; RING tasks; RING runnable; PRING pRing = NULL; PRING pNext = NULL; PSELECT_TASK pTask = NULL; CLOCK clock = {0}; LONG64 llNow; LONG64 llNextDeadline; fd_set readSet; fd_set writeSet; fd_set exceptSet; int ready = 0; int nfds = 0; char c = 0; int res = 0; BOOLEAN bShutdown = FALSE; PLW_TASK_GROUP pGroup = NULL; BOOLEAN bYielding = FALSE; RingInit(&tasks); RingInit(&runnable); FD_ZERO(&readSet); FD_ZERO(&writeSet); FD_ZERO(&exceptSet); LOCK_THREAD(pThread); while (!bShutdown || !RingIsEmpty(&tasks)) { /* Reset variables */ llNextDeadline = 0; nfds = 0; bYielding = FALSE; /* Get current time for this iteration */ GOTO_ERROR_ON_STATUS(status = ClockGetMonotonicTime(&clock, &llNow)); /* Figure out which tasks are runnable */ for (pRing = tasks.pNext; pRing != &tasks; pRing = pNext) { pNext = pRing->pNext; pTask = LW_STRUCT_FROM_FIELD(pRing, SELECT_TASK, EventRing); /* Update trigger set with results from select() */ UpdateTriggerSet( pTask, &readSet, &writeSet, &exceptSet, llNow); /* Schedule tasks to run if they have been triggered or were yielding */ if ((pTask->TriggerWait & LW_TASK_EVENT_YIELD) || ((pTask->TriggerWait | LW_TASK_EVENT_EXPLICIT) & pTask->TriggerSet)) { /* Put task on a separate list to run its trigger function */ RingRemove(&pTask->EventRing); RingInsertBefore(&runnable, &pTask->EventRing); /* Update the trigger args with the trigger set */ pTask->TriggerArgs |= pTask->TriggerSet; /* Turn off bits (except cancel) now that we have copied them */ pTask->TriggerSet &= (LW_TASK_EVENT_CANCEL); } else { /* Update select parameters to wait for task to trigger */ UpdateTriggerWait( pTask, &nfds, &readSet, &writeSet, &exceptSet, &llNextDeadline); } } UNLOCK_THREAD(pThread); for (pRing = runnable.pNext; pRing != &runnable; pRing = pNext) { pNext = pRing->pNext; pTask = LW_STRUCT_FROM_FIELD(pRing, SELECT_TASK, EventRing); GOTO_ERROR_ON_STATUS(status = TaskProcessTrigger(pTask, llNow)); if (pTask->TriggerWait != 0) { /* Task is still waiting to be runnable, update select parameters and put it back in the task list */ UpdateTriggerWait( pTask, &nfds, &readSet, &writeSet, &exceptSet, &llNextDeadline); if (pTask->TriggerWait & LW_TASK_EVENT_YIELD) { /* Task is yielding temporarily. Set the yield flag on its trigger arguments. Leave it on the runnable list */ pTask->TriggerArgs |= LW_TASK_EVENT_YIELD; } else { RingRemove(&pTask->EventRing); RingInsertBefore(&tasks, &pTask->EventRing); } } else { /* Task is complete, notify and remove from task group if it is in one */ RingRemove(&pTask->EventRing); /* Unregister task from global signal loop */ if (pTask->pUnixSignal) { RegisterTaskUnixSignal(pTask, 0, FALSE); } pGroup = pTask->pGroup; if (pGroup) { LOCK_GROUP(pGroup); pTask->pGroup = NULL; RingRemove(&pTask->GroupRing); pthread_cond_broadcast(&pGroup->Event); UNLOCK_GROUP(pGroup); } LOCK_THREAD(pThread); if (--pTask->ulRefCount) { pTask->TriggerSet = TASK_COMPLETE_MASK; pthread_cond_broadcast(&pThread->Event); UNLOCK_THREAD(pThread); } else { UNLOCK_THREAD(pThread); TaskDelete(pTask); } } } if (!RingIsEmpty(&runnable)) { /* We have runnable tasks that are yielding. Move them back to the event list and note the fact. */ bYielding = TRUE; RingMove(&runnable, &tasks); } if (!bShutdown) { /* Also wait for a poke on the thread's signal fd */ FD_SET(pThread->SignalFds[0], &readSet); if (pThread->SignalFds[0] >= nfds) { nfds = pThread->SignalFds[0] + 1; } } if (nfds) { /* If there are still runnable tasks due to LW_TASK_EVENT_YIELD, set the next deadline to now so we wake immediately. This gives other tasks the chance to become runnable before we proceed */ if (bYielding) { llNextDeadline = llNow; } /* Wait for a task to be runnable */ GOTO_ERROR_ON_STATUS(status = Sleep( &clock, &llNow, nfds, &readSet, &writeSet, &exceptSet, llNextDeadline, &ready)); } LOCK_THREAD(pThread); /* Check for a signal to the thread */ if (FD_ISSET(pThread->SignalFds[0], &readSet)) { FD_CLR(pThread->SignalFds[0], &readSet); pThread->bSignalled = FALSE; res = read(pThread->SignalFds[0], &c, sizeof(c)); assert(res == sizeof(c)); /* Move all tasks in queue into local task list */ RingMove(&pThread->Tasks, &tasks); if (pThread->bShutdown && !bShutdown) { bShutdown = pThread->bShutdown; /* Cancel all outstanding tasks */ for (pRing = tasks.pNext; pRing != &tasks; pRing = pRing->pNext) { pTask = LW_STRUCT_FROM_FIELD(pRing, SELECT_TASK, EventRing); pTask->TriggerSet |= LW_TASK_EVENT_CANCEL | LW_TASK_EVENT_EXPLICIT; } } } } error: UNLOCK_THREAD(pThread); return status; }
static NTSTATUS Poll( IN PCLOCK pClock, IN OUT PLONG64 pllNow, IN int KqueueFd, IN PKQUEUE_COMMANDS pCommands, OUT struct kevent* pEvents, IN int maxEvents, IN LONG64 llNextDeadline, OUT int* pReady ) { NTSTATUS status = STATUS_SUCCESS; int ready = 0; struct timespec timeout = {0, 0}; LONG64 llTimeDiff = 0; do { if (llNextDeadline >= 0) { /* Convert to timeout structure */ llTimeDiff = llNextDeadline - *pllNow; if (llTimeDiff < 0) { timeout.tv_sec = 0; timeout.tv_nsec = 0; } else { timeout.tv_sec = llTimeDiff / 1000000000ll; timeout.tv_nsec = llTimeDiff % 1000000000ll; } } ready = kevent( KqueueFd, pCommands->pCommands, pCommands->ulCommandCount, pEvents, maxEvents, llNextDeadline >= 0 ? &timeout : NULL); if (ready < 0 && errno == EINTR) { /* Update current time so the next timeout calculation is correct */ status = ClockGetMonotonicTime(pClock, pllNow); GOTO_ERROR_ON_STATUS(status); } } while (ready < 0 && errno == EINTR); if (ready < 0) { status = LwErrnoToNtStatus(errno); GOTO_ERROR_ON_STATUS(status); } ClearCommands(pCommands); *pReady = ready; error: return status; }