Folks,
My Clock object won't be GCed, because of the thead is kept inside as an Instance variable. clock := nil. Clock allInstances "#( a Clock )" By using "Inspect References" back tracking, startig from Process allInstances select: [:each| each name = 'Clock'] "1 instance" The thread is referencing my Clock object and make it unable to be GCed. I've failed to find a way to make this reference from the thread be weak. implementation of Clock>>run: run "WARNING: If you run a clock, you must remember to stop it. Otherwise, the clock object and its process 'updateProcess' may not be garbage collected." updateProcess := [ [ (Delay forSeconds: self period) wait. self trigger: #tickTock ] repeat ] fork. updateProcess name: 'Clock'; beFinalizable Have a good one. |
Howard Oh wrote:
> My Clock object won't be GCed, because of the thead is kept inside as > an Instance variable. So what you want is for the Process to be a sort of slave to the Clock which dies when the Clock is no longer reachable /except/ by the Process ? If so, you can make that happen, but it's a bit messy. You have to ensure that your Clock is finalisable, and that it stops the Process when it is finalised. Then you have to ensure that the Process has no strong references to the Clock that owns it. But that gives you a problem -- how to trigger events off the Clock ? I think that there are two approaches that could work. One is for the Clock to trigger events off a /different/ object (one that both it and the Process have references to, but which does not itself hold references back to them). The other would be to keep the reference from the Process to the Clock as the only element in a weak collection such as a WeakArray, then you can iterate over the array using #trigger with "every" element (#do: makes the collection strong temporarily). BTW1: When you create the Process, it's probably better to do that from a class-side utility method, just to make /sure/ that you don't accidentally capture a reference to the Clock instance. BTW2: If you are using the WeakArray approach with D5, then I think it would be better to make the iteration look like: weakArray do: [:each | each trigger: #tickTock. each := nil]. or you risk capturing a reference to the Clock in 'each' which will outlast the loop itself. The assignment to 'each is not necessary in D6, and in fact the compiler won't even allow it. -- chris |
In reply to this post by Howard Oh
Howard,
> My Clock object won't be GCed, because of the thead is kept inside as > an Instance variable. > > clock := nil. > Clock allInstances "#( a Clock )" > > By using "Inspect References" back tracking, startig from > > Process allInstances select: [:each| each name = 'Clock'] "1 instance" > > The thread is referencing my Clock object and make it unable to be > GCed. > I've failed to find a way to make this reference from the thread be > weak. My usual approach to such problems is to use an #ensure: block in the thread, and I release any troublesome references (from the thread) there. Stopping the clock (or whatever it happens to be) then reduces to terminating the thread. Any termination of the thread (normally on its own, by unhandled exception, #terminate, etc.) does the cleanup courtesy of #ensure:. Note that you might want to use a Mutex to protect variables used by the thread. You can create the mutex in the clock's (instance side) #initialize, and use #critical: to synchronize access to the variables. Note Dolphin's process browser, and that you can use it to start a debugger on the various threads. Also, you can use the run command to close such a debugger w/o terminating the thread (something that took me all too long to learn). Still, you might want to use my ProcessViewer goodie at times. It displays only call stacks, and IMHO is better than the process browser for isolating deadlocks; often it is a matter of finding a thread that has a wait inside a critical section. Sometimes a critical section is simply too "wide" - I don't make that mistake too often these days, but it was a common problem for me at the beginning. The above is a vague way of saying that once you start creating your own threads, you will start getting into trouble with them. It is generally better to over protect and possibly have a deadlock than to try to find and fix a dirty read or write (which will often be intermittent). I use threads quite often, and enjoy working with them - most of the time ;) Have a good one, Bill -- Wilhelm K. Schwab, Ph.D. [hidden email] |
In reply to this post by Chris Uppal-3
> So what you want is for the Process to be a sort of slave to the Clock which
> dies when the Clock is no longer reachable /except/ by the Process ? That's right! A slave thread is what I want! :-) > You have to ensure that your Clock is finalisable, and that it stops the > Process when it is finalised. Then you have to ensure that the Process has no > strong references to the Clock that owns it. But that gives you a problem -- > how to trigger events off the Clock ? I think that there are two approaches > that could work. One is for the Clock to trigger events off a /different/ > object (one that both it and the Process have references to, but which does not > itself hold references back to them). The other would be to keep the reference > from the Process to the Clock as the only element in a weak collection such as > a WeakArray, then you can iterate over the array using #trigger with "every" > element (#do: makes the collection strong temporarily). > > > BTW1: When you create the Process, it's probably better to do that from a > class-side utility method, just to make /sure/ that you don't accidentally > capture a reference to the Clock instance. > > > BTW2: If you are using the WeakArray approach with D5, then I think it would be > better to make the iteration look like: > weakArray do: > [:each | > each trigger: #tickTock. > each := nil]. > or you risk capturing a reference to the Clock in 'each' which will outlast the > loop itself. The assignment to 'each is not necessary in D6, and in fact the > compiler won't even allow it. As I have some success stories using WeakCollections. I thought there might be a cleaner solution. To read about the messy approach, proposed by a inspiring Chris, I'm very lost what to do now. Before trying the both approaches, I had to see what if there's no reference at all inside the thread defining block. run "This thread has no reference to self, therefore is to be GCed right away." updateProcess := [ [ ] repeat ] forkBack. "My version of #forkAt: user background priority" updateProcess name: 'Clock'; beFinalizable Although it might be a meaningless to have a thread that does nothing, i really wanted to see the thread go GCed following Clock's GC. The simtoms were same; Neither objects were GCed, until niling the clock and killing the process. If the no reference thread is still a problem, weak reference has no chance. As we have agreed to GC rootless objects that has cycling reference, even though the reference count of them is not zero, I think the thread owned by an object must follow the fate of its master. If there is some exceptional case not to, i would like to know about them for a study. :-) I need to study more on this problem. Best Regards |
Howard Oh wrote:
> Before trying the both approaches, I had to see what if there's no > reference at all inside the thread defining block. > > run > > "This thread has no reference to self, therefore is to be GCed > right away." > updateProcess := > [ > [ ] repeat > ] forkBack. "My version of #forkAt: user background > priority" It's always difficult to know exactly what references a block with capture. I /think/ that in D6 that block won't capture a reference to 'this'. I'm pretty sure that under D5 it /will/ capture one. That was why I recommended that you create the Process in a class-side utility method. Actually a better code structure is to split the Clock into two objects. One is the finalisable object which client code creates and uses. The other is an implementation object which manages the Process and which only has weak references to the main Clock. I put together a quick implementation of the above, and (since its pretty small) will attach it to this message in the hope that it'll show better what I mean. NB: tested exactly /once/ ! > I think the thread owned by an object must follow the fate of its > master. If there is some exceptional case not to, i would like to know > about them for a study. :-) That will only happen if you write code to /make/ it happen. It doesn't happen naturally since a Process, once running, is an independent entity which will stay "alive" for as long as it keeps running. It stays alive all by itself ;-) Which is what you normally want threads to do -- they shouldn't stop running just because no one refers to them anymore. -- chris ================== | package | package := Package name: 'CU Clock'. package paxVersion: 0; basicComment: 'Copyright © Chris Uppal, 2006. Simple ticking clock. Illustrates one way to make a slave thread that is cleaned up by finalisation. clock = Clock seconds: 1. clock start. "will now trigger #tick off itself every second" clock stop. "stop ticking" clock start. "starts ticking again" clock := nil. "ticker thread will be cleaned up by finalisation"'. package basicPackageVersion: '0.0001 (unpublished)'. package classNames add: #Clock; add: #ClockTicker; yourself. package binaryGlobalNames: (Set new yourself). package globalAliases: (Set new yourself). package allResourceNames: (Set new yourself). package setPrerequisites: (IdentitySet new add: '..\..\..\Program Files\Dolphin Smalltalk 5.1\Object Arts\Dolphin\Base\Dolphin'; yourself). package! "Class Definitions"! Object subclass: #Clock instanceVariableNames: 'ticker' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''! Object subclass: #ClockTicker instanceVariableNames: 'interval targets process' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''! "Global Aliases"! "Loose Methods"! "End of package definition"! "Source Globals"! "Classes"! Clock guid: (GUID fromString: '{B4B2E8B4-399D-482E-A04B-FDF355C83726}')! Clock comment: 'One of these triggers #tick notification off itself at regular intervals. Once created you should #start it. Thereafter you can #stat and #stop it as you wish. Implementation note: uses a slave thread to implement the ticker. That thread is cleaned up by finalisation if necessary.'! !Clock categoriesForClass!Unclassified! ! !Clock methodsFor! finalize "private -- called once no more strong refs to ourself exist" "clean up the ticker Process, if any" self stop.! interval "answer our interval" ^ ticker interval. ! interval: anInteger "private -- set our interval to anInteger" ticker := ClockTicker interval: anInteger. ticker addTarget: self.! isRunning "answer whether we have been asked to #start but not yet been #stopped" ^ ticker isRunning.! start "start ticking" self isRunning ifFalse: [ticker start. self beFinalizable].! stop "stop ticking" self isRunning ifTrue: [ticker stop. self beUnfinalizable].! ! !Clock categoriesFor: #finalize!finalizing!private!starting & stopping! ! !Clock categoriesFor: #interval!accessing!public! ! !Clock categoriesFor: #interval:!initializing!private! ! !Clock categoriesFor: #isRunning!public!starting & stopping!testing! ! !Clock categoriesFor: #start!public!starting & stopping! ! !Clock categoriesFor: #stop!public!starting & stopping! ! !Clock class methodsFor! milliseconds: anInteger "answer a new instance which will issue ticks at intervals of anInteger milliseconds" ^ (self new) interval: anInteger; yourself.! seconds: anInteger "answer a new instance which will issue ticks at intervals of anInteger seconds" ^ self milliseconds: anInteger * 1000.! ! !Clock class categoriesFor: #milliseconds:!instance creation!public! ! !Clock class categoriesFor: #seconds:!instance creation!public! ! ClockTicker guid: (GUID fromString: '{E6199062-6A8E-40B9-AB02-CDCCB069A954}')! ClockTicker comment: 'One of these provides the real implementation of a Clock. All this stuff is split out so that our ticker Process does not retain a strong reference to the owning clock, and so it can clean up the ticker thread by finalisation.'! !ClockTicker categoriesForClass!Unclassified! ! !ClockTicker methodsFor! addTarget: anObject "add anObject to our list of targets (off which we will trigger tick notifications)" targets add: anObject.! initialize "private -- establish a coherent initial state" targets := WeakIdentitySet new.! interval "answer our interval" ^ interval. ! interval: anInteger "private -- set our interval to anInteger" interval := anInteger.! isRunning "answer whether we have been asked to #start but not yet been #stopped" ^ process notNil.! makeTickerProcess "answer a new Process (in a suspended state) which will do the ticking for us until we ask it to stop" ^ ([ self tickerLoop ] newProcess) priority: self tickingPriority; name: 'Clock ticker'; yourself.! notifyTargets "private -- notify any registered targets of the a clock tick" "NB: the weak collection becomes strong temporarily while this loop executes" targets do: [:each | each trigger: #tick. each := nil].! pause "private -- sleep between steps of our ticker process" (Delay forMilliseconds: interval) wait.! start "start the ticker running" process := self makeTickerProcess. process resume.! stop "stop the ticker" process := nil. ! tick "private -- one step of our ticker process. Answers whether to continue ticking" "we only keep going for as long as we are the designated ticker process" process == Processor activeProcess ifFalse: [^ false]. self notifyTargets. ^ true. ! tickerLoop "private -- the main loop of our ticker process" [self tick] whileTrue: [self pause].! tickingPriority "private -- answer the priority to use for our ticker Process" #CUtodo. "not sure if this is right" ^ Processor userInterruptPriority.! ! !ClockTicker categoriesFor: #addTarget:!initializing!public! ! !ClockTicker categoriesFor: #initialize!initializing!private! ! !ClockTicker categoriesFor: #interval!accessing!public! ! !ClockTicker categoriesFor: #interval:!initializing!private! ! !ClockTicker categoriesFor: #isRunning!public!starting & stopping!testing! ! !ClockTicker categoriesFor: #makeTickerProcess!private!ticking! ! !ClockTicker categoriesFor: #notifyTargets!private!ticking! ! !ClockTicker categoriesFor: #pause!private!ticking! ! !ClockTicker categoriesFor: #start!public!starting & stopping! ! !ClockTicker categoriesFor: #stop!public!starting & stopping! ! !ClockTicker categoriesFor: #tick!private!ticking! ! !ClockTicker categoriesFor: #tickerLoop!private!ticking! ! !ClockTicker categoriesFor: #tickingPriority!private!ticking! ! !ClockTicker class methodsFor! interval: anInteger "answer a new instance which will issue ticks at intervals of anInteger milliseconds" ^ (self new) interval: anInteger; yourself.! new "private -- use #interval" ^ (self basicNew) initialize; yourself.! ! !ClockTicker class categoriesFor: #interval:!instance creation!public! ! !ClockTicker class categoriesFor: #new!instance creation!private! ! "Binary Globals"! "Resources"! ================== |
The entire implementation of Clock Ticker!!
I'm very happy and sorry for that. (sorry for taking your time :-) ) Using WeakIdentitySet goes as your previous post says. The most impression part your package is #isRunning, #stop. They are niling and nil-checking for the process. I think they are eliminating the need for flag variable. I need to read your package more carefully to see the relation bewteen Clock and ClockTicker how they interact. Thank you for your kindness. Best Regards |
I've removed my Clock and filed yours in.
Your Clock is what I've dreamed of. It is a facinating idea to separate the process bearer(ClockTicker) and the finalizer(Clock). BlockContext of the process can not reach Clock, making it possible not to add reference count on it. If the ClockTicker is always created by Clock which is true to responsibility to stop the ticker by its finalization. Kool! My ClockView works fine with your Clock for sharing the same protocol. I wound like to advertise this object to my local colleges if you don't mind. Best Regards, |
Howard,
> I wound like to advertise this object to my local colleges if you don't > mind. If you mean me, then I don't mind at all. Flattered... (Come to that, I don't mind even if you don't mean me ;-) -- chris |
Free forum by Nabble | Edit this page |