2009/4/28 Andreas Raab andreas.raab@gmx.de:
Andreas Raab wrote:
I'll settle for a definition of "correct" behavior in the presence of asynchronous interrupts since it is really not clear to me what you mean when you say "correct".
I had a thought about that this morning. One possible definition of "correct" in this context may mean that a process in a "frozen" state (to disambiguate from a current "suspended" state) simply does not advance its pc but behaves identical to an unfrozen process in all other respects.
The effect can be simulated (within reason) by something that simply drops the priority of a process to below the idle process' priority so that it just doesn't get any computational resources.
The point being that a freezing a process doesn't even touch the suspended list. You could implement this for example by using negative priorities to indicate that a process is frozen, along the lines of:
Process>>isFrozen "Answer if the process is frozen" ^priority < 0
Process>>freeze "This needs to be a primitive" self isFrozen ifTrue:[^self]. priority := priority negated.
Process>>unfreeze "This needs to be a primitive" self isFrozen ifFalse:[^self]. priority := priority negated.
Changing synchronousSignal: not to allow frozen processes to become runnable (i.e., test for negative priority and just ignore it if that's the case) would achieve most of what you need (modulo the primitives for freeze/unfreeze).
Obviously the effects will be "interesting" (to say the least) if you freeze a process on its way into a mutex since that process will indeed hold the mutex until such a point that it becomes unfrozen. But I guess that is kind of what you were asking for after all ;-)
Good idea, except that you losing the original priority value. And freezing/suspending is very much synonyms as to me :)
After your criticism (sorry i haven't commented it yet..), my thoughts vent somewhere else, trying to identify what is wrong with Squeak scheduling/concurrency and why its so brittle even at core levels (not mentioning an application levels which rely on it).
I came to an idea , you might be interested in. As many of us know, some CPUs having a special mode - interrupt mode. What if we introduce the interrupt mode for scheduler?
Processor interruptWith: aBlock "evaluate the block closure in interrupt mode. VM guarantees the atomicity of block evaluation (no external events could interrupt its evaluation, since processor are already in interrupt mode). A 'Processor interruptedProcess' holds a process which is being interrupted. Once block evaluation is done, VM continues with process which holding interruptedProcess ivar. In case of any external events(such as semaphore signal), which may occur during interrupt, they are queued in list. Interpreter continues to handle interrupts in interrupt mode until there are pending interrupts in its queue. If code sends #interruptWith: when already in interrupt mode , then block will be evaluated immediately, as if sending #value message to it "
Now i trying to imagine, how a basic stuff might look like(please correct me if its utterly wrong way ;), if we will be able to use interrupt mode.
First, task switching, and scheduling policy is in hands of developers, not VM:
Processor>>initializeSchedulingAction "when VM going to switch tasks , like in wait/suspend/running to the bottom of stack, it picks a block closure , located in Processor's schedulingAction instance variable, and runs it as an interrupt. " schedulingAction := [ self interruptedProcess: self pickNextScheduledProcessBasedOnWhateverCriteria. ].
and next is trivial:
Processor>>yield "yield the current process" self interruptWith: [ self schedulingAction value. ].
As a consequence of this, semaphores can be implemented as is (w/o the need of special primitives)
Semaphore>>signal Processor interruptWith: [ self isEmpty ifTrue: [excessSignals := excessSignals+1] ifFalse: [Processor resume: self removeFirstLink] ].
Semaphore>>wait Processor interruptWith: [ excessSignals>0 ifTrue: [excessSignals := excessSignals-1] ifFalse: [self addLastLink: Processor activeProcess suspend] ]
Delays:
Delay>>wait "action & delayedProcess is instance variables" [ Processor interruptWith: [ delayedProcess := Processor interruptedProcess. Processor unschedule: delayedProcess. action := [ Processor rescheduleProcess: delayedProcess ]. self class scheduleDelay: self. Processor continueWithNextScheduledProcess. ]. ] ifCurtailed: [ action := [] ].
---
Delay class>>scheduleDelay: delay "gory details about picking the nearest delay to be signaled... " ActiveDelay := ...
self primitiveScheduleAction: [ ActiveDelay action value. self sheduleNextDelay ] registerAsExternalObject atMilliseconds: pointInTime.
A 'primitiveScheduleAction...' will schedule an interrupt to evaluate a closure which were registered in external objects table. Note, that since access to state (ActiveDelay) is possible only during interrupt phase (we can enforce this quite easy), we don't need an extra synchronizations like AccessProtect critical: []. So, the obvious benefit - a code is greatly simplified.
Also, note the tricks with 'action' ivar of delay. An ifCurtailed: block sets action to empty block, so nothing bad happens when given delay will be activated by timerInterrupt.
In similar way, we could add a 'signalAction' ivar to Semaphore, and replace it in case, if we don't want it to be performed (such in #terminate /#critical: case).
And, what is now possible - a shift in mindset - use direct actions (closures), instead of semaphores. Lets get straight to the business: why do we need to wrap stuff with semaphores first, and only then do something? :) There could be many variations in actions - a different mixtures of waiting for semaphore + abandoning wait after timeout.. etc etc. There is almost no need for extra primitives, except one which gives you interrupt mode.
Like in sockets, what if we instead of creating a bunch of semaphores:
semaIndex := Smalltalk registerExternalObject: semaphore. readSemaIndex := Smalltalk registerExternalObject: readSemaphore. writeSemaIndex := Smalltalk registerExternalObject: writeSemaphore.
and passing them to primitive: socketHandle := self primSocketCreateNetwork: 0 type: socketType receiveBufferSize: 8000 sendBufSize: 8000 semaIndex: semaIndex readSemaIndex: readSemaIndex writeSemaIndex: writeSemaIndex.
pass the closures instead?
Then, when something happens on socket -> VM will evaluate given closure in interrupt mode. And you're free to react on these events as you like.
What you think?
Cheers, - Andreas