ProcSched.st
author claus
Mon, 20 Dec 1993 00:40:23 +0100
changeset 25 e34a6267c79b
parent 24 20cfbafcd0a3
child 27 d98f9dd437f7
permissions -rw-r--r--
*** empty log message ***

"
 COPYRIGHT (c) 1993 by Claus Gittinger
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

Object subclass:#ProcessorScheduler
         instanceVariableNames:'quiescentProcessLists
                                zombie
                                activeProcess currentPriority
                                readFds readSemaphores readChecks
                                writeFds writeSemaphores writeChecks
                                timeouts timeoutActions timeoutSemaphores
                                idleActions nTimeouts dispatching'
         classVariableNames:'KnownProcesses KnownProcessIds
                             PureEventDriven
                             UserSchedulingPriority TimingPriority'
         poolDictionaries:''
         category:'Kernel-Processes'
!

ProcessorScheduler comment:'

COPYRIGHT (c) 1993 by Claus Gittinger
             All Rights Reserved

$Header: /cvs/stx/stx/libbasic/Attic/ProcSched.st,v 1.7 1993-12-19 23:40:17 claus Exp $
'!

Smalltalk at:#Processor put:nil!

!ProcessorScheduler class methodsFor:'initialization'!

initialize
    KnownProcesses isNil ifTrue:[
        KnownProcesses := WeakArray new:5.
        KnownProcesses watcher:self.
        KnownProcessIds := OrderedCollection new.

        "want to get informed when returning from snapshot"
        ObjectMemory addDependent:self
    ].

    Processor isNil ifTrue:[
        "create the one and only processor"

        Processor := self new.
    ].
    PureEventDriven := self threadsAvailable not.
    PureEventDriven ifTrue:[
        'no process support - running event driven' printNewline
    ].
    UserSchedulingPriority := 8.
    TimingPriority := 16.
!

update:something
    something == #returnFromSnapshot ifTrue:[
        self reinstallProcesses
    ]
!

reinstallProcesses
    "recreate all processes after a snapShot load"

    KnownProcesses do:[:p |
        p notNil ifTrue:[
            "how, exactly should this be done ?"

            p id ~~ 0 ifTrue:[
                Transcript showCr:'process restart not implemented'
            ]
        ]
    ]
! !

!ProcessorScheduler class methodsFor:'instance creation'!

new
    "there is (currently) only one processor ..."

    Processor isNil ifTrue:[
        Processor := self basicNew initialize
    ].
    ^ Processor.
! !

!ProcessorScheduler class methodsFor:'instance release'!

informDispose
    "some Process has been collected - terminate the underlying thread"

    |id sz "{ Class: SmallInteger }"|

    sz := KnownProcessIds size.
    1 to:sz do:[:index |
        (KnownProcesses at:index) isNil ifTrue:[
            id := KnownProcessIds at:index.
            id notNil ifTrue:[
                Transcript showCr:('terminate thread (no longer refd) ', id printString).
                self threadDestroy:id.
                KnownProcessIds at:index put:nil.
            ]
        ]
    ]
! !

!ProcessorScheduler class methodsFor:'queries'!

isPureEventDriven
    "this is temporary - (maybe not :-).
     you can run ST/X either with or without processes.
     Without, there is conceptionally a single process handling all
     outside events and timeouts. This has some negative implications
     (Debugger is ugly), but allows a fully portable ST/X without any
     assembler support - i.e. quick portability.
     The PureEvent flag will automatically be set if the runtime system
     does not support threads - otherwise, it can be set manually
     (from rc-file).
    "

    ^ PureEventDriven
!

pureEventDriven
    "turn on pure-event driven mode - no processes, single dispatch loop"

    PureEventDriven := true
!

processDriven
    "turn on process driven mode"

    PureEventDriven := false
! !

!ProcessorScheduler class methodsFor:'primitive process primitives'!

threadsAvailable
    "return true, if the runtime system supports threads (i.e. processes);
     false otherwise."

