Strange Memory Patterns


Sandor Szatmari
 

We have an app that handles incoming server connections. For each connection it opens a window that shows the activity of the connection. When the client is done the window closes. When the activity happens while the app is in the background the application does not release memory and appears to leak. However, when I bring the app to the foreground the backlog of memory is released. It looks like auto release pools that aren’t popped until the app is foregrounded. I can track that down, but I thought I’d ask if anyone could identify this as a known issue before going down that path.

MacOS Sierra
Xcode 8.1
Build SDK 10.9
Deployment Target 10.6

Sandor


Alex Zavatone
 

What background options have you enabled in the app? I admit that I am unfamiliar with Mac policy on background apps and only ask with iOS background.

On Apr 24, 2019, at 9:41 PM, Sandor Szatmari <admin.szatmari.net@gmail.com> wrote:

We have an app that handles incoming server connections. For each connection it opens a window that shows the activity of the connection. When the client is done the window closes. When the activity happens while the app is in the background the application does not release memory and appears to leak. However, when I bring the app to the foreground the backlog of memory is released. It looks like auto release pools that aren’t popped until the app is foregrounded. I can track that down, but I thought I’d ask if anyone could identify this as a known issue before going down that path.

MacOS Sierra
Xcode 8.1
Build SDK 10.9
Deployment Target 10.6

Sandor


Sandor Szatmari
 

It is a standard GUI MacOS app. No special options. It runs in user space like any other user focused Mac app. To be clear, when I say background, I mean it’s menubar is not the menubar being shown in finder, ie. It’s not the ‘frontmost’ application. Or in code speak NSRunningApplication’s -active method would return no for this application. Clicking on the App’s dock icon to bring the app to the foreground, so it’s menubar is displayed in the finder, is what causes the memory to be released.

Sandor

On Apr 24, 2019, at 16:44, Alex Zavatone via Groups.Io <zav=mac.com@groups.io> wrote:

What background options have you enabled in the app? I admit that I am unfamiliar with Mac policy on background apps and only ask with iOS background.
On Apr 24, 2019, at 9:41 PM, Sandor Szatmari <admin.szatmari.net@gmail.com> wrote:

We have an app that handles incoming server connections. For each connection it opens a window that shows the activity of the connection. When the client is done the window closes. When the activity happens while the app is in the background the application does not release memory and appears to leak. However, when I bring the app to the foreground the backlog of memory is released. It looks like auto release pools that aren’t popped until the app is foregrounded. I can track that down, but I thought I’d ask if anyone could identify this as a known issue before going down that path.

MacOS Sierra
Xcode 8.1
Build SDK 10.9
Deployment Target 10.6

Sandor




Jonathan Taylor
 

This sounds very similar to problems I have encountered myself.

Issue 1: GCD callbacks are part of an autorelease pool, but you can't rely on it getting drained in a reliable manner, so wrap your callbacks with your own explicit pool.

Issue 2: Background apps do not seem to always have their main thread autorelease pools drained reliably. A snippet of code I've posted a few times over the years:

// Create a periodic timer that "tickles" the main event loop to drain autorelease pools.
// Response from cocoa-dev discussion was that:
//  This is a long-standing problem with AppKit. According to the documentation,
//  "The Application Kit creates an autorelease pool on the main thread at the
//  beginning of every cycle of the event loop, and drains it at the end, thereby
//  releasing any autoreleased objects generated while processing an event."
//  However, this is somewhat misleading. The "end" of the event loop cycle is
//   immediately before the beginning. Thus, for example, if your app is in the background
//   and not receiving events, then the autorelease pool will not be drained. That's why
//   your memory drops significantly when you click the mouse or switch applications.
[JDispatchTimer allocRepeatingTimerOnQueue:dispatch_get_main_queue() atInterval:5.0 withHandler:^{
NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:[NSDate timeIntervalSinceReferenceDatewindowNumber:0 context:nil subtype:0 data1:0 data2:0];
[NSApp postEvent:event atStart:YES];
}];
 
 


Alex Zavatone
 

Nice observation, Jonathan.  Do you suspect that throwing them on their own queue that you made would be an easy way to solve that?

I’d try it out myself, but am not in the best location to do that at the moment.  

