[Vm-dev] Delay machinery (was Re: [Pharo-dev] Suspending a Process)

Eliot Miranda eliot.miranda at gmail.com
Fri Jul 25 17:38:49 UTC 2014


Hi Ben,

On Fri, Jul 25, 2014 at 7:56 AM, Ben Coman <btc at openinworld.com> wrote:

>
>  Over the last few days I have been looking deeper into the image locking
> when suspending a process. It is an interesting rabbit hole [1] that leads
> to pondering the Delay machinery, that leads to some VM questions.
>
> When  pressing the interrupt key it seems to always opens the debugger
> with the following call stack.
> Semaphore>>critical:   'self wait'
> BlockClosure>>ensure:     'self valueNoContextSwitch'
> Semaphore>>critical:      'ensure: [ caught ifTrue: [self signal]]
> Delay>>schedule         'AccessProtect critical: ['
> Delay>>wait              'self schedule'
> WorldState>>interCyclePause:
>
> I notice...
>     Delay class >> initialize
>         TimingSemaphore := (Smalltalk specialObjectsArray at: 30).
> and...
>     Delay class >> startTimerEventLoop
>         TimingSemaphore := Semaphore new.
> which seems incongruous that TimingSemaphore is set in differently.  So
> while I presume this critical stuff all works fine, just in an exotic way,
> my entropy-guarding-neuron would just like confirm this is so.
>

The TimingSemaphore gets installed in the specialObjectsArray via

primSignal: aSemaphore atMilliseconds: aSmallInteger
"Signal the semaphore when the millisecond clock reaches the value of the
second argument. Fail if the first argument is neither a Semaphore nor nil.
Essential. See Object documentation whatIsAPrimitive."
<primitive: 136>
^self primitiveFailed

and from that the VM sets the nextWakeupUsecs:

primitiveSignalAtMilliseconds
"Cause the time semaphore, if one has been registered, to be
 signalled when the microsecond clock is greater than or equal to
 the given tick value. A tick value of zero turns off timer interrupts."
| msecsObj msecs deltaMsecs sema |
<var: #msecs type: #usqInt>
msecsObj := self stackTop.
sema := self stackValue: 1.
msecs := self positive32BitValueOf: msecsObj.
 self successful ifTrue:
[(objectMemory isSemaphoreOop: sema) ifTrue:
[*objectMemory splObj: TheTimerSemaphore put: sema*.
 deltaMsecs := msecs - (self ioMSecs bitAnd: MillisecondClockMask).
 deltaMsecs < 0 ifTrue:
[deltaMsecs := deltaMsecs + MillisecondClockMask + 1].
 *nextWakeupUsecs := self ioUTCMicroseconds + (deltaMsecs * 1000)*.
 ^self pop: 2].
 sema = objectMemory nilObject ifTrue:
[objectMemory
storePointer: TheTimerSemaphore
ofObject: objectMemory specialObjectsOop
withValue: objectMemory nilObject.
 *nextWakeupUsecs := 0*.
 ^self pop: 2]].
self primitiveFailFor: PrimErrBadArgument


> --------------
>
> In Delay class >> handleTimerEvent the comment says...
>     "Handle a timer event....
>           -a timer signal (not explicitly specified)"
> ...is that event perhaps a 'tick' generated periodically by the VM via
> that item from specialObjectArray ?  Or is there some other mechanism ?
>

Every time the VM checks for interrupts, which, in the Cog.Stack VMs is
controlled by the heartbeat frequency, which defaults to 2 milliseconds,
the VM checks if the current time has progressed to or beyond
nextWakeupUsecs and signals the timer semaphore if so.

--------------
>
> [1] http://www.urbandictionary.com/define.php?term=Rabbit+Hole
>

and the problem here is not in the VM.  So climb out and breath some fresh
air ;-)