%{  /* NOCONTEXT */
    extern OBJ __threadsAvailable();

    RETURN (__threadsAvailable());
%}
!

threadInterrupt:id with:aBlock
    "make the process evaluate aBlock when it awakes the next time"

%{  /* NOCONTEXT */

    if (_isSmallInteger(id) && _isBlock(aBlock)) {
        __threadInterrupt(_intVal(id), aBlock);
    }
%}
!

threadCreate:aBlock
    "physical creation of a process executing aBlock.
     (warning: low level entry, no administration done).
     This may return nil, if process could not be created."

%{  /* NOCONTEXT */
    int tid;
    extern int __threadCreate();

    tid = __threadCreate(aBlock, 0 /* stackSize no longer needed */);
    if (tid != 0) {
        RETURN ( _MKSMALLINT(tid));
    }
%}
.
    ^ nil
!

threadDestroy:id
    "physical destroy other process ...
     (warning: low level entry, no administration done)"

%{  /* NOCONTEXT */

    if (_isSmallInteger(id)) {
        __threadDestroy(_intVal(id));
    }
%}
! !

!ProcessorScheduler methodsFor:'primitive process primitives'!

threadSwitch:aProcess
    "continue execution in aProcess.
     (warning: low level entry, no administration is done here)"

    |id pri|

    aProcess isNil ifTrue:[^ self].
    aProcess == activeProcess ifTrue:[^ self].

    id := aProcess id.
    pri := aProcess priority.

    aProcess state:#active.

    "no interrupts now - activeProcess has already been changed
     (dont add any message sends here)"
    activeProcess := aProcess.
    currentPriority := pri.
%{
    __threadSwitch(__context, _intVal(id));
%}
.
    "time passes ...
     ... here again"

    zombie notNil ifTrue:[
        self class threadDestroy:zombie.
        zombie := nil
    ]
!

scheduleForInterrupt:aProcess
    "make aProcess evaluate its pushedInterrupt block(s)"

    |id pri|

    aProcess isNil ifTrue:[^ self].
    aProcess == activeProcess ifTrue:[^ self].

    id := aProcess id.
    self class threadInterrupt:id with:[aProcess interrupt].
    "and, make the process runnable"
    aProcess resume
! !

!ProcessorScheduler methodsFor:'constants'!

lowestPriority
    "return the lowest priority value"

    ^ 1   "do not change this - its not variable"
!

highestPriority
    "return the highest priority value processes can have"

    "must be below schedulingPriority - otherwise scheduler
     could be blocked ..."
    ^ 30  
!

schedulingPriority
    "return the priority at which the scheduler runs"

    "must be above highestPriority - otherwise scheduler
     could be blocked ..."
    ^ 31
!

userInterruptPriority
    "not currently used - for ST80 compatibility only"

    ^ 24
!

timingPriority
    "return the priority, at which all timing takes place (messageTally,
     delay etc.)"

    ^ TimingPriority
!

userSchedulingPriority
    "return the priority, at which all normal user (interactive) processing
     takes place"

    ^ UserSchedulingPriority
!

userBackgroundPriority
    "not currently used - for ST80 compatibility only"

    ^ 6
!

systemBackgroundPriority
    "not currently used - for ST80 compatibility only"

    ^ 4
! !

!ProcessorScheduler methodsFor:'private initializing'!

initialize
    "initialize the one-and-only ProcessorScheduler"

    |nPrios l|

    nPrios := self schedulingPriority.
    quiescentProcessLists := Array new:nPrios.

    readFds := Array with:nil.
    readChecks := Array with:nil.
    readSemaphores := Array with:nil.
    writeFds := Array with:nil.
    writeChecks := Array with:nil.
    writeSemaphores := Array with:nil.
    timeouts := Array with:nil.
    timeoutSemaphores := Array with:nil.
    timeoutActions := Array with:nil.
    nTimeouts := 0.
    dispatching := false.

    "handcraft the first (dispatcher-) process - this one will never
     block, but go into a select if there is nothing to do.
     Also it has a prio of max+1"

    activeProcess := Process new.
    activeProcess setId:0.
    activeProcess name:'scheduler'.
    activeProcess state:#run.
    currentPriority := self schedulingPriority.
    activeProcess setPriority:currentPriority.

    l := LinkedList new.
    l add:activeProcess.

    quiescentProcessLists at:currentPriority put:l.

    ObjectMemory ioInterruptHandler:self.
    ObjectMemory timerInterruptHandler:self.
