[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