From: Benjamin J. <bhj...@gm...> - 2008-07-02 23:35:43
|
Hi all, I wanted to share my experience and see if anyone else has had the same issues as we have had with Blogo and memory management. In short: we make liberal use of Cocoa's asynchronous classes for IO, almost all of it using NSURLConnection. In all cases we hook our controllers into methods which accept a proc as a callback and call it when their delegate methods or notification listeners get called. Anyone who has followed Blogo's launch knows we've had more than our share of crashing bugs. Most of these incidents amounted to us receiving crash reports with segfaults and cryptic libruby- and AppKit-related stack traces. Some of them pointed us in the direction of the asynchronous callbacks, which led us to examine why GC was ripping up our objects before they were called. This in turn led us to the realization that Ruby GC really doesn't give a damn about Objective C's reference counter, which led to the realization that we couldn't just throw an object into a local variable and expect it to hang around waiting for the delegate methods to fire. Changing to use NSNotifications solved the crashes but was the equivalent of putting a band-aid on a broken leg: now we had to run timers to check for dropped notifications when GC acts up. Clearly not ideal. So there we thought we had it worked out. Until the crashes started reappearing in other areas of the app which used no local variables. While researching ruby GC today I had an idea. What if the objects are still there, but the procs being used for callbacks were getting swallowed by GC? Normally we call our helper classes as such: @helper = MyAwesomeAsyncHelper.alloc.init @helper.getSomething do |result| # do something interesting with the result... end MyAwesomeAsyncHelper then stores the lambda in an instance var to be called later: class MyAwesomeAsyncHelper < OSX::NSObject def getSomething &callback @myCallback = callback # do something asynchronous... end def connectionDidFinishLoading @myCallback.call(@resultData) end end By our logic, this should be sound. The reference is there. It's assigned to an instance var. For kicks I tried changing the helper classes to use Procs instead of lambdas: @helper = MyAwesomeAsyncHelper.alloc.init @helperCallback = Proc.new { |result| # do something interesting with the result... } @helper.getSomething(@helperCallback) And the helper (removed the & from the args list): class MyAwesomeAsyncHelper < OSX::NSObject def getSomething callback @myCallback = callback # do something asynchronous... end def connectionDidFinishLoading @myCallback.call(@resultData) end end Voila. No more dropped notifications. So here's what I think is happening: 1. Client sends lambda to helper. 2. Helper calls to_proc on lambda 3. Helper stores reference to the new Proc 4. Lambda has no references to it and is swept at the first opportunity Can anyone confirm or deny that this is the case? |
From: Benjamin J. <bhj...@gm...> - 2008-07-03 15:25:52
|
An update for anyone following along: The dropped notifications have re-appeared, though with less frequency. I'm not sure if this lambda thing was a real or imagined problem. Perhaps something else is causing it independently. On Wed, Jul 2, 2008 at 8:35 PM, Benjamin Jackson <bhj...@gm...> wrote: > Hi all, > > I wanted to share my experience and see if anyone else has had the > same issues as we have had with Blogo and memory management. > > In short: we make liberal use of Cocoa's asynchronous classes for IO, > almost all of it using NSURLConnection. In all cases we hook our > controllers into methods which accept a proc as a callback and call it > when their delegate methods or notification listeners get called. > > Anyone who has followed Blogo's launch knows we've had more than our > share of crashing bugs. Most of these incidents amounted to us > receiving crash reports with segfaults and cryptic libruby- and > AppKit-related stack traces. Some of them pointed us in the direction > of the asynchronous callbacks, which led us to examine why GC was > ripping up our objects before they were called. > > This in turn led us to the realization that Ruby GC really doesn't > give a damn about Objective C's reference counter, which led to the > realization that we couldn't just throw an object into a local > variable and expect it to hang around waiting for the delegate methods > to fire. Changing to use NSNotifications solved the crashes but was > the equivalent of putting a band-aid on a broken leg: now we had to > run timers to check for dropped notifications when GC acts up. Clearly > not ideal. > > So there we thought we had it worked out. Until the crashes started > reappearing in other areas of the app which used no local variables. > > While researching ruby GC today I had an idea. What if the objects are > still there, but the procs being used for callbacks were getting > swallowed by GC? > > Normally we call our helper classes as such: > > @helper = MyAwesomeAsyncHelper.alloc.init > @helper.getSomething do |result| > # do something interesting with the result... > end > > MyAwesomeAsyncHelper then stores the lambda in an instance var to be > called later: > > class MyAwesomeAsyncHelper < OSX::NSObject > def getSomething &callback > @myCallback = callback > # do something asynchronous... > end > > def connectionDidFinishLoading > @myCallback.call(@resultData) > end > end > > By our logic, this should be sound. The reference is there. It's > assigned to an instance var. > > For kicks I tried changing the helper classes to use Procs instead of lambdas: > > @helper = MyAwesomeAsyncHelper.alloc.init > @helperCallback = Proc.new { |result| # do something interesting with > the result... } > @helper.getSomething(@helperCallback) > > And the helper (removed the & from the args list): > > class MyAwesomeAsyncHelper < OSX::NSObject > def getSomething callback > @myCallback = callback > # do something asynchronous... > end > > def connectionDidFinishLoading > @myCallback.call(@resultData) > end > end > > Voila. No more dropped notifications. > > So here's what I think is happening: > > 1. Client sends lambda to helper. > 2. Helper calls to_proc on lambda > 3. Helper stores reference to the new Proc > 4. Lambda has no references to it and is swept at the first opportunity > > Can anyone confirm or deny that this is the case? > |