Thread held in a instance variable won't be GCed.

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Thread held in a instance variable won't be GCed.

Howard Oh
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.


Reply | Threaded
Open this post in threaded view
|

Re: Thread held in a instance variable won't be GCed.

Chris Uppal-3
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


Reply | Threaded
Open this post in threaded view
|

Re: Thread held in a instance variable won't be GCed.

Schwab,Wilhelm K
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]


Reply | Threaded
Open this post in threaded view
|

Re: Thread held in a instance variable won't be GCed.

Howard Oh
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


Reply | Threaded
Open this post in threaded view
|

Re: Thread held in a instance variable won't be GCed.

Chris Uppal-3
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"!
==================


Reply | Threaded
Open this post in threaded view
|

Re: Thread held in a instance variable won't be GCed.

Howard Oh
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


Reply | Threaded
Open this post in threaded view
|

Re: Thread held in a instance variable won't be GCed.

Howard Oh
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,


Reply | Threaded
Open this post in threaded view
|

Re: Thread held in a instance variable won't be GCed.

Chris Uppal-3
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