687 lines
26 KiB
C#
687 lines
26 KiB
C#
|
//------------------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Research Singularity
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
// Description: MinScheduler.cs
|
||
|
//
|
||
|
// Minimal round-robin style without priorities scheduler.
|
||
|
//
|
||
|
// MinScheduler favors thread that have recently become unblocked
|
||
|
// and tries to avoid reading the clock or reseting the timer as
|
||
|
// much as possible.
|
||
|
//
|
||
|
// The minimal scheduler maintains two queues of threads that can
|
||
|
// be scheduled. The unblockedThreads queue contains threads which
|
||
|
// have become unblocked during this scheduling quantum; mostly,
|
||
|
// these are threads that were unblocked by the running thread.
|
||
|
// The runnableThreads queue contains all other threads that are
|
||
|
// currently runnable. If the current thread blocks, MinScheduler
|
||
|
// will schedule threads from the unblockedThread queue, without
|
||
|
// reseting the timer. When the timer finally fires, MinScheduler
|
||
|
// moves all unblockedThreads to the end of the runnableThreads
|
||
|
// queue and schedules the next runnableThread.
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Diagnostics;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using System.Threading;
|
||
|
|
||
|
using Microsoft.Bartok.Options;
|
||
|
|
||
|
using Microsoft.Singularity;
|
||
|
using Microsoft.Singularity.Io;
|
||
|
using Microsoft.Singularity.Scheduling;
|
||
|
|
||
|
namespace Microsoft.Singularity.Scheduling
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Summary description for MinScheduler.
|
||
|
/// </summary>
|
||
|
[NoCCtor]
|
||
|
[CLSCompliant(false)]
|
||
|
public class MinScheduler : Scheduler
|
||
|
{
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Constructor for MinScheduler object
|
||
|
/// </summary>
|
||
|
///
|
||
|
public MinScheduler()
|
||
|
{
|
||
|
// If busy, don't run for more than 10ms on same task.
|
||
|
minSlice = TimeSpan.FromMilliseconds(10);
|
||
|
|
||
|
// Note that we have made the idleSlice small always.
|
||
|
// This is necessary in the MP case - otherwise a
|
||
|
// CPU seeing no work will go to sleep for a month and
|
||
|
// there is no mechanism to be woken when another CPU
|
||
|
// has excess work. (This support should be added
|
||
|
// eventually if we intend to use this scheduler.)
|
||
|
idleSlice = TimeSpan.FromMilliseconds(100);
|
||
|
|
||
|
affinity = 0;
|
||
|
|
||
|
// Create a scheduler lock
|
||
|
this.runnableLock = new SchedulerLock();
|
||
|
|
||
|
// Initialize timer's spinlock
|
||
|
this.timerLock = new SpinLock(SpinLock.Types.Timer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initialize min scheduler
|
||
|
/// </summary>
|
||
|
///
|
||
|
public override void Initialize()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Finalize scheduler object
|
||
|
/// </summary>
|
||
|
///
|
||
|
public override void Finalize()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Notify scheduler about new dispatcher
|
||
|
/// </summary>
|
||
|
///
|
||
|
public override void OnDispatcherInitialize(int dispatcherId)
|
||
|
{
|
||
|
// Min scheduler doesn't care about multiple dispatchers
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Attach thread to scheduler: thread specific initializtion
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread to attach </param>
|
||
|
/// <param name="constructorCalled">Have we called thread constructor </param>
|
||
|
///
|
||
|
public override void OnThreadStateInitialize(Thread thread, bool constructorCalled)
|
||
|
{
|
||
|
// Only initialize thread-local state! No locks held and interrupts on.
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Retrieve scheduler lock - used by dispatcher to protect scheduler state
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="affinity">Affinity of dispatcher making actual call</param>
|
||
|
///
|
||
|
[CLSCompliant(false)]
|
||
|
[NoHeapAllocation]
|
||
|
internal override SchedulerLock RetrieveSchedulerLock(int affinity)
|
||
|
{
|
||
|
// Return our default scheduler
|
||
|
return MyLock;
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Run scheduling policy. This method is called by dispatcher when both interrupts
|
||
|
/// disabled and disptach lock is acquired. As long as multiple dispatchers don't have access
|
||
|
/// to the this method no protection is required.
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="affinity">Set the returned running thread to this affinity.</param>
|
||
|
/// <param name="currentThread">the thread currently running</param>
|
||
|
/// <param name="schedulerAction">thread state to change to for the current thread</param>
|
||
|
/// <param name="currentTime">Current system time</param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override Thread RunPolicy(
|
||
|
int affinity,
|
||
|
Thread currentThread,
|
||
|
ThreadState schedulerAction,
|
||
|
SchedulerTime currentTime)
|
||
|
{
|
||
|
Thread threadToReturn = null;
|
||
|
ThreadState newState = ThreadState.Undefined;
|
||
|
ThreadEntry entry = null;
|
||
|
|
||
|
// Put current thread on a runnable queue only if it is currently in a running state
|
||
|
if (currentThread != null) {
|
||
|
|
||
|
// At this point current threads state has to be
|
||
|
// running - when running derived scheduler state we will deduce new state
|
||
|
// but until then it has to be running
|
||
|
VTable.Assert(currentThread.ThreadState == ThreadState.Running);
|
||
|
|
||
|
// If scheduler action is running make it runnable to have proper state transition
|
||
|
// Currently we disallow to go from running to running
|
||
|
if (schedulerAction == ThreadState.Running) {
|
||
|
schedulerAction = ThreadState.Runnable;
|
||
|
}
|
||
|
|
||
|
// Change current's thread new scheudler state
|
||
|
newState = currentThread.ChangeSchedulerState(schedulerAction);
|
||
|
|
||
|
// If new state is runnable add it to runnable queue
|
||
|
if (newState == ThreadState.Runnable) {
|
||
|
// REVIEW: Should we make sure the thread is enqueue already?
|
||
|
|
||
|
// Indicate that thread has been marked as runnable
|
||
|
this.runnableThreads.EnqueueTail(currentThread.schedulerEntry);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Derived state of entry on the runnable queue can be either suspended or runnable.
|
||
|
// Consequently first we remove an entry from the queue. If it is runnable,
|
||
|
// we will be able to convert it to running, If it is suspended,
|
||
|
// we will convert its real state to suspended. The first thread that marks the entry
|
||
|
// unsuspended will be responsible for putting it on back on a runnable queue.
|
||
|
|
||
|
// Check the unblocked queue first.
|
||
|
while ((entry = this.unblockedThreads.DequeueHead()) != null) {
|
||
|
|
||
|
// At this point thread direct state can be only runnable...
|
||
|
VTable.Assert(entry.Thread.IsRunnable);
|
||
|
|
||
|
// Attempt to make thread running
|
||
|
newState = entry.Thread.ChangeSchedulerState(ThreadState.Running);
|
||
|
|
||
|
// Get of the loop if we marked one of the entries as running
|
||
|
if (newState == ThreadState.Running) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// If the thread is suspended, then look for another.
|
||
|
VTable.Assert(newState == ThreadState.Suspended);
|
||
|
}
|
||
|
|
||
|
// If no recently unblocked threads, then check the runnable queue.
|
||
|
if (entry == null) {
|
||
|
while ((entry = this.runnableThreads.DequeueHead()) != null) {
|
||
|
|
||
|
// At this point thread direct state can be only runnable...
|
||
|
VTable.Assert(entry.Thread.IsRunnable);
|
||
|
|
||
|
// Attempt to make thread running
|
||
|
newState = entry.Thread.ChangeSchedulerState(ThreadState.Running);
|
||
|
|
||
|
// Get of the loop if we marked one of the entries as running
|
||
|
if (newState == ThreadState.Running) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// If the thread is suspended, then look for another.
|
||
|
VTable.Assert(newState == ThreadState.Suspended);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We got an entry from the runnable queue that we can actually run.
|
||
|
if (entry != null) {
|
||
|
|
||
|
// Thread must realy by in the running state.
|
||
|
VTable.Assert(newState == ThreadState.Running);
|
||
|
|
||
|
// Initialize thread we about to return.
|
||
|
threadToReturn = entry.Thread;
|
||
|
threadToReturn.Affinity = affinity;
|
||
|
}
|
||
|
|
||
|
return threadToReturn;
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Add thread to runnable queue. This method is called by dispatcher at the
|
||
|
/// point when both dispatcher lock and interrupts are disabled
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread to add to runnable queue </param>
|
||
|
///
|
||
|
[Inline]
|
||
|
[NoHeapAllocation]
|
||
|
public override void AddRunnableThread(Thread thread)
|
||
|
{
|
||
|
ThreadState newState;
|
||
|
|
||
|
// Change thread's state to runnable: Runpolicy will decide latter on
|
||
|
// if it can actually run the thread
|
||
|
newState = thread.ChangeSchedulerState(ThreadState.Runnable);
|
||
|
|
||
|
// Add thread to runnable queue only if new state is runnable
|
||
|
if (newState == ThreadState.Runnable) {
|
||
|
// While we adding a thread to runnable queue someone could have called freeze on
|
||
|
// it again - RunPolicy will notice it and remove thread from runnable queue appropriatly
|
||
|
|
||
|
this.unblockedThreads.EnqueueTail(thread.schedulerEntry);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Start thread - put a thread on dispatcher's runnable queue so it can be run
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread to start </param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override void OnThreadStart(Thread thread)
|
||
|
{
|
||
|
// Initialize thread's affinity
|
||
|
thread.Affinity = (int)ProcessorDispatcher.Affinity.All;
|
||
|
|
||
|
// Enqueue thread into dispatcher.
|
||
|
ProcessorDispatcher.AddRunnableThread(thread);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Block thread -process thread blocking including putting it on a timer queue if
|
||
|
/// timeout is specified.
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread that blocks </param>
|
||
|
/// <param name="stop">Amount of time for the thread to be blocked</param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override void OnThreadBlocked(Thread thread, SchedulerTime stop)
|
||
|
{
|
||
|
// Assert preconditions
|
||
|
VTable.Assert(thread == Thread.CurrentThread);
|
||
|
|
||
|
if (stop != SchedulerTime.MaxValue) {
|
||
|
// Enqueue timer so that if it expires we will notice it right a way
|
||
|
EnqueueTimer(thread, stop);
|
||
|
}
|
||
|
|
||
|
// Thread is blocked now - indicate this to dispatcher. Dispatcher will make
|
||
|
// make sure that thread's scheduler is aware of the blockage
|
||
|
ProcessorDispatcher.SwitchContext(ThreadState.Blocked);
|
||
|
|
||
|
// We just got unblocked and happilly running. We need to remove ourselves
|
||
|
// from the timer queue if we are unblocked by signal rather than by timeout
|
||
|
if (thread.UnblockedBy != WaitHandle.WaitTimeout) {
|
||
|
// One of our buddies unblocked us - remove the time out. Before we can
|
||
|
// actually assert that we were indeed unblocked by someone.
|
||
|
VTable.Assert(thread.UnblockedBy != WaitHandle.UninitWait);
|
||
|
|
||
|
// Remove a timer from the timer queue and happilly continue!
|
||
|
RemoveTimer(thread);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Unblock thread - resume thread by putting it on the dispatcher runnable queue.
|
||
|
/// This method can be invoked by threads running on other processors
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread to resume </param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override void OnThreadUnblocked(Thread thread)
|
||
|
{
|
||
|
// Only call this method if thread is Indeed blocked
|
||
|
VTable.Assert(thread.IsBlocked);
|
||
|
|
||
|
// Thread is ready to run: Add it to dispatcher
|
||
|
ProcessorDispatcher.AddRunnableThread(thread);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Yield thread - suspend thread based on time slice
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread that is yielding </param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override void OnThreadYield(Thread thread)
|
||
|
{
|
||
|
// Perform a context switch: If thread is runnable it will be put on a runnable queue
|
||
|
// by dispatcher.
|
||
|
ProcessorDispatcher.SwitchContext(ThreadState.Running);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Stop thread execution
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread that is stopping </param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override void OnThreadStop(Thread thread)
|
||
|
{
|
||
|
// Perform a context switch: If thread is stopped it will be cleaned up, otherwise
|
||
|
// it is suspended and will be cleaned up latter
|
||
|
ProcessorDispatcher.SwitchContext(ThreadState.Stopped);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Increment frozen counter
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread for which to increment freeze counter </param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override void OnThreadFreezeIncrement(Thread thread)
|
||
|
{
|
||
|
// Update threads: Freeze count
|
||
|
thread.IncrementFreezeCounter();
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Decrement frozen counter
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread for which to update freeze counter </param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override void OnThreadFreezeDecrement(Thread thread)
|
||
|
{
|
||
|
bool shouldPutThreadOnRunnableQueue = false;
|
||
|
|
||
|
// Assert preconditions
|
||
|
DebugStub.Assert(thread.FreezeCount > 0);
|
||
|
|
||
|
// Update threads: Freeze count
|
||
|
if (thread.DecrementFreezeCounter(ref shouldPutThreadOnRunnableQueue) == 0 &&
|
||
|
shouldPutThreadOnRunnableQueue) {
|
||
|
|
||
|
// Thread became runnable - add it to runnable queue
|
||
|
ProcessorDispatcher.AddRunnableThread(thread);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Suspend thread and wait until it is suspended.
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread to suspend </param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override void Suspend(Thread thread)
|
||
|
{
|
||
|
// Assert preconditions: We can't call suspend on ourselves
|
||
|
VTable.Assert(thread != Thread.CurrentThread);
|
||
|
|
||
|
// Increment freeze counter and then spin wait until thread will become suspended
|
||
|
OnThreadFreezeIncrement(thread);
|
||
|
|
||
|
// Wait until thread is capable of running
|
||
|
while (thread.IsRunning || thread.IsRunnable) {
|
||
|
Thread.SpinWait(100);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Resume thread from being suspended
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread to resume </param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
public override void Resume(Thread thread)
|
||
|
{
|
||
|
// Increment freeze counter and then spin wait until thread will becom suspended
|
||
|
OnThreadFreezeDecrement(thread);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Dispatch timer interrupt. This method is called by dispather when interrupts are
|
||
|
/// disabled.
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name = "affinity">Processor affinity</param>
|
||
|
/// <param name = "now">Current time</param>
|
||
|
///
|
||
|
[CLSCompliant(false)]
|
||
|
[NoHeapAllocation]
|
||
|
[HalLock]
|
||
|
public override TimeSpan OnTimerInterrupt(int affinity, SchedulerTime now)
|
||
|
{
|
||
|
ThreadEntry entry;
|
||
|
TimeSpan delta;
|
||
|
|
||
|
// Assert pre conditions
|
||
|
DebugStub.Assert(this.minSlice.Ticks != 0);
|
||
|
DebugStub.Assert(this.idleSlice.Ticks != 0);
|
||
|
|
||
|
// For now interrupts should be disabled when this method is called
|
||
|
VTable.Assert(Processor.InterruptsDisabled());
|
||
|
|
||
|
// Move all of the unblockedThreads to the runnable queue.
|
||
|
while ((entry = this.unblockedThreads.DequeueHead()) != null) {
|
||
|
this.runnableThreads.EnqueueTail(entry);
|
||
|
}
|
||
|
|
||
|
// Acquire timer lock
|
||
|
timerLock.Acquire();
|
||
|
|
||
|
// Unblock any threads whose timers have elapsed.
|
||
|
while ((entry = this.timerThreads.Head) != null &&
|
||
|
entry.Thread.BlockedUntil <= now) {
|
||
|
|
||
|
// Remove thread from the timer queue: There shouldn't be race with RemoveTimer
|
||
|
// call since we are protected by a timer queue's lock
|
||
|
this.timerThreads.Remove(entry);
|
||
|
|
||
|
// Indicate to the thread that its wait completed with timeout
|
||
|
if (entry.Thread.Unblock(WaitHandle.WaitTimeout) == WaitHandle.WaitTimeout) {
|
||
|
// Only if we have unblocked thread and it was already in blocked state
|
||
|
// put it on a dispatcher deferred wake up queue: otherwise scheduler will notice
|
||
|
// expiration itself and put the thread on a runnable queue. For more info
|
||
|
// see RunPolicy
|
||
|
if (entry.Thread.ShouldCallSchedulerUnBlock(WaitHandle.WaitTimeout)) {
|
||
|
|
||
|
// Change thread's state to runnable: Runpolicy will decide latter on
|
||
|
// if it can actually run the thread
|
||
|
entry.Thread.ChangeSchedulerState(ThreadState.Runnable);
|
||
|
|
||
|
// Place thread at the tail of the unblocked queue
|
||
|
this.unblockedThreads.EnqueueTail(entry);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Adjust the timeout so that we wake up sooner if
|
||
|
// there is a thread waiting on a timer.
|
||
|
if (!this.timerThreads.IsEmpty() &&
|
||
|
this.timerThreads.Head.Thread.blockedUntil < (now + this.minSlice)) {
|
||
|
this.nextTimer = this.timerThreads.Head.Thread.blockedUntil;
|
||
|
}
|
||
|
else {
|
||
|
this.nextTimer = now + this.idleSlice;
|
||
|
}
|
||
|
|
||
|
// Remember new alarm we are to set
|
||
|
delta = this.nextTimer - now;
|
||
|
|
||
|
// We are done: Don't forget to release timer lock:
|
||
|
timerLock.Release();
|
||
|
|
||
|
return delta;
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Remove a timer from timer queue: if thread is still on a timer queue. Need to
|
||
|
/// disable interrupts before acquire timer lock - dispatcher always calls into timer
|
||
|
/// with interrupts disable. If we don't disable interrupts we will get into possible deadlock
|
||
|
/// with dispatcher
|
||
|
/// </summary>
|
||
|
///
|
||
|
[CLSCompliant(false)]
|
||
|
[NoHeapAllocation]
|
||
|
[HalLock]
|
||
|
private void RemoveTimer(Thread thread)
|
||
|
{
|
||
|
// For now disable interrupts and acquire scheduler lock
|
||
|
bool shouldEnable = Processor.DisableInterrupts();
|
||
|
|
||
|
// Acquire timer lock
|
||
|
timerLock.Acquire();
|
||
|
|
||
|
if (thread.timerEntry.queue != null) {
|
||
|
this.timerThreads.Remove(thread.timerEntry);
|
||
|
}
|
||
|
|
||
|
// Release timer lock
|
||
|
timerLock.Release();
|
||
|
|
||
|
// Reenable interrupts
|
||
|
Processor.RestoreInterrupts(shouldEnable);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Park blocking thread in a timer queue with a given time out. Need to
|
||
|
/// disable interrupts before acquiring timer lock - dispatcher always calls into timer
|
||
|
/// with interrupts disable. If we don't disable interrupts we will get into deadlock
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <param name="thread">Thread to block</param>
|
||
|
/// <param name="stop">Period of time for a thread to wait before timeout</param>
|
||
|
///
|
||
|
[NoHeapAllocation]
|
||
|
[HalLock]
|
||
|
private void EnqueueTimer(Thread thread, SchedulerTime stop)
|
||
|
{
|
||
|
// For now disable interrupts and acquire scheduler lock...
|
||
|
bool shouldEnable = Processor.DisableInterrupts();
|
||
|
int unblockedBy;
|
||
|
SchedulerTime now = SchedulerTime.Now;
|
||
|
TimeSpan delta = stop - now;
|
||
|
|
||
|
//Acquire scheduler lock:
|
||
|
this.timerLock.Acquire();
|
||
|
|
||
|
// Find out if we need to update alarm.
|
||
|
bool shouldChangeAlarm = ((this.timerThreads.Head == null) ||
|
||
|
(this.timerThreads.Head.Thread.blockedUntil > stop));
|
||
|
|
||
|
// If new wait already expired just try to fail it right a way. Alarm upate
|
||
|
// indicates that only this new wait can be retired
|
||
|
if (shouldChangeAlarm && stop <= now) {
|
||
|
|
||
|
unblockedBy = thread.Unblock(WaitHandle.WaitTimeout);
|
||
|
|
||
|
// We no longer should be changing alarm
|
||
|
shouldChangeAlarm = false;
|
||
|
|
||
|
// if we unblocked by timer or by someone else we don't have to anything -
|
||
|
// OnThreadBlock will adjust thread's state accordingly. For more info see
|
||
|
// OnThreadBlock, Dispatcher.ContextSwitch and RunPolicy
|
||
|
}
|
||
|
else {
|
||
|
// Enqueue thread in the right place
|
||
|
ThreadEntry entry = this.timerThreads.Head;
|
||
|
|
||
|
while (entry != null && entry.Thread.blockedUntil <= stop) {
|
||
|
// Loop until we find the first thread with a later stop.
|
||
|
entry = entry.Next;
|
||
|
}
|
||
|
|
||
|
// Store thread's block until information
|
||
|
thread.blockedUntil = stop;
|
||
|
|
||
|
// Found the right place go ahead and put the thread into the queue
|
||
|
this.timerThreads.InsertBefore(entry, thread.timerEntry);
|
||
|
}
|
||
|
|
||
|
// Before we let others to party on a timer check if timer has to be reset:
|
||
|
// and if so rember it and reset once we release the lock
|
||
|
if (shouldChangeAlarm && this.nextTimer > stop) {
|
||
|
this.nextTimer = stop;
|
||
|
}
|
||
|
else {
|
||
|
shouldChangeAlarm = false;
|
||
|
}
|
||
|
|
||
|
// Release timer lock
|
||
|
this.timerLock.Release();
|
||
|
|
||
|
// If we are required to set a time on dispatcher, do it outside of
|
||
|
// spinlock since nobody is currently can be running on our processor
|
||
|
// since we have interrupts disabled.
|
||
|
if (shouldChangeAlarm) {
|
||
|
Processor.CurrentProcessor.SetNextTimerInterrupt(delta);
|
||
|
}
|
||
|
|
||
|
// Re-enable interrupts
|
||
|
Processor.RestoreInterrupts(shouldEnable);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Property to get to the scheduler runnable queue lock
|
||
|
/// </summary>
|
||
|
///
|
||
|
internal SchedulerLock MyLock
|
||
|
{
|
||
|
[Inline]
|
||
|
[NoHeapAllocation]
|
||
|
get
|
||
|
{
|
||
|
return this.runnableLock;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// <summary>
|
||
|
/// Get the affinity of this base scheduler
|
||
|
/// </summary>
|
||
|
///
|
||
|
internal int Affinity
|
||
|
{
|
||
|
[Inline]
|
||
|
[NoHeapAllocation]
|
||
|
get
|
||
|
{
|
||
|
return this.affinity;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary> A spinlock protecting state of the timer queue </summary>
|
||
|
private SchedulerLock runnableLock;
|
||
|
|
||
|
/// <summary> A spinlock protecting state of the timer queue </summary>
|
||
|
private SpinLock timerLock;
|
||
|
|
||
|
/// <summary> List of recently runnable, but unscheduled threads. </summary>
|
||
|
private ThreadQueue unblockedThreads = new ThreadQueue();
|
||
|
|
||
|
/// <summary> List of runnable, but unscheduled threads. </summary>
|
||
|
private ThreadQueue runnableThreads = new ThreadQueue();
|
||
|
|
||
|
/// <summary> List of blocked threads, sorted by wait time. </summary>
|
||
|
private ThreadQueue timerThreads = new ThreadQueue();
|
||
|
|
||
|
/// <summary> Scheduler affinity </summary>
|
||
|
private readonly int affinity;
|
||
|
|
||
|
/// <summary> List of frozen threads (unsorted) </summary>
|
||
|
private SchedulerTime nextTimer;
|
||
|
|
||
|
/// <summary> Idle thread </summary>
|
||
|
private Thread idleThread;
|
||
|
|
||
|
/// <summary> Run time slice </summary>
|
||
|
private TimeSpan minSlice;
|
||
|
|
||
|
/// <summary> Run time for idle slice </summary>
|
||
|
private TimeSpan idleSlice;
|
||
|
|
||
|
/// <summary> Check if need to process interprocess interrupt </summary>
|
||
|
private bool isIpiNeeded;
|
||
|
}
|
||
|
}
|