Moment of Weakness: Weak Event Listeners Can Be Dangerous

June 20, 2007 on 1:43 am | In Flex, Programming |

It’s been a funny week, in which I’ve run into two different killer bugs, both of which were caused by the same dangerous (but occasionally useful) programming practice: weak event listeners in ActionScript 3. I’m going to write a bit about this scenario, because it’s very hard to debug and it’s a very non-obvious mistake that I think many people can make. (Even the Flex team can run into this: one of the bugs I found was in the Flex 3 Beta framework, SDK-11389.) And, yes, this can occur in other languages: I’ve seen it in Java too.

Both bugs manifested themselves as an event which, unpredictably, would fail to be dispatched to a waiting listener. When I say “unpredictably”, I mean, about 1 out of 3 times, with no apparent rhyme or reason. It took a lot of troublesome debugging to reach the conclusion: “hey, there’s this object which I can see adding itself as a listener for event X, and then X gets dispatched and the object never gets notified.” It took a bit longer to reach the next conclusion: the object wasn’t getting notified because it didn’t even exist any more!

In both cases, the missing object was a weak event listener. Recap: when a listener object registers to receive an event from an EventDispatcher by calling addEventListener(), the dispatcher keeps track of that listener in an internal list. This bookkeeping happens in native code, down in the Flash Player. By default, this implies that the listeners will stick around in memory because they’re referenced by something (the dispatcher). However, since AS3 we’ve had the notion of weak listeners, in which the listener/dispatcher relationship can be optionally flagged as not pinning the listener in memory. In such situations, if no other object or thread in the program has a reference to the weak listener, it can be garbage collected, at which point it is no longer a listener (it’s not even an object at that point, of course).

That can be a handy trick, when 1) you aren’t sure how long a listener will be needed for, and 2) you know that some part of the program is going to make sure the listener sticks around long enough to receive the event, by referencing it and thus keeping it in memory. When the listener is no longer referenced, it goes away. Sounds great, eh? You can churn out these “disposable” objects that are listening for some event from a dispatcher, and as soon as no one cares about these objects, the listener will be unregistered from the dispatcher.

It’s that second point that’s the tricky part.

In both of the cases I ran into, the listeners in question were also event dispatchers — in pattern-speak, you might say they were mediators. So, here’s the scenario, or one version of it:

  • A calls some function on B to obtain an object that can dispatch events that are “about B”, or about some object that B manages.
  • B creates a “disposable mediator”, M, which is both a listener (to B) and a dispatcher (to the caller, in this case A).
  • M adds a callback function as a weak listener to B, so it can get events from B and dispatch other events in turn. Because it’s a weak listener, this does not pin M in memory.
  • B returns M as the result of the function called by A.
  • A adds a callback function of its own as a listener (of the regular kind) to M, then discards M. Because this only places A in the list of M’s listeners, this does not pin M in memory either! A is assuming that M has “a life of its own” — a fair assumption, given that M is capable of dispatching events at all.
  • M gets garbage collected (sometimes) before B ever dispatches the event. The event disappears in the ether. Somewhere, a developer gets very, very confused and wastes a lot of time.

You can see how pernicious this is. Weak event listeners are great if the client that obtains them can be expected to maintain a reference to them for as long as they need live. But weak event listeners that themselves dispatch events are very liable to be used only for the purpose of dispatching those very events — and as we’ve seen, this does not guarantee that they will survive GC for very long!

8 Comments »

RSS feed for comments on this post. TrackBack URI

  1. We are avid users of the MVCS pattern you described on the Adobe website and I swear this has happened to us on occasion. Seems that the operation classes in the pattern would be victim. Do you see any potential pitfalls in the MVCS pattern for this bug?

    Also, I was looking forward to your talk at Flex360 you going to be on the West Coast in the future? Thanks for the great tips and see ya at the next Flex event.

    -paul

    Comment by Paul Rangel — August 11, 2007 #

  2. Paul, I don’t see any problems off hand in the pattern. There is nothing in the pattern that says weak listeners need to be used, and an Operation is a disposable object, so attaching strong listeners to it should be safe (they can be harvested once the Operation itself is GCed). I try to avoid weak listeners anywhere unless there’s some ironclad argument for them.

    Did I miss something? Let me know if you see a specific case.

    As far as 360Flex in Seattle goes, my Allurent colleague Jim Echmalian (http://www.ech.net/blog/) will be speaking there about “Practical Patterns in Flex” which picks up where MVCS leaves off. I highly, highly recommend seeing his talk!

    –Joe

    Comment by joe — August 11, 2007 #

  3. Awesome and thanks again Joe. I owe you a pint at the next event!

    paul

    Comment by Paul Rangel — August 11, 2007 #

  4. [...] L’article sur le blog de Joe Berkovitz : Moment of Weakness: Weak Event Listeners Can Be Dangerous [...]

    Pingback by Weak References: Utilisation potentiellement dangereuse. | Matsiya — September 8, 2007 #

  5. > Weak event listeners are great if the client that obtains them can be expected to maintain a reference to them for as long as they need live.

    If you are not maintaining a reference to an object and thereby directly controlling how long it lives, then you are headed for one of two problems:

    1) If you use a weak listener reference, the object will be garbage collected at a random time, as you describe.

    2) If you DON’T use a weak listener reference, you’re setting yourself up for a memory leak. If the object falls out of scope and you no longer have a named reference to it, that means you have no way to release it to the garbage collector. There will be an unnamed reference to the object in a listeners array somewhere that will exist forever. That is, unless you have some scheme where the object removes itself as a listener. But if the object fails to do that, it will stick around.

    Comment by Robert Penner — November 21, 2007 #

  6. As Robert mentioned if you use strong references sometimes the object gets lost and you can never GC it. This happened to me running a flex app that dispatched many events as part of a streaming bus between a server and the app. As well as created and destroyed many widgets as part of a UI. The problems because the app needs to run for a long time. The strong references somehow prevented the widgets from GC’ing or barring that kept the even listeners around. The end result was a totally bogged down flash app that ran 100% faster once all the listeners were replaced with weak versions. That is all except those that would always need to be active.

    So in short this is a godsend and I don’t use strong references unless absolutely necessary. I can see how this will cause trouble in terms of the patterns described above but I think it worth mentioning that advising to use strong references whenever possible is not necessarily the right answer here.

    My understanding of AS3 is still limited but from what I can tell I would suggest using weak references whenever possible, and be aware of the pitfalls. The performance boost was amazing once I started using them.

    Comment by David Dumke — January 15, 2008 #

  7. Well, this continues to be a hot topic. I am presently tracking down a bunch of memory leaks in an app I wrote with the help of the Flex Builder Profiler, and many of them are amenable to being solved via weak references. But I also have run into a number of situations in which objects using the Mediator design pattern are prematurely GCed if all their listener references were weak. Obviously an object such as a Mediator (whose only role is to listen to events from other objects) is not going to stick around unless there is at least one strong ref. So I continue to maintain that weak refs, while an essential tool, are not a panacea!

    Comment by joe — January 15, 2008 #

  8. One other note: I continue to be careful with weak refs because problems with premature GCs show up so unreliably and unreproducibly that they are hard to catch in testing. My approach is to start with strong refs, and then weaken them until memory leaks disappear (hooray for the Profiler). A memory leak is deterministic: it’s bound to show up if you look for it. A premature GC might or might not happen.

    Comment by joe — January 15, 2008 #

Leave a comment

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Entries and comments feeds. Valid XHTML and CSS.
All content copyright (c) 2006-2007 Joseph Berkovitz. All Rights Reserved.