|
View:
New views
9 Messages
—
Rating Filter:
Alert me
|
|
|
WeakReferenceProxyAfter glancing at the JavaDoc for
GlazedLists.weakReferenceProxy(EventList<E> source, ListEventListener<E> target), I have a few questions: Would it have been possible to eliminate the need for the EventList.dispose() methods if EventList implementations registered weakReferenceProxys on their upstream source lists? How does the weakReferenceProxy receive its notification to remove itself from the source? Does it use some sort of background thread to check a java.lang.ref.ReferenceQueue<T>? Bruce --------------------------------------------------------------------- To unsubscribe, e-mail: users-unsubscribe@... For additional commands, e-mail: users-help@... |
|
|
Re: WeakReferenceProxyHey Bruce,
Unfortunately it's not as simple as you mention. Both in user code and in GL core code we often have intermediate EventLists that no entity is supposed to "see" or "have a handle to" since they represent intermediate states that aren't appropriate for use in and of themselves. WeakReferences sort of fail on those scenarios since we don't want those intermediate EventLists to be GC'd (they exist and support downstream EventLists that ARE meant to be accessed), BUT, if the downstream EventLists are all disposed, then we WOULD like them to be GC'd. As a concrete example, UniqueList rests on a private SortedList and does not retain a reference to it. Today it simply calls (effectively): super(new SortedList(source)); As such, we sort of mulled over the idea of hard references throughout your tree of EventLists EXCEPT for leaf ListEventListeners. The idea is that when the leaves are no longer referenced we'd GC them and then replace the hard references to the "new leaves" with weak references. But, we backed off this scenario due to the shear implementation complexity and the fact that dispose() was already part of EventList for years and thus its deprecation and ultimate removal would also be years in the making. To your other question, we take the laziest approach one can take: each time listChanged() is called we check if the delegate ListEventListener has been GC'd. If it has, we remove "ourselves" as a listener. The relevant code looks like this: /**The conditions under which the WeakReferenceProxy are cleaned up are documented in the class doc: <p>Specifically, the proxy stops listening to <i>L</i> theJames
On Fri, Sep 4, 2009 at 11:58 AM, Bruce Alspaugh <compulinkltd@...> wrote: After glancing at the JavaDoc for GlazedLists.weakReferenceProxy(EventList<E> source, ListEventListener<E> target), I have a few questions: |
|
|
Re: WeakReferenceProxy[dispose() has not been a part of the EventList interface for that much time. That is not the problem here, though, IMO] The "leaf logic" is not necessary if I have understood this correctly. I believe that this has been "touched into" before on the list.
Consider: BasicEventList -> SortedList -> FilteredList The BasicEventList is strongly held (say, for the sake of argument, as a static), as is the FilteredList (again, for the sake of the argument, as a stack variable). The SortedList is just a intermediary, which hasn't got any references to it, except that it has registered with BasicEventList as listener, which thus strongly holds it.
A problem is that if we just let go of the FilteredList, nothing happens - it is still strongly referenced. The API of GL doesn't help us either - there is no way to get at the FilteredList anymore, and it is utterly leaked. (Both "memory leaked", but maybe more annoyingly, also "processing leaked", as all events still will be sent to it)
A perceived problem of introducing WeakReferenceProxies between the lists, is that SortedList would be GC'ed out of existence as nothing would reference it strongly, thus leaving the FilteredList hanging outside the event stream. This is not so, I believe:
Reasoning: All current lists except the BasicEventList, which obviously acts as "roots" for these chains, holds a reference to its _source_ (since they all extend TransformedList). Thus, we really have this situation BasicEventList -> SortedList -> FilteredList <- <-
When we introduce the WeakReferenceProxies, we end up like this (a WeakReferenceProxy also has a link to its source) BasicEventList -strong-> WeakRefProxy -weak-> SortedList -strong-> WeakRefProxy -weak-> FilteredList
<-strong---------------------- <-strong---------------------- <-strong- <-strong-
We can for now basically ignore the fact that it isn't like this at all: It is not the lists that hold references to their listeners. The list holds a reference to its publisher, which holds a reference to ALL the listeners. But (I believe) it at this point boils down to the same.
If we let go of the FilteredList, there is nothing that holds it strongly anymore. NEITHER is there for SortedList. They would be free to be GC'ed. When they are GC'ed, their referent in the WeakReferenceProxy is also cleared - thats how [Soft|Weak]References work.
When anything that triggers the WeakReferenceProxy purging happens (see below), these proxies are noticed (there is special handling for them in the publisher), and it will be noticed that they are cleared. Therefore, they are deregistered as listeners to their source (and the source field is nulled), and then they're also GCable.
Thus, we are now left with only the BasicEventList - just as we would want. (The "node logic" of the publisher, which I still haven't gotten a proper answer to how really works, would maybe have to be extended to handle the WeakReferenceProxies also when stacking up the listening order (the "if a listener is also a list"-part: If that test fails, it should also check whether it is a WeakReferenceProxy, in which case, it should check the referent whether it is a list instead).
The conditions under which the WeakReferenceProxy are cleaned up are documented in the class doc:
It rests on the publisher. Thus, if you register or deregister any listener on any list that has the same publisher, all cleared WeakReferenceProxies will be purged. The same goes for sending an event: If you make a change to any list whose ListEvent ends up going through any dead WeakReferenceProxy (on any list with the same publisher), the proxy listener will deregister itself, and hence trig the above mentioned situation.
Back to the above argument: The fact that the ASCII-art doesn't really represent the situation doesn't destroy the argument. The FilteredList is still GC'ed, and its proxy listener referent it cleared. This would happen at the same time, or later, or even before, to the SortedList's proxy listener. But, the WeakReferenceProxies themselves are actually strongly referenced still, by the fact that the BasicEventList (which is referenced through a static, remember) holds a reference to the publisher, which holds all the listeners, and thus the proxies. But, the clearing-logic still kicks in: Their referent is cleared, and thus they will be deregistered and out of the system, not incurring neither mem nor processing leaks.
Coincidentally, I've just these latter days used hours - actually, a full day - on hunting down just such a retention-bug due to WeakReferenceProxies and the publisher. Here's a static method I ended up with to generically be able to "trig" a manual purging of all WeakReferenceProxies on a publisher, with some JavaDoc to explain (helping me to remember, at least!).
/** * <b><font color="red">Special method for GlazedLists memory leak hunting (using a profiler)</font></b>: What * happens, is that several GlazedLists elements seem to be hanging for no good reason. It turns out that this is a
* problem caused by two "crashing" aspects during testing: When using {@link Assert#assertEquals(Object, Object)} * and sticking (typically) some {@link List} as expected and an {@link EventList} as actual, the JUnit-framework
* typically ends up invoking {@link AbstractList#equals(Object)} (<code>expected.equals(actual)</code>) which again * employs the {@link List#listIterator() ListIterators} of both lists for comparison. The ListIterator is in
* GlazedLists an active iterator, employing a {@link ListEventListener} to listen on the {@link EventList}. Since * there is no dispose logic on the iterator, it is implemented using a {@link WeakReferenceProxy}, which is a
* {@link ListEventListener} proxy implemented using a {@link WeakReference} which one registers on the list one * want to listen to. It lets the actual listener become GCable when it is ditched from the universe (it is the
* WeakReference referent). This is surely handy for the {@link ListIterator} mentioned - when the code is finished * with the iterator, it goes out of scope, and since it then is only weakly referenced, it will become GC'ed, and
* the referent in the WeakReference is cleared (nulled). * <p /> * The WeakReferenceProxy is registered with the source list, and the source reference is contained within the
* WeakRefrenceProxy. However, listeners are not registered with the list itself, but instead on the publisher for * the list. Thus we now end up with a strong reference to the source by way of any other list having the same
* publisher. (Each EventList holds a reference to some publisher. Lists that want to work together needs the same * publisher. Thus, if any list is retained with this publisher, the source list in question will also be retained).
* <p /> * The publisher is cleaned of these weak proxies lazily (it is not possible to attach any {@link ReferenceQueue} to * them upon creation), and the cleaning can from the outside be triggered by adding or removing a ListEventListener
* to any list that the publisher handles: When doing such add or remove, the entire set of listeners is traversed - * and in this process, any WeakReferenceProxies that is stumbled across that has a null referent is dropped.
* <p /> * The cleaning can however also be done by making <b>any</b> list change on <b>any</b> list that results in a * {@link ListEvent} being <i>fired through</i> <b>any</b> cleared {@link WeakReferenceProxy} in the
* publisher-system. This because the proxy (obviously) has to check whether the referent is cleared, in which case * it also deregisters itself from the source (which it as mentioned carries a reference to), which triggers the
* above mentioned process, thus removing <b>all</b> cleared WeakReferenceProxies in this publisher. Finally it * nulls source, which I actually believe thus happens twice for the ONE proxy that starts the process. Note though
* that this isn't as certain - if you don't quite know how to be sure that you ensure an event is fired through * such a proxy, you will have to do some change to all lists. Instead, one only have to do one change to the set of
* ListEventListeners registered with the publisher to be ensured of clearing. * <p /> * <b>How this method works:</b> This method first GC's to have all WeakReferences cleared (the referent is nulled,
* the referent being the ListEventListener that should get the event). It then then makes a dummy * {@link BasicEventList} <i>using the supplied {@link ListEventPublisher}</i>, makes a dummy
* {@link ListEventListener}, then adds and remove the latter on the former. This should (and apparently does!) * ensure that these {@link WeakReferenceProxy}s are removed, and hence that the <i>source</i> lists of these
* proxies become GCable. */ public static void cleanListEventPublisherForWeakReferenceProxies(ListEventPublisher publisher) { System.gc(); try {
Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } EventList<?> dummyList = new BasicEventList<Object>(publisher, LockFactory.DEFAULT.createReadWriteLock());
ListEventListener<Object> dummyListener = new ListEventListener<Object>() { @Override public void listChanged(@SuppressWarnings ("unused") ListEvent<Object> listChanges) {
/* no-op */ } }; dummyList.addListEventListener(dummyListener); dummyList.removeListEventListener(dummyListener);
} James |
|
|
re[2]: WeakReferenceProxyWell, dispose() has been part of the design for a long, long time - so I disagree with that - but I also agree that isn't really the problem here. Endre's point about strong backwards references is solid (the discussion about leaf nodes, etc... assumed that this backwards pointer is not available, but I think in the general case that it is).
The cost of trying to track down one of these resource leaks is huge (and it generally happens after you've shipped RC1).
Some thoughts:
If a list in the library itself doesn't hold references that would prevent GC from collecting where it shouldn't, it seems like it should be easy to add those references to the specific list implementations. I know that this is not trivial - there are often lots of crazy intermediate lists generated in some of the more complex systems - but is this really as hard as we think? Perhaps it would be easier with the ability to obtain deleted element references?
A bigger issue is GL users who have code constructed around this behavior. To give a concrete example of where problems could occur, consider a Calculation object that accepts a FilteredList as it's source. If the calculation does not hold a hard reference to it's source (which it very well might not do), and the user does not hold a reference of the FilteredList (it is an intermediate object, after all), then the calculation would just silently stop receiving events. Not good (these sorts of things are just as hard to track down and fix as the possibility for a resource leak we have now!).
From an API perspective, I think that this could be managed with an additional parameter passed when setting up listeners. Internal GL lists would always set the parameter (and ensure that they hold backwards references to source). Is it that simple?
- K
----------------------- Original Message -----------------------
From: Endre Stølsvik
To: users@...
Cc:
Date: Wed, 9 Sep 2009 00:45:01 +0200
Subject: Re: WeakReferenceProxy
[dispose() has not been a part of the EventList interface for that much time. That is not the problem here, though, IMO]
The "leaf logic" is not necessary if I have understood this correctly. I believe that this has been "touched into" before on the list.
Consider:
BasicEventList -> SortedList -> FilteredList
The BasicEventList is strongly held (say, for the sake of argument, as a static), as is the FilteredList (again, for the sake of the argument, as a stack variable). The SortedList is just a intermediary, which hasn't got any references to it, except that it has registered with BasicEventList as listener, which thus strongly holds it.
A problem is that if we just let go of the FilteredList, nothing happens - it is still strongly referenced. The API of GL doesn't help us either - there is no way to get at the FilteredList anymore, and it is utterly leaked. (Both "memory leaked", but maybe more annoyingly, also "processing leaked", as all events still will be sent to it)
A perceived problem of introducing WeakReferenceProxies between the lists, is that SortedList would be GC'ed out of existence as nothing would reference it strongly, thus leaving the FilteredList hanging outside the event stream. This is not so, I believe:
Reasoning: All current lists except the BasicEventList, which obviously acts as "roots" for these chains, holds a reference to its _source_ (since they all extend TransformedList).
Thus, we really have this situation
BasicEventList -> SortedList -> FilteredList
<- <-
When we introduce the WeakReferenceProxies, we end up like this (a WeakReferenceProxy also has a link to its source)
BasicEventList -strong-> WeakRefProxy -weak-> SortedList -strong-> WeakRefProxy -weak-> FilteredList
<-strong---------------------- <-strong----------------------
<-strong- <-strong-
We can for now basically ignore the fact that it isn't like this at all: It is not the lists that hold references to their listeners. The list holds a reference to its publisher, which holds a reference to ALL the listeners. But (I believe) it at this point boils down to the same.
If we let go of the FilteredList, there is nothing that holds it strongly anymore. NEITHER is there for SortedList. They would be free to be GC'ed.
When they are GC'ed, their referent in the WeakReferenceProxy is also cleared - thats how [Soft|Weak]References work.
When anything that triggers the WeakReferenceProxy purging happens (see below), these proxies are noticed (there is special handling for them in the publisher), and it will be noticed that they are cleared. Therefore, they are deregistered as listeners to their source (and the source field is nulled), and then they're also GCable.
Thus, we are now left with only the BasicEventList - just as we would want.
(The "node logic" of the publisher, which I still haven't gotten a proper answer to how really works, would maybe have to be extended to handle the WeakReferenceProxies also when stacking up the listening order (the "if a listener is also a list"-part: If that test fails, it should also check whether it is a WeakReferenceProxy, in which case, it should check the referent whether it is a list instead). The conditions under which the WeakReferenceProxy are cleaned up are documented in the class doc: It rests on the publisher. Thus, if you register or deregister any listener on any list that has the same publisher, all cleared WeakReferenceProxies will be purged.
The same goes for sending an event: If you make a change to any list whose ListEvent ends up going through any dead WeakReferenceProxy (on any list with the same publisher), the proxy listener will deregister itself, and hence trig the above mentioned situation.
Back to the above argument: The fact that the ASCII-art doesn't really represent the situation doesn't destroy the argument. The FilteredList is still GC'ed, and its proxy listener referent it cleared. This would happen at the same time, or later, or even before, to the SortedList's proxy listener. But, the WeakReferenceProxies themselves are actually strongly referenced still, by the fact that the BasicEventList (which is referenced through a static, remember) holds a reference to the publisher, which holds all the listeners, and thus the proxies. But, the clearing-logic still kicks in: Their referent is cleared, and thus they will be deregistered and out of the system, not incurring neither mem nor processing leaks.
Coincidentally, I've just these latter days used hours - actually, a full day - on hunting down just such a retention-bug due to WeakReferenceProxies and the publisher.
Here's a static method I ended up with to generically be able to "trig" a manual purging of all WeakReferenceProxies on a publisher, with some JavaDoc to explain (helping me to remember, at least!).
/**
* <b><font color="red">Special method for GlazedLists memory leak hunting (using a profiler)</font></b>: What
* happens, is that several GlazedLists elements seem to be hanging for no good reason. It turns out that this is a
* problem caused by two "crashing" aspects during testing: When using {@link Assert#assertEquals(Object, Object)}
* and sticking (typically) some {@link List} as expected and an {@link EventList} as actual, the JUnit-framework
* typically ends up invoking {@link AbstractList#equals(Object)} (<code>expected.equals(actual)</code>) which again
* employs the {@link List#listIterator() ListIterators} of both lists for comparison. The ListIterator is in
* GlazedLists an active iterator, employing a {@link ListEventListener} to listen on the {@link EventList}. Since
* there is no dispose logic on the iterator, it is implemented using a {@link WeakReferenceProxy}, which is a
* {@link ListEventListener} proxy implemented using a {@link WeakReference} which one registers on the list one
* want to listen to. It lets the actual listener become GCable when it is ditched from the universe (it is the
* WeakReference referent). This is surely handy for the {@link ListIterator} mentioned - when the code is finished
* with the iterator, it goes out of scope, and since it then is only weakly referenced, it will become GC'ed, and
* the referent in the WeakReference is cleared (nulled).
* <p />
* The WeakReferenceProxy is registered with the source list, and the source reference is contained within the
* WeakRefrenceProxy. However, listeners are not registered with the list itself, but instead on the publisher for
* the list. Thus we now end up with a strong reference to the source by way of any other list having the same
* publisher. (Each EventList holds a reference to some publisher. Lists that want to work together needs the same
* publisher. Thus, if any list is retained with this publisher, the source list in question will also be retained).
* <p />
* The publisher is cleaned of these weak proxies lazily (it is not possible to attach any {@link ReferenceQueue} to
* them upon creation), and the cleaning can from the outside be triggered by adding or removing a ListEventListener
* to any list that the publisher handles: When doing such add or remove, the entire set of listeners is traversed -
* and in this process, any WeakReferenceProxies that is stumbled across that has a null referent is dropped.
* <p />
* The cleaning can however also be done by making <b>any</b> list change on <b>any</b> list that results in a
* {@link ListEvent} being <i>fired through</i> <b>any</b> cleared {@link WeakReferenceProxy} in the
* publisher-system. This because the proxy (obviously) has to check whether the referent is cleared, in which case
* it also deregisters itself from the source (which it as mentioned carries a reference to), which triggers the
* above mentioned process, thus removing <b>all</b> cleared WeakReferenceProxies in this publisher. Finally it
* nulls source, which I actually believe thus happens twice for the ONE proxy that starts the process. Note though
* that this isn't as certain - if you don't quite know how to be sure that you ensure an event is fired through
* such a proxy, you will have to do some change to all lists. Instead, one only have to do one change to the set of
* ListEventListeners registered with the publisher to be ensured of clearing.
* <p />
* <b>How this method works:</b> This method first GC's to have all WeakReferences cleared (the referent is nulled,
* the referent being the ListEventListener that should get the event). It then then makes a dummy
* {@link BasicEventList} <i>using the supplied {@link ListEventPublisher}</i>, makes a dummy
* {@link ListEventListener}, then adds and remove the latter on the former. This should (and apparently does!)
* ensure that these {@link WeakReferenceProxy}s are removed, and hence that the <i>source</i> lists of these
* proxies become GCable.
*/
public static void cleanListEventPublisherForWeakReferenceProxies(ListEventPublisher publisher) {
System.gc();
try {
Thread.sleep(500);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
EventList<?> dummyList = new BasicEventList<Object>(publisher, LockFactory.DEFAULT.createReadWriteLock());
ListEventListener<Object> dummyListener = new ListEventListener<Object>() {
@Override
public void listChanged(@SuppressWarnings ("unused") ListEvent<Object> listChanges) {
/* no-op */
}
};
dummyList.addListEventListener(dummyListener);
dummyList.removeListEventListener(dummyListener);
} James --------------------------------------------------------------------- To unsubscribe, e-mail: users-unsubscribe@... For additional commands, e-mail: users-help@... |
|
|
Re: re[2]: WeakReferenceProxyOn Wed, Sep 9, 2009 at 01:50, Kevin Day <kevin@...> wrote:
Me: [dispose() has not been a part of the EventList interface for that much time. ..]
Changelog: --------------------- User: jplemieux Date: 2009-01-26 01:46:46+0000 Removed: glazedlists/source/ca/odell/glazedlists/DisposableEventList.java Modified: glazedlists/source/ca/odell/glazedlists/BasicEventList.java glazedlists/source/ca/odell/glazedlists/EventList.java glazedlists/source/ca/odell/glazedlists/DebugList.java glazedlists/source/ca/odell/glazedlists/TransformedList.java glazedlists/extensions/hibernate/source/ca/odell/glazedlists/hibernate/PersistentEventList.java Log: adding dispose() to the EventList interface --------------------- It was a part of the abstract class TransformedList before. I argued that it had to be a part of the EventList interface. The reasoning was that I write APIs that return EventList. I didn't want to return _a class_ to have the slightest possibility of having proper resource handling, I wanted to return _an interface_.
Endre. |
|
|
Re: re[2]: WeakReferenceProxy
Which EventList implementation would this be? As GL stands now, only BasicEventList, DebugEventList and PersistentEventList directly implement EventList. All of these I believe are meant as "roots" (I don't know PersistentEL, though). The rest actually inherits from TransformedList - which has the source field.
However, this might be a moot point - read on!
This argument was pretty good - an extra requirement has apparently been introduced by having WeakReferenceProxies all over: Any listener must hold a reference to its source.
However, magically, I believe it is not (that big of) a problem anyway - and the savior here is the publisher. Lets make a longer chain, so that there are more middle nodes.
BL -s-> SL -s> FL -s> WL -s> Calc Introduce proxies BL -s> P -w> SL -s> P -w> FL -s> P -w> WL -s> P -w> Calc
<s--------- <s--------- <s--------- <s-- <s-- <s--
The Calc doesn't hold a strong reference backwards. At this point, your argument holds: The WhateverList (WL) has no strong reference to it, nor does anything else up to BasicList (BL - which is held by the static), even though Calc is strongly held by the thread in question. The entire structure would thus be torn down, leaving you with the BasicList and Calc without any connection in between.
However, if we now make it more realistic
BL . P -w> SL . P -w> FL . P -w> WL . P -w> Calc <s-+----- <s-+----- <s-+----- |
<s-+ <s-+ <s-+ <s-+ | | | |
Publisher ------+----------+----------/ What we see, is that even though Calc doesn't have a strong reference backwards, the publisher, which the BasicList references (and all intermediate lists share, but that is not the point), holds a strong reference to each of the proxies, which hold a strong reference back to the list it proxies. As long as the Calc object is strongly referenced from somewhere, the WeakReferenceProxy's referent will not be nulled. The whole structure thus stands.
When the Calc object goes out of scope (isn't referenced anymore), it will be GC'ed, and the referent cleared. The publisher will notice this on the next fired event, and drop that proxy out of the system (deregister it (from the publisher), and actually nulls the source reference, an operation which isn't actually needed in this case). At this point, WL is not held by any strong reference, and will eventually be GC'ed, and hence that list's proxy's referent will be cleared. On the next fire, that one will be cleared - and so on. So, after a sequence of a GC then an event fired, repeated four times, the BasicEventList would be alone.
See, even if one drops the reference backwards on the intermediate lists:
BL . P -w> SL . P -w> FL . P -w> WL . P -w> Calc
<s-+ <s-+ <s-+ <s-+ | | | |
Publisher ------+----------+----------/ .. the entire argument still seems to stand.
HOWEVER, if the Calc-object itself e.g. have some different event firing system outside of GlazedLists, whereby some new object X hooks itself up as a listener onto Calc's own event system, without X keeping a reference to Calc, one would be in trouble: Calc would be completely unreferenced and thus GCable, and the chain in front of it would tumble down. That would probably be very hard to track down. One can argue that this would hold for ANY object that one just adds oneself as a reference to without holding a reference back to - it will be GC'ed. Problem is that one could quite possibly expect "an active object" that fires events and all, to obviously somehow be referenced somewhere! Which is the case - only it is through a WeakReference.
The discussion should probably revolve around what is the most common problem - what would benefit GL users the most. One question I sit with, though, is how you guys handle the problem now? With such a chain of intermediate lists that you have not got a reference to (you only reference the tip)? Neither dispose() nor removeListEventListener() recurse.
That is my reason for needing to extend the classes - which I couldn't, so I had to copy them. Have this ever been tried implemented? (How I personally handle the problem now is by doing these "reimplementations" to implement recursion, AND having some shim "trackers" that I dish out instead of the actual list instance. These are referenced using WeakReferences that I register with a ReferenceQueue. When the ref-cleaner thread checks in on such a shim (because it was GCed), it checks if the "user" has disposed the list in question. If it hasn't, I do it then - and log a angry error log message, with an "InstantiationStackTrace" for easy debugging!)
(Personally, I currently lean towards that dispose() and/or removeListEventListener() should recurse. If one at a certain stage of the pipeline didn't want the recursion to happen, one could insert a DontRecurseDisposeList shim that .. did as its name implies)
Endre.
|
|
|
re[4]: WeakReferenceProxyHah - excellent point. I am so used to working with TransformedList that I forgot this. I'm still digesting your other email - will respond to it when I understand it fully (or partially :-) )
- K
----------------------- Original Message -----------------------
From: Endre Stølsvik
To: users@...
Cc:
Date: Wed, 9 Sep 2009 02:30:27 +0200
Subject: Re: re[2]: WeakReferenceProxy
On Wed, Sep 9, 2009 at 01:50, Kevin Day <kevin@...> wrote:
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@...
For additional commands, e-mail: users-help@...
Me:
[dispose() has not been a part of the EventList interface for that much time. ..]
Changelog:
---------------------
User: jplemieux Date: 2009-01-26 01:46:46+0000 Removed: glazedlists/source/ca/odell/glazedlists/DisposableEventList.java Modified: glazedlists/source/ca/odell/glazedlists/BasicEventList.java glazedlists/source/ca/odell/glazedlists/EventList.java glazedlists/source/ca/odell/glazedlists/DebugList.java glazedlists/source/ca/odell/glazedlists/TransformedList.java glazedlists/extensions/hibernate/source/ca/odell/glazedlists/hibernate/PersistentEventList.java Log: adding dispose() to the EventList interface ---------------------
It was a part of the abstract class TransformedList before. I argued that it had to be a part of the EventList interface. The reasoning was that I write APIs that return EventList. I didn't want to return _a class_ to have the slightest possibility of having proper resource handling, I wanted to return _an interface_.
Endre. |
|
|
re[4]: WeakReferenceProxyEndre -
This is a very good explanation of what is going on - thanks for putting it together (especially the ASCII art!).
I was especially inerested in this diagram:
BL . P -w> SL . P -w> FL . P -w> WL . P -w> Calc
<s-+ <s-+ <s-+ <s-+
| | | | Publisher ------+----------+----------/ which implies that the downstream list doesn't technically have to hold a reference to it's source at all... This would further imply that it would be possible to create an EventList implementation that would implement the proxy behavior (instead of the *listener* being the weak reference, the list itself would behave as a weak reference to it's source). I think that this is similar to what you are currently doing with your shim lists, right? Are there advantages to this approach, vs having the downstream list hold a hard reference to the source, and only using weak references for the listeners? It would seem that there would be some modest performance advantages to holding the hard reference - as long as there is no need to detect the need to call dispose()...
To answer your question about how we handle this now, it's a hassle. This is one reason that I'm so interested in the discussion. For simple applications where down stream event list pipelines don't come and go on a regular basis, the current implementation is ok - but as apps increase in complexity (and our app is well into that territory), this becomes a big administrative problem. So, is this all really as simple as just using weak reference proxy listeners in each list implementation? How important is dispose() then (even as an internal implementation detail)? I could certainly see the argument that it should be removed from the EventList interface. Can this be pushed further and just say that all entries registered with the publisher are treated as weak reference proxies (i.e. this would be an internal detail of the publisher, or any other GL class that holds listeners)?
I keep coming back to saying that we have to look at what is actually going on in dispose() on some of these lists to get a feel for what the implications here would be. For example, FilterList uses dispose() to releast listeners from matcher editors, and remove references to the matchereditor and matcher (in addition to removing the listener from the publisher). If the FilterList was registered as a weak reference to the MatcherEditor, would things stop working? You would think that, in the general case, users would have to hold a reference to the MatcherEditor to make it do anything useful, so maybe this is OK?
CollectionList is another list that has fairly complex dispose() behavior (clearing related listeners, etc...).
It would really help if we could get some more counter examples showing why this sort of thing *wouldn't* work. The Calculation-with-reference-not-held is one case, but one that I personally could live with.
Are there other solid use cases that anyone else can suggest?
- K
----------------------- Original Message -----------------------
From: Endre Stølsvik
To: users@...
Cc:
Date: Wed, 9 Sep 2009 03:46:18 +0200
Subject: Re: re[2]: WeakReferenceProxy
Which EventList implementation would this be?
As GL stands now, only BasicEventList, DebugEventList and PersistentEventList directly implement EventList. All of these I believe are meant as "roots" (I don't know PersistentEL, though). The rest actually inherits from TransformedList - which has the source field.
However, this might be a moot point - read on!
This argument was pretty good - an extra requirement has apparently been introduced by having WeakReferenceProxies all over: Any listener must hold a reference to its source.
However, magically, I believe it is not (that big of) a problem anyway - and the savior here is the publisher.
Lets make a longer chain, so that there are more middle nodes.
BL -s-> SL -s> FL -s> WL -s> Calc
Introduce proxies
BL -s> P -w> SL -s> P -w> FL -s> P -w> WL -s> P -w> Calc
<s--------- <s--------- <s---------
<s-- <s-- <s--
The Calc doesn't hold a strong reference backwards. At this point, your argument holds: The WhateverList (WL) has no strong reference to it, nor does anything else up to BasicList (BL - which is held by the static), even though Calc is strongly held by the thread in question. The entire structure would thus be torn down, leaving you with the BasicList and Calc without any connection in between.
However, if we now make it more realistic
BL . P -w> SL . P -w> FL . P -w> WL . P -w> Calc
<s-+----- <s-+----- <s-+----- |
<s-+ <s-+ <s-+ <s-+
| | | | Publisher ------+----------+----------/
What we see, is that even though Calc doesn't have a strong reference backwards, the publisher, which the BasicList references (and all intermediate lists share, but that is not the point), holds a strong reference to each of the proxies, which hold a strong reference back to the list it proxies. As long as the Calc object is strongly referenced from somewhere, the WeakReferenceProxy's referent will not be nulled. The whole structure thus stands.
When the Calc object goes out of scope (isn't referenced anymore), it will be GC'ed, and the referent cleared. The publisher will notice this on the next fired event, and drop that proxy out of the system (deregister it (from the publisher), and actually nulls the source reference, an operation which isn't actually needed in this case). At this point, WL is not held by any strong reference, and will eventually be GC'ed, and hence that list's proxy's referent will be cleared. On the next fire, that one will be cleared - and so on. So, after a sequence of a GC then an event fired, repeated four times, the BasicEventList would be alone.
See, even if one drops the reference backwards on the intermediate lists:
BL . P -w> SL . P -w> FL . P -w> WL . P -w> Calc
<s-+ <s-+ <s-+ <s-+
| | | | Publisher ------+----------+----------/
.. the entire argument still seems to stand.
HOWEVER, if the Calc-object itself e.g. have some different event firing system outside of GlazedLists, whereby some new object X hooks itself up as a listener onto Calc's own event system, without X keeping a reference to Calc, one would be in trouble: Calc would be completely unreferenced and thus GCable, and the chain in front of it would tumble down. That would probably be very hard to track down. One can argue that this would hold for ANY object that one just adds oneself as a reference to without holding a reference back to - it will be GC'ed. Problem is that one could quite possibly expect "an active object" that fires events and all, to obviously somehow be referenced somewhere! Which is the case - only it is through a WeakReference.
The discussion should probably revolve around what is the most common problem - what would benefit GL users the most.
One question I sit with, though, is how you guys handle the problem now? With such a chain of intermediate lists that you have not got a reference to (you only reference the tip)? Neither dispose() nor removeListEventListener() recurse.
That is my reason for needing to extend the classes - which I couldn't, so I had to copy them.
Have this ever been tried implemented?
(How I personally handle the problem now is by doing these "reimplementations" to implement recursion, AND having some shim "trackers" that I dish out instead of the actual list instance. These are referenced using WeakReferences that I register with a ReferenceQueue. When the ref-cleaner thread checks in on such a shim (because it was GCed), it checks if the "user" has disposed the list in question. If it hasn't, I do it then - and log a angry error log message, with an "InstantiationStackTrace" for easy debugging!)
(Personally, I currently lean towards that dispose() and/or removeListEventListener() should recurse. If one at a certain stage of the pipeline didn't want the recursion to happen, one could insert a DontRecurseDisposeList shim that .. did as its name implies)
Endre.
--------------------------------------------------------------------- To unsubscribe, e-mail: users-unsubscribe@... For additional commands, e-mail: users-help@... |
|
|
Re: re[4]: WeakReferenceProxyOn Sun, Sep 13, 2009 at 21:23, Kevin Day <kevin@...> wrote:
I found this hard to read - what do you mean by the first and second period? I've made two rounds of shims, where the first use finalize(), while the second use WeakReference
I will drop the first type, instead focusing on the latter. This because finalizer is very bad for GC - the References with ReferenceQueue gives pretty much the same behaviour, is more flexible, and doesn't force the GC to make two rounds for any object "implementing" it.
NB: Note that if you "loose" a Reference, that is, that the WeakReference itself isn't any longer referenced, it will not upon garbage collection be enqueued to the ReferenceQueue as one might expect. So, only when you actually get a "tearing" of the referent, that is, the referent is nulled WHILE the Reference itself still is referenced, will you get notified through the ReferenceQueue. If the Reference is not referenced anymore, and is GC'ed while the referent isn't, or both the Reference and the referent is taken out on the same GC cycle, no one will notice! This isn't necessarily that obvious when you work with them, so watch out - or you'll loose the entire point. Object.finalize() does NOT behave as such, as any object implementing finalize() will itself be notified about impending garbage collection.
What I do now, is that I just have a flag indicating whether dispose has been invoked, and if not, upon handling through the ReferenceQueue, I dispose it, and log a hefty warning along with the "Instantiation Stacktrace" which is stored on construction (typically if log.isDebugEnabled()). It is thus meant to catch errors, not as a fixer.
Currently, I believe the dispose() should be recursive. (I have re-implemented several EventLists to let dispose be recursive, as they as mentioned earlier are final and/or have private internals where I needed to override dispose functionality)
If one at some level doesn't want such recursion, one could wrap this with a DisposeDontRecurseList. I also think that it should be possible to register any WeakReferenceProxy with a ReferenceQueue. As I haven't thought of any better solutions, a suggestion is that GlazedLists has a static method whereby such a ReferenceQueue could be set statically. It could be held weakly.
PS: Nice to know that I am not totally alone with these problems, Kevin! :-) Kind regards, Endre. |
| Free embeddable forum powered by Nabble | Forum Help |