cheers -ben
>
>
> P.S. I've left the following for some initial context as I change the
> subject.  btw Nicolai, I confirm that my proposed fixes only work on
> Windows, not Mavericks (and I haven't checked Linux).
>
> Nicolai Hess wrote:
>
>
> Hi ben, thank you for looking at this.
>
> 2014-07-22 20:17 GMT+02:00 <btc at openinworld.com>:
>
>>  I thought this might be interesting to learn, so I've gave it a go.  I
>> had some success at the end, but I'll give a progressive report.
>>
>> First I thought I'd try moving the update of StringMorph outside the
>> worker-process using a Morph's #step method as follows...
>>
>> Morph subclass: #BackgroundWorkDisplayMorph
>>     instanceVariableNames: 'interProcessString stringMorph'
>>     classVariableNames: ''
>>     category: 'BenPlay'
>>     "---------"
>>
>> BackgroundWorkDisplayMorph>>initializeMorph
>>     self color: Color red.
>>     stringMorph := StringMorph new.
>>     self addMorphBack: stringMorph.
>>     self extent:(300 at 50).
>>     "---------"
>>
>> BackgroundWorkDisplayMorph>>newWorkerProcess
>>     ^[
>>         | work |
>>         work := 0.
>>         [     20 milliSeconds asDelay wait.
>>             work := work + 1.
>>             interProcessString := work asString.
>>         ] repeat.
>>     ] newProcess.
>>     "---------"
>>
>> BackgroundWorkDisplayMorph>>step
>>     stringMorph contents: interProcessString.
>>     "---------"
>>
>> BackgroundWorkDisplayMorph>>stepTime
>>     ^50
>>     "---------"
>>
>> BackgroundWorkDisplayMorph>>initialize
>>     | workerProcess running |
>>     super initialize.
>>     self initializeMorph.
>>
>>     workerProcess := self newWorkerProcess.
>>     running := false.
>>
>>     self on: #mouseUp send: #value to:
>>     [      (running := running not)
>>             ifTrue: [  workerProcess resume. self color: Color green.  ]
>>             ifFalse: [ workerProcess suspend. self color: Color red. ]
>>     ]
>>     "---------"
>>
>>
>>
>> But evaluating "BackgroundWorkDisplayMorph new openInWorld"  found this
>> exhibited the same problematic behavior you reported... Clicking on the
>> morph worked a few times and then froze the UI until Cmd-. pressed a few
>> times.
>>
>
>> However I found the following never locked the GUI.
>>
>> BackgroundWorkDisplayMorph>>initialize
>>     "BackgroundWorkDisplayMorph new openInWorld"
>>     | workerProcess running |
>>     super initialize.
>>     self initializeMorph.
>>
>>     workerProcess := self newWorkerProcess.
>>     running := false.
>>
>>     [ [      (running := running not)
>>             ifTrue: [  workerProcess resume. self color: Color green  ]
>>             ifFalse: [ workerProcess suspend. self color: Color red ].
>>         10 milliSeconds asDelay wait.
>>     ] repeat ] fork.
>>     "---------"
>>
>>
>  This locks the UI as well. Not every timet hough. I did this 5 times,
> every time in a freshly loaded image and it happens two times.
>
>
>
>> So the problem seemed to not be with #suspend/#resume or with the shared
>> variable /interProcessString/.  Indeed, since in the worker thread
>> /interProcessString/ is atomically assigned a copy via #asString, and the
>> String never updated, I think there is no need to surround use of it with a
>> critical section.
>>
>> The solution then was to move the "#resume/#suspend" away from the "#on:
>> #mouseUp send: #value to:" as follows...
>>
>> BackgroundWorkDisplayMorph>>initialize
>>     "BackgroundWorkDisplayMorph new openInWorld"
>>     | workerProcess running lastRunning |
>>     super initialize.
>>     self initializeMorph.
>>
>>     workerProcess := self newWorkerProcess.
>>     lastRunning := running := false.
>>
>>     [ [    lastRunning = running ifFalse:
>>         [    running
>>                 ifTrue: [  workerProcess resume  ]
>>                 ifFalse: [ workerProcess suspend ].
>>             lastRunning := running.
>>         ].
>>         10 milliSeconds asDelay wait.
>>     ] repeat ] fork.
>>
>>     self on: #mouseUp send: #value to:
>>     [      (running := running not)
>>             ifTrue: [  self color: Color green.  ]
>>             ifFalse: [ self color: Color red. ]
>>     ]
>>     "---------"
>>
>
>  And this too :(
>
>
>
>>
>> And finally remove the busy loop.
>>
>> BackgroundWorkDisplayMorph>>initialize
>>     "BackgroundWorkDisplayMorph new openInWorld"
>>     | workerProcess running lastRunning semaphore |
>>     super initialize.
>>     self initializeMorph.
>>
>>     workerProcess := self newWorkerProcess.
>>     lastRunning := running := false.
>>     semaphore := Semaphore new.
>>
>>     [ [    semaphore wait.
>>         running
>>             ifTrue: [  workerProcess resume  ]
>>             ifFalse: [ workerProcess suspend ].
>>     ] repeat ] fork.
>>
>>     self on: #mouseUp send: #value to:
>>     [      (running := running not)
>>             ifTrue: [  self color: Color green.  ]
>>             ifFalse: [ self color: Color red. ].
>>         semaphore signal.
>>     ]
>>     "---------"
>>
>>
>
>  And this locks the UI too. (Loaded the code 20 times, every time after a
> fresh image start up. Two times I got a locked
>  ui after the first two clicks).
>  And I don't understand this code :)
>
>
>
>> Now I can't say how close that is to how it "should" be done.  Its the
>> first time I used sempahores and just what I discovered hacking around.
>> But hey! it works :)
>>
>> cheers -ben
>>
>>
>>
>> Nicolai Hess wrote:
>>
>>  I am still struggling with it.
>>
>>  Any ideas?
>>
>>
>> 2014-07-09 11:19 GMT+02:00 Nicolai Hess <nicolaihess at web.de>:
>>
>>>
>>>
>>>
>>> 2014-07-09 2:07 GMT+02:00 Eliot Miranda <eliot.miranda at gmail.com>:
>>>
>>>  Hi Nicolai,
>>>>
>>>>
>>>>  On Tue, Jul 8, 2014 at 7:19 AM, Nicolai Hess <nicolaihess at web.de>
>>>> wrote:
>>>>
>>>>>  I want to create a process doing some work and call #changed on a
>>>>> Morph.
>>>>> I want to start/suspend/resume or stop this process.
>>>>> But sometimes, suspending the process locks the UI-Process,
>>>>> and I don't know why. Did I miss something or do I have to care when
>>>>> to call suspend?
>>>>>
>>>>>  Wrapping the "morph changed" call in
>>>>>  UIManager default defer:[ morph changed].
>>>>>  Does not change anything.
>>>>>
>>>>> Here is an example to reproduce it.
>>>>> Create the process,
>>>>> call resume, call supsend. It works, most of the time,
>>>>> but sometimes, calling suspend locks the ui.
>>>>>
>>>>> p:=[[true] whileTrue:[ Transcript crShow: (DateAndTime now asString).
>>>>> 30 milliSeconds asDelay wait]] newProcess.
>>>>>
>>>>  p resume.
>>>>> p suspend.
>>>>>
>>>>
>>>>   If you simply suspend this process at random form a user-priority
>>>> process you'll never be able to damage the Delay machinery you're using,
>>>> but chances are you'll suspend the process inside the critical section that
>>>> Transcript uses to make itself thread-safe, and that'll lock up the
>>>> Transcript.
>>>>
>>>
>>>  Thank you Eliot
>>>  yes I guessed it locks up the critical section, but I hoped with would
>>> not happen if I the use UIManager defer call.
>>>
>>>
>>>
>>>>
>>>>  ThreadSafeTranscript>>nextPutAll: value
>>>>   accessSemaphore
>>>>  critical: [stream nextPutAll: value].
>>>>  ^value
>>>>
>>>>  So instead you need to use a semaphore.  e.g.
>>>>
>>>>  | p s wait |
>>>> s := Semaphore new.
>>>> p:=[[true] whileTrue:[wait ifTrue: [s wait]. Transcript crShow:
>>>> (DateAndTime now asString). 30 milliSeconds asDelay wait]] newProcess.
>>>> wait := true.
>>>> 30 milliSeconds asDelay wait.
>>>>  wait := false.
>>>> s signal
>>>>
>>>>  etc...
>>>>
>>>
>>>  Is this a common pattern I can find in pharos classes. Or I need some
>>> help understanding this. The semaphore
>>>  wait/signal is used instead of process resume/suspend?
>>>
>>> What I want is a process doing repeatly some computation,
>>> calls or triggers an update on a morph, and I want to suspend and resume
>>> this process.
>>>
>>> I would stop this discussion if someone tells me, "No your are doing it
>>> wrong, go this way ..",  BUT what strikes me:
>>> in this example, that reproduces my problem more closely:
>>>
>>> |p m s running|
>>> running:=false.
>>> m:=Morph new color:Color red.
>>> s:= StringMorph new.
>>> m addMorphBack:s.
>>> p:=[[true]whileTrue:[20 milliSeconds asDelay wait. s
>>> contents:(DateAndTime now asString). m changed]] newProcess.
>>> m on:#mouseUp send:#value to:[
>>>     running ifTrue:[p suspend. m color:Color red.]
>>>     ifFalse:[p resume.m color:Color green.].
>>>     running := running not].
>>> m extent:(300 at 50).
>>> m openInWorld
>>>
>>>
>>>  clicking on the morph will stop or resume the process, if it locks up
>>> I can still press alt+dot ->
>>>  - a Debugger opens but the UI is still not responsive. I can click
>>> with the mouse on the debuggers close icon.
>>>  - nothing happens, as the UI is still blocked.
>>>  - pressing alt+Dot again, the mouse click on the close icon is
>>> processed and the first debugger window closes
>>> - maybe other debuggers open.
>>>
>>> Repeating this steps, at some time the system is *fully* responsive
>>> again!
>>>  And miraculously, it works after that without further blockages.
>>> What's happening here?
>>>
>>>  Nicolai
>>>
>>> HTH
>>>>
>>>>   regards
>>>>>  Nicolai
>>>>>
>>>>
>>>>  --
>>>> best,
>>>> Eliot
>>>>
>>> --
Aloha,
Eliot
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/vm-dev/attachments/20140725/70fd9bc6/attachment-0001.htm


More information about the Vm-dev mailing list