!

reInitialize
    "all previous stuff is obsolete - each object should reinstall itself
     upon restart."

    |l|

    KnownProcesses := WeakArray new:5.
    KnownProcesses watcher:self class.
    KnownProcessIds := OrderedCollection new.

    "for now (cannot snapin processes)"

    quiescentProcessLists := Array new:self schedulingPriority.

    readFds := Array with:nil.
    readChecks := Array with:nil.
    readSemaphores := Array with:nil.
    writeFds := Array with:nil.
    writeChecks := Array with:nil.
    writeSemaphores := Array with:nil.
    dispatching := false.

    "handcraft the first (dispatcher-) process - this one will never
     block, but go into a select if there is nothing to do.
     Also it has a prio of max+1"

    activeProcess := Process new.
    activeProcess setId:0.
    activeProcess name:'scheduler'.
    activeProcess state:#run.
    currentPriority := self schedulingPriority.
    activeProcess setPriority:currentPriority.

    l := LinkedList new.
    l add:activeProcess.

    quiescentProcessLists at:currentPriority put:l.

    ObjectMemory ioInterruptHandler:self.
    ObjectMemory timerInterruptHandler:self.
! !

!ProcessorScheduler methodsFor:'private'!

remember:aProcess
    |newShadow oldSize oldId
     index "{ Class: SmallInteger }"
     sz    "{ Class: SmallInteger }" |

    index := 1.
    sz := KnownProcessIds size.
    [index <= sz] whileTrue:[
        (KnownProcesses at:index) isNil ifTrue:[
            oldId := KnownProcessIds at:index.
            oldId notNil ifTrue:[
                self class threadDestroy:oldId.
            ].
            KnownProcesses at:index put:aProcess.
            KnownProcessIds at:index put:aProcess id.
            ^ self
        ].
        index := index + 1
    ].

    KnownProcessIds grow:index.
    KnownProcessIds at:index put:aProcess id.

    oldSize := KnownProcesses size.
    (index > oldSize) ifTrue:[
        newShadow := WeakArray new:(oldSize * 2).
        newShadow watcher:self class.
        newShadow replaceFrom:1 with:KnownProcesses.
        KnownProcesses := newShadow
    ].
    KnownProcesses at:index put:aProcess 
!

unRemember:aProcess
    |index|

    index := KnownProcesses identityIndexOf:aProcess.
    index ~~ 0 ifTrue:[
        KnownProcessIds at:index put:nil.
        KnownProcesses at:index put:nil.
    ]
! !

!ProcessorScheduler methodsFor:'process creation'!

newProcessFor:aBlock
    "create a new process executing aBlock. 
     Return a process (or nil if fail). The new process is not scheduled. 
     To start it running, it needs a Process>>resume."

    |id p|

    id := self class threadCreate:aBlock.
    id isNil ifTrue:[
        self error:'cannot create new Process'.
        ^ nil
    ].
    p := Process new.
    p setId:id.
    p startBlock:aBlock.
    p state:#light.  "meaning: has no stack yet"
    p setPriority:currentPriority.
    self remember:p.
    ^ p
! !

!ProcessorScheduler methodsFor:'scheduling'!

reschedule
    "switch to the highest prio runnable process
     The scheduler itself is always runnable, so there is always a switch."

    |l p|

    (self schedulingPriority) to:1 by:-1 do:[:prio |
        l := quiescentProcessLists at:prio.
        l notNil ifTrue:[
            p := l first.
            p notNil ifTrue:[
                activeProcess state == #active ifTrue:[
                    activeProcess state:#run.
                ].
                ^ self threadSwitch:p
            ].
            quiescentProcessLists at:prio put:nil
        ]
    ].
    "no process to run - this cannot happen"

    self halt