Cheers.

Alex Zavatone

On Apr 25, 2019, at 10:44 AM, Jonathan Taylor <jonathan.taylor@...> wrote:

This sounds very similar to problems I have encountered myself.

Issue 1: GCD callbacks are part of an autorelease pool, but you can't rely on it getting drained in a reliable manner, so wrap your callbacks with your own explicit pool.

Issue 2: Background apps do not seem to always have their main thread autorelease pools drained reliably. A snippet of code I've posted a few times over the years:

// Create a periodic timer that "tickles" the main event loop to drain autorelease pools.
// Response from cocoa-dev discussion was that:
//  This is a long-standing problem with AppKit. According to the documentation,
//  "The Application Kit creates an autorelease pool on the main thread at the
//  beginning of every cycle of the event loop, and drains it at the end, thereby
//  releasing any autoreleased objects generated while processing an event."
//  However, this is somewhat misleading. The "end" of the event loop cycle is
//   immediately before the beginning. Thus, for example, if your app is in the background
//   and not receiving events, then the autorelease pool will not be drained. That's why
//   your memory drops significantly when you click the mouse or switch applications.
[JDispatchTimer allocRepeatingTimerOnQueue:dispatch_get_main_queue() atInterval:5.0 withHandler:^{
NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:[NSDate timeIntervalSinceReferenceDatewindowNumber:0 context:nil subtype:0 data1:0 data2:0];
[NSApp postEvent:event atStart:YES];
}];
 
 


Jonathan Taylor
 

> Do you suspect that throwing them on their own queue that you made would be an easy way to solve that?

In terms of my own issue, the work items I'm dealing with are GUI-related so their natural home is on the main queue. I don't know if it's possible to have a dedicated named queue that "lives" on the main thread, and whether that would solve anything here, but it's not something I've tried.


Sandor Szatmari
 

Jonathan,

Thanks for the solution of sending events to perturb the main runloop.  With a little googling, I adapted your solution as follows…

Cheers!

double delayInSeconds = 2.0;

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,

0,

0,

dispatch_get_main_queue());

void (^memorySentinel)(void) = ^()

{

NSLog( @"I'm firing!" );

NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined

location:NSZeroPoint

  modifierFlags:0

  timestamp:[NSDate timeIntervalSinceReferenceDate]

windowNumber:0

context:nil

subtype:0

  data1:0

  data2:0];

[NSApp postEvent:event

atStart:YES];

};

if (timer)

{

dispatch_source_set_timer(timer,

  dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC),

  delayInSeconds * NSEC_PER_SEC,

  (1ull * NSEC_PER_SEC) / 10);

dispatch_source_set_event_handler(timer, memorySentinel);

dispatch_resume(timer);

}

else

{

// tell someone?

}


Sandor

On Apr 25, 2019, at 04:44, Jonathan Taylor <jonathan.taylor@...> wrote:

This sounds very similar to problems I have encountered myself.

Issue 1: GCD callbacks are part of an autorelease pool, but you can't rely on it getting drained in a reliable manner, so wrap your callbacks with your own explicit pool.

Issue 2: Background apps do not seem to always have their main thread autorelease pools drained reliably. A snippet of code I've posted a few times over the years:

// Create a periodic timer that "tickles" the main event loop to drain autorelease pools.
// Response from cocoa-dev discussion was that:
//  This is a long-standing problem with AppKit. According to the documentation,
//  "The Application Kit creates an autorelease pool on the main thread at the
//  beginning of every cycle of the event loop, and drains it at the end, thereby
//  releasing any autoreleased objects generated while processing an event."
//  However, this is somewhat misleading. The "end" of the event loop cycle is
//   immediately before the beginning. Thus, for example, if your app is in the background
//   and not receiving events, then the autorelease pool will not be drained. That's why
//   your memory drops significantly when you click the mouse or switch applications.
[JDispatchTimer allocRepeatingTimerOnQueue:dispatch_get_main_queue() atInterval:5.0 withHandler:^{
NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:[NSDate timeIntervalSinceReferenceDatewindowNumber:0 context:nil subtype:0 data1:0 data2:0];
[NSApp postEvent:event atStart:YES];
}];