Hi Ben, On Fri, Jul 25, 2014 at 7:56 AM, Ben Coman <[email protected]> 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 <[email protected]>: > >> 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@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 <[email protected]>: >> >>> >>> >>> >>> 2014-07-09 2:07 GMT+02:00 Eliot Miranda <[email protected]>: >>> >>> Hi Nicolai, >>>> >>>> >>>> On Tue, Jul 8, 2014 at 7:19 AM, Nicolai Hess <[email protected]> >>>> 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@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