!

yield
    "move the currently running process to the end of the currentList
     and reschedule to the first in the list, thus switching to the 
     next same-prio-process."

    |l|

    l := quiescentProcessLists at:currentPriority.
    l isNil ifTrue:[
        'oops - nil runnable list' printNewline.
        ^ self
    ].
    l removeFirst.
    l isEmpty ifTrue:[
        l addLast:activeProcess.
        ^ self
    ].
    l addLast:activeProcess.
    activeProcess state:#run.
    self threadSwitch:(l first).
!

suspend:aProcess
    "remove the argument, aProcess from the list of runnable processes.
     If the process is the current one, reschedule."

    |pri l s|

    aProcess isNil ifTrue:[self error:'nil suspend'. ^ self].
    aProcess id isNil ifTrue:[self error:'bad suspend: already dead'. ^ self].

    pri := aProcess priority.

    l := quiescentProcessLists at:pri.
    l isNil ifTrue:[self error:'bad suspend: not running'. ^ self].

    l remove:aProcess ifAbsent:[self error:'bad suspend'. ^ self].
    l isEmpty ifTrue:[
        quiescentProcessLists at:pri put:nil.
        l := nil
    ].

    s := aProcess state.
    ((s == #active) or:[s == #run]) ifTrue:[
        aProcess state:#suspended.
    ].
    (aProcess == activeProcess) ifTrue:[
        "can be done a bit faster sometimes"
        l notNil ifTrue:[
            self threadSwitch:(l first)
        ] ifFalse:[
            self reschedule
        ]
    ]
!

resume:aProcess
    "set aProcess runnable - if its prio is higher than the currently running prio,
     reschedule."

    |l pri|

    aProcess == activeProcess ifTrue:[^ self].
    aProcess isNil ifTrue:[^ self].

    "ignore, if process is already dead"
    aProcess id isNil ifTrue:[^ self].

    pri := aProcess priority.

    l := quiescentProcessLists at:pri.
    l isNil ifTrue:[
        l := LinkedList new.
        quiescentProcessLists at:pri put:l
    ] ifFalse:[
        "if already running, ignore"
        (l includes:aProcess) ifTrue:[
            ^ self
        ]
    ].
    l addLast:aProcess.

    (pri > currentPriority) ifTrue:[
        activeProcess state:#run.
        self threadSwitch:aProcess
    ] ifFalse:[
        aProcess state:#suspended
    ]
!

processTermination
    "current process finished its startup block without termination,
     lay him to rest now"

    self terminate:activeProcess.
    self reschedule
!

terminate:aProcess
    "terminate aProcess. If its not the current process, its simply
     removed from its list and destroyed. Otherwise, a switch is forced
     and the process is destroyed by the next running process."

    |pri id l state|

    aProcess isNil ifTrue:[^ self].
    id := aProcess id.
    id isNil ifTrue:[^ self].   "already dead"

    aProcess setId:nil.
    aProcess startBlock:nil.

    "remove the process from the runnable list"
    pri := aProcess priority.
    l := quiescentProcessLists at:pri.
    (l notNil and:[l includes:aProcess]) ifTrue:[
        l remove:aProcess.
        l isEmpty ifTrue:[quiescentProcessLists at:pri put:nil].
    ].

    aProcess exitAction notNil ifTrue:[
        aProcess exitAction value.
        aProcess exitAction:nil
    ].

    aProcess state:#dead.
    aProcess == activeProcess ifTrue:[
        "hard case - its the currently running process
         we must have the next active process destroy this one
        "
        zombie := id.
        self unRemember:aProcess.
        self reschedule.
        ^ self
    ].
    self class threadDestroy:id.
    self unRemember:aProcess.
    ^ self
!

changePriority:newPrio for:aProcess
    "change the priority of aProcess"

    |oldList newList oldPrio s|

    oldPrio := aProcess priority.
    oldPrio == newPrio ifTrue:[^ self].

    aProcess setPriority:newPrio.

    oldList := quiescentProcessLists at:oldPrio.
    (oldList includes:aProcess) ifTrue:[
        oldList remove:aProcess.
        oldList isEmpty ifTrue:[quiescentProcessLists at:oldPrio put:nil].

        newList := quiescentProcessLists at:newPrio.
        newList isNil ifTrue:[
            newList := LinkedList new.
            quiescentProcessLists at:newPrio put:newList
        ].
        newList addLast:aProcess.
        
        "if its the current process lowering its prio 
         or another one raising, we have to reschedule"

        aProcess == activeProcess ifTrue:[
            currentPriority := newPrio.
            newPrio < oldPrio ifTrue:[
                self reschedule.
            ]
        ] ifFalse:[
            newPrio > currentPriority ifTrue:[
                activeProcess state:#run.
                self threadSwitch:aProcess.
            ]
        ]
    ]
! !

!ProcessorScheduler methodsFor:'accessing'!

currentPriority
    "return the priority of the currently running process"

    ^ currentPriority

    "Processor currentPriority"
!

activeProcess
    "return the currently running process"

    ^ activeProcess

    "Processor activeProcess"
! !

!ProcessorScheduler methodsFor:'queries'!

highestPriorityRunnableProcess
    "return the highest prio runnable process"

    |l p maxPri "{ Class: SmallInteger }" |

    maxPri := self highestPriority.
    maxPri to:1 by:-1 do:[:prio |
        l := quiescentProcessLists at:prio.
        l notNil ifTrue:[
            p := l first.
            p notNil ifTrue:[^ p].
            "in the fly clear out empty lists"
            quiescentProcessLists at:prio put:nil
        ]
    ].
    ^ nil
! !

!ProcessorScheduler methodsFor:'dispatching'!

dispatchLoop
    "dispatch forever - the main process is running here all the time"

    dispatching == true ifTrue:[^ self].
    dispatching := true.
    [true] whileTrue:[
        self dispatch
    ]
!

dispatch
    |any millis pri p nActions "{ Class: SmallInteger }" |

    "handle all timeout actions"
    nTimeouts ~~ 0 ifTrue:[
        self evaluateTimeouts
    ].

    "first do a quick check using checkActions - this is needed for
     devices like X-connection, where some events might be in the event
     queue, so a select does not always help"

    any := false.
    nActions := readChecks size.
    1 to:nActions do:[:index |
        |checkBlock sema action|

        checkBlock := readChecks at:index.
        (checkBlock notNil and:[checkBlock value]) ifTrue:[
            sema := readSemaphores at:index.
            sema notNil ifTrue:[
                sema signalOnce.
            ].
            any := true.
        ]
    ].

    "now, someone might be runnable:"

    p := self highestPriorityRunnableProcess.
    p isNil ifTrue:[
        "no one runnable, hard wait for event or timeout"

        self waitForEventOrTimeout.
        ^ self
    ].

    pri := p priority.

    "want to give control to another process p.
     If the switched-to processes priority is lower than the
     userSchedulingPriority, we have to make certain, that the 
     next input or timer will bring us back for a reschedule.
     This is done by enabling ioInterrupts for all file descriptors.
     If ioInterrupts are not available, we schedule a timer interrupt 
     to interrupt us after 1/20s of a second - effectively polling 
     the filedescriptors. - which is very bad, since low prio processes
     will be hurt in performance - dont let benchmarks run with low prio ...
     Higher prio processes must suspend, same prio ones must yield to
     get back control"

"uncommenting this will make timeouts interrupt the current process
 (i.e. run at TimingPrio); if commented, they run at UserSchedulingPrio.
 this will all change, when timeouts are removed and all is process driven
"

"
    pri < TimingPriority ifTrue:[
        (nTimeouts ~~ 0) ifTrue:[
            millis := self timeToNextTimeout.
            millis == 0 ifTrue:[^ self].
        ]
    ].
"
    pri < UserSchedulingPriority ifTrue:[

        "comment out this if above is uncommented"
        (nTimeouts ~~ 0) ifTrue:[
            millis := self timeToNextTimeout.
            millis == 0 ifTrue:[^ self].
        ].

        OperatingSystem supportsIOInterrupts ifTrue:[
            readFds do:[:fd |
                fd notNil ifTrue:[
                    OperatingSystem enableIOInterruptsOn:fd
                ].
            ].
        ] ifFalse:[
            millis notNil ifTrue:[
                millis := millis min:50
            ] ifFalse:[
                millis := 50
            ]
        ]
    ].

    millis notNil ifTrue:[
        "schedule a clock interrupt"
        OperatingSystem enableTimer:millis rounded.
    ].

    "now let the process run - will come back here by reschedule
     from ioInterrupt or timerInterrupt ... (running at max+1)"

    activeProcess state:#run.
    self threadSwitch:p.

    "... when we arrive here, we are back on stage"

    millis notNil ifTrue:[
        OperatingSystem disableTimer.
        self checkForInputWithTimeout:0.
    ]
! !

!ProcessorScheduler methodsFor:'waiting'!

ioInterrupt
    "data arrived while waiting - reschedule to bring dispatcher into play"

    self reschedule
!

timerInterrupt
    "timer expired while waiting - reschedule to bring dispatcher into play"

    self reschedule
!

timeToNextTimeout
    "return the delta-T (in millis) to next timeout, or nil if
     there is none"

    |aTime now minDelta n "{ Class: SmallInteger }"|

    "find next timeout. since there are usually not many, just search.
     If there where many, the list should be kept sorted ... keeping deltas
     to next (as in Unix kernel)"

    n := timeouts size.
    1 to:n do:[:index |
        aTime := timeouts at:index.
        aTime notNil ifTrue:[
            minDelta isNil ifTrue:[
                now := OperatingSystem getMillisecondTime.
                (OperatingSystem millisecondTime:aTime isAfter:now) ifFalse:[^ 0].
                minDelta := OperatingSystem millisecondTimeDeltaBetween:aTime and:now.
            ] ifFalse:[
                (OperatingSystem millisecondTime:aTime isAfter:now) ifFalse:[^ 0].
                minDelta := minDelta min:(OperatingSystem millisecondTimeDeltaBetween:aTime and:now)
            ]
        ]
    ].

    ^ minDelta
!

waitForEventOrTimeout
    |millis limit doingGC|

    doingGC := true.
    [doingGC] whileTrue:[
        (nTimeouts ~~ 0) ifTrue:[
            millis := self timeToNextTimeout.
            (millis notNil and:[millis <= 0]) ifTrue:[
                ^ self    "oops - hurry up checking"
            ].
        ].

        "if its worth doing, collect a bit of garbage"
        limit := ObjectMemory incrementalGCLimit.
        doingGC := limit notNil and:[ObjectMemory oldSpaceAllocatedSinceLastGC > limit].
        doingGC ifTrue:[
            ObjectMemory gcStep.
        ].

        "then do idle actions"
        (idleActions size ~~ 0) ifTrue:[
            idleActions do:[:aBlock |
                aBlock value.
            ].
            ^ self   "go back checking"
        ].

        doingGC ifTrue:[
            (self checkForInputWithTimeout:0) ifTrue:[
                ^ self  "go back checking"
            ]
        ]
    ].

    "no, really nothing to do - simply wait"

    (OperatingSystem supportsSelect) ifFalse:[
        "SCO instant ShitStation has a bug here,
         waiting always 1 sec in the select - therefore we delay a bit and
         return - effectively polling in 50ms cycles
        "
        OperatingSystem millisecondDelay:50.
        ^ self
    ].

    millis isNil ifTrue:[
        millis := 9999.
    ] ifFalse:[
        millis := millis rounded
    ].
    self checkForInputWithTimeout:millis
!

checkForInputWithTimeout:millis
    |fd index sema action|

    fd := OperatingSystem selectOnAnyReadable:readFds writable:writeFds error:nil withTimeOut:millis.
    fd notNil ifTrue:[
        index := readFds indexOf:fd.
        index ~~ 0 ifTrue:[
            sema := readSemaphores at:index.
            sema notNil ifTrue:[
                sema signalOnce
            ] ifFalse:[
                action := readChecks at:index.
                action notNil ifTrue:[
                    action value.
                     ^ true
                ]
            ]
        ]
    ].
    ^ false
!

evaluateTimeouts
    "walk through timeouts and evaluate blocks or signal semas those that need to be .."

    |now aTime block blocksToEvaluate n "{ Class: SmallInteger }"|

    nTimeouts == 0 ifTrue:[ ^ self].

    "have to collect the blocks first, then evaluate them. This avoids
     problems due to newly inserted blocks."

    now := OperatingSystem getMillisecondTime.
    blocksToEvaluate := nil.
    n := timeouts size.
    1 to:n do:[:index |
        aTime := timeouts at:index.
        aTime notNil ifTrue:[
            (OperatingSystem millisecondTime:aTime isAfter:now) ifFalse:[
                "this one should be triggered"

                (timeoutSemaphores at:index) notNil ifTrue:[
                    (timeoutSemaphores at:index) signalOnce.
                    timeoutSemaphores at:index put:nil
                ] ifFalse:[
                    "to support pure-events"
                    block := timeoutActions at:index.
                    block notNil ifTrue:[
                        blocksToEvaluate isNil ifTrue:[
                            blocksToEvaluate := OrderedCollection with:block
                        ] ifFalse:[
                            blocksToEvaluate add:block
                        ].
                        timeoutActions at:index put:nil
                    ]
                ].
                timeouts at:index put:nil.
                nTimeouts := nTimeouts - 1
            ]
        ]
    ].

    blocksToEvaluate notNil ifTrue:[
        blocksToEvaluate do:[:aBlock |
            aBlock value
        ]
    ]
! !

!ProcessorScheduler methodsFor:'adding / removing'!

enableSemaphore:aSemaphore onInput:aFileDescriptor
    self enableSemaphore:aSemaphore onInput:aFileDescriptor check:nil
!

enableSemaphore:aSemaphore onInput:aFileDescriptor check:aBlock
    |idx|

    OperatingSystem blockInterrupts.
    (readFds includes:aFileDescriptor) ifFalse:[
        idx := readFds indexOf:nil.
        idx ~~ 0 ifTrue:[
            readFds at:idx put:aFileDescriptor.
            readSemaphores at:idx put:aSemaphore.
            readChecks at:idx put:aBlock
        ] ifFalse:[
            readFds := readFds copyWith:aFileDescriptor.
            readSemaphores := readSemaphores copyWith:aSemaphore.
            readChecks := readChecks copyWith:aBlock.
        ]
    ].
    OperatingSystem unblockInterrupts.
!

disableSemaphore:aSemaphore
    |idx|

    OperatingSystem blockInterrupts.
    idx := readSemaphores identityIndexOf:aSemaphore.
    idx ~~ 0 ifTrue:[
        readFds at:idx put:nil.
        readSemaphores at:idx put:nil.
        readChecks at:idx put:nil
    ].
    idx := timeoutSemaphores identityIndexOf:aSemaphore.
    idx ~~ 0 ifTrue:[
        timeouts at:idx put:nil.
        timeoutSemaphores at:idx put:nil.
        timeoutActions at:idx put:nil.
        nTimeouts := nTimeouts - 1.
    ].
    OperatingSystem unblockInterrupts.
!

enableSemaphore:aSemaphore afterSeconds:seconds
    self enableSemaphore:aSemaphore afterMilliseconds:(seconds * 1000)
!

enableSemaphore:aSemaphore afterMilliseconds:millis
    |now then index|

    now := OperatingSystem getMillisecondTime.
    then := OperatingSystem millisecondTimeAdd:now and:millis.

    OperatingSystem blockInterrupts.
    index := timeoutSemaphores identityIndexOf:aSemaphore.
    index ~~ 0 ifTrue:[
        timeouts at:index put:then
    ] ifFalse:[
        index := timeouts indexOf:nil.
        index ~~ 0 ifTrue:[
            timeoutSemaphores at:index put:aSemaphore.
            timeouts at:index put:then.
            timeoutActions at:index put:nil.
        ] ifFalse:[
            timeoutSemaphores := timeoutSemaphores copyWith:aSemaphore.
            timeouts := timeouts copyWith:then.
            timeoutActions := timeoutActions copyWith:nil.
        ].
        nTimeouts := nTimeouts + 1.
    ].
    OperatingSystem unblockInterrupts.
! !

!ProcessorScheduler methodsFor:'pure event support'!

enableIOAction:aBlock on:aFileDescriptor
    |idx|

    OperatingSystem blockInterrupts.
    (readFds includes:aFileDescriptor) ifFalse:[
        idx := readFds indexOf:nil.
        idx ~~ 0 ifTrue:[
            readFds at:idx put:aFileDescriptor.
            readChecks at:idx put:aBlock.
            readSemaphores at:idx put:nil
        ] ifFalse:[
            readFds := readFds copyWith:aFileDescriptor.
            readChecks := readChecks copyWith:aBlock.
            readSemaphores := readSemaphores copyWith:nil.
        ]
    ].
    OperatingSystem unblockInterrupts.
!

disableFd:aFileDescriptor
    |idx|

    OperatingSystem blockInterrupts.
    idx := readFds indexOf:aFileDescriptor.
    idx ~~ 0 ifTrue:[
        readFds at:idx put:nil.
        readChecks at:idx put:nil.
        readSemaphores at:idx put:nil
    ].
    OperatingSystem unblockInterrupts.
!

addIdleBlock:aBlock
    "add the argument, aBlock to the list of idle-actions; to be
     evaluated whenever no events are pending"

    OperatingSystem blockInterrupts.
    idleActions isNil ifTrue:[
        idleActions := OrderedCollection new
    ].
    idleActions add:aBlock.
    OperatingSystem unblockInterrupts.
!

removeIdleBlock:aBlock
    "remove the argument, aBlock from the list of idle-blocks"

    OperatingSystem blockInterrupts.
    idleActions notNil ifTrue:[
       idleActions remove:aBlock
    ].
    OperatingSystem unblockInterrupts.
!

addTimedBlock:aBlock after:delta
    "add the argument, aBlock to the list of time-sceduled-blocks; to be
     evaluated after delta seconds; the block will be removed after beeing
     evaluated"

    |now then index|

    now := OperatingSystem getMillisecondTime.
    then := OperatingSystem millisecondTimeAdd:now and:(delta * 1000).

    OperatingSystem blockInterrupts.
    index := timeoutActions identityIndexOf:aBlock.
    index ~~ 0 ifTrue:[
        timeouts at:index put:then
    ] ifFalse:[
        index := timeouts indexOf:nil.
        index ~~ 0 ifTrue:[
            timeoutActions at:index put:aBlock.
            timeouts at:index put:then.
            timeoutSemaphores at:index put:nil 
        ] ifFalse:[
            timeoutActions := timeoutActions copyWith:aBlock.
            timeouts := timeouts copyWith:then.
            timeoutSemaphores := timeoutSemaphores copyWith:nil.
        ].
        nTimeouts := nTimeouts + 1.
    ].
    OperatingSystem unblockInterrupts.
!

removeTimedBlock:aBlock
    "remove the argument, aBlock from the list of time-sceduled-blocks"

    |index|

    OperatingSystem blockInterrupts.
    index := timeoutActions identityIndexOf:aBlock.
    (index ~~ 0) ifTrue:[
        timeoutActions at:index put:nil. 
        timeouts at:index put:nil.
        timeoutSemaphores at:index put:nil.
        nTimeouts := nTimeouts - 1.
    ].
    OperatingSystem unblockInterrupts.
! !