AWS SNS Unsubscribe loop is not releasing memory
Problem
When I unsubscribe to my endpoints subscriptions, instruments memory graph goes from 32MB to 300MB and app is killed Jetsam. It was my understanding that AWS BFTasks clean up after themselves… If I remove the do...while
and allow it to process the first 100 subscriptions, all works fine.
Process
I have about 8000 subscriptions in my development app that uses AWS SNS to push alerts to iOS devices and email. After the user selects the subscriptions they want, I remove their existing subscriptions, then subscribe them to the new ones. The [_awsSnsClient listSubscriptions:request]
returns a max of 100 subscriptions, I loop through these, performing [_awsSnsClient unsubscribe:unsubscribeRequest]
as needed. Then get the next 100...
Code
- (void) awsUsubscribeAllSubscriptions {
DLog(@"Removing existing subscriptions");
AWSSNSListSubscriptionsInput *request = [AWSSNSListSubscriptionsInput new];
__block Boolean sentLastUnsubscribe = NO; // YES when the last unsubscribe request was sent
__block int pendingUnsubscribe = 0; // Number of outsianding unsubscribes (async process not yet complete)
do {
[[[_awsSnsClient listSubscriptions:request] continueWithSuccessBlock:^id(BFTask *task) {
AWSSNSListSubscriptionsResponse *response = (AWSSNSListSubscriptionsResponse *)task.result;
NSString *nextToken = response.nextToken;
NSArray *subscriptions = response.subscriptions;
for (AWSSNSSubscription *subscription in subscriptions) {
if ([subscription.endpoint isEqualToString:_awsPlatformEndpoint.endpointArn]) {
AWSSNSUnsubscribeInput *unsubscribeRequest = [AWSSNSUnsubscribeInput new];
unsubscribeRequest.subscriptionArn = subscription.subscriptionArn;
pendingUnsubscribe++;
[[[_awsSnsClient unsubscribe:unsubscribeRequest] continueWithSuccessBlock:^id(BFTask *task) {
DLog(@"Unsubscribed from:%@",subscription.subscriptionArn);
return nil;
}] continueWithBlock:^id(BFTask *task) {
pendingUnsubscribe--;
if (task.error) {
// failed with error
ALog(@"Error unsubscribing to: %@, %@, %@", subscription, [task.error localizedDescription], [task.error localizedFailureReason]);
}
// If we have processed the last unsubscribe, then create topics and subscribe
if (sentLastUnsubscribe && (pendingUnsubscribe <= 0)) {
[self awsCreateTopicsAndSubscriptions];
}
return nil;
}];
}
}
request.nextToken = nextToken;
if(!nextToken) sentLastUnsubscribe = YES; // Reached the end of the SNS subscriptions
return nil;
}] continueWithBlock:^id(BFTask *task) {
if (task.error) {
// failed with error
ALog(@"Error listing subscriptions: %@, %@", [task.error localizedDescription], [task.error localizedFailureReason]);
}
return nil;
}];
} while (!sentLastUnsubscribe);
}
Is there a better way to remove all SNS subscriptions from an endpoint? Is there a way for force release any retained data during this loop?
Solution
Using both recursion and - waitUntilFinished
. Memory load is now much lower.
-(void) awsUnsubscribeFromAllSubscriptionsWithNextToken:(NSString *)nextToken AndSubscribe:(BOOL)subscribe {
AWSSNSListSubscriptionsInput *request = [AWSSNSListSubscriptionsInput new];
request.nextToken = nextToken;
[[[_awsSnsClient listSubscriptions:request] continueWithSuccessBlock:^id(BFTask *task) {
AWSSNSListSubscriptionsResponse *response = (AWSSNSListSubscriptionsResponse *)task.result;
NSArray *subscriptions = response.subscriptions;
for (AWSSNSSubscription *subscription in subscriptions) {
if ([subscription.endpoint isEqualToString:_awsPlatformEndpoint.endpointArn]) {
DLog(@"Unsubscribe from %@", subscription.topicArn);
AWSSNSUnsubscribeInput *unsubscribeRequest = [AWSSNSUnsubscribeInput new];
unsubscribeRequest.subscriptionArn = subscription.subscriptionArn;
[[_awsSnsClient unsubscribe:unsubscribeRequest] waitUntilFinished];
}
}
if(response.nextToken) {
[self awsUnsubscribeFromAllSubscriptionsWithNextToken:response.nextToken AndSubscribe:subscribe];
} else if (subscribe) {
[self awsCreateTopicsAndSubscriptions];
}
return nil;
}] continueWithBlock:^id(BFTask *task) {
if (task.error) {
// failed with error
ALog(@"Error listing subscriptions: %@, %@", [task.error localizedDescription], [task.error localizedFailureReason]);
}
return nil;
}];
}
Please note that - listSubscriptions:
is an asynchronous method and returns immediately. You are calling an async method in a for loop. It means you are potentially calling - listSubscriptions:
hundreds or even thousands of times in a short period.
The easiest thing you can do to fix it is to call - waitUntilFinished
at the end of the async block. It makes the entire method synchronous
However, in general, you should avoid calling - waitUntilFinished
as much as possible. AWSKinesisRecorderTests.m
has a function to call - getRecords:
recursively. You can adopt a similar pattern.
上一篇: iOS GCM。 重新安装应用程序后收到来自旧安装的通知
下一篇: AWS SNS取消订阅循环未释放内存