[squeak-dev] Re: Suspending process fix

Igor Stasenko siguctua at gmail.com
Tue Apr 28 18:58:51 UTC 2009


2009/4/28 Andreas Raab <andreas.raab at 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
>
>
>



-- 
Best regards,
Igor Stasenko AKA sig.



More information about the Squeak-dev mailing list