Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EXC_BAD_ACCESS when nesting [Promise new:...] calls #77

Closed
Glavin001 opened this issue Aug 22, 2014 · 32 comments
Closed

EXC_BAD_ACCESS when nesting [Promise new:...] calls #77

Glavin001 opened this issue Aug 22, 2014 · 32 comments

Comments

@Glavin001
Copy link

Result:
image

I am wrapping the internals of many asynchronous method's with

    return [PMKPromise new:^(PMKPromiseFulfiller fulfiller, PMKPromiseRejecter rejecter) {

and am now finding that EXC_BAD_ACCSS issue, as mentioned on the website:

2. EXC_BAD_ACCESS
ARC is pretty good, but in some cases it is possible for your Promise chain to be partially deallocated. Usually when wrapping delegate systems. PromiseKit itself has macros that force additional retains and releases to avoid this.

I have been "debugging" this for hours, however I can find little helpful information or correlation except that it stops if I flatten the Promise chain a little and then occurs later on.

I am hoping that you may know what I am talking about / seen this before / have a solution. I have been up all night working on this and implemented PromiseKit everywhere and not am a little discouraged to see I have nothing to show for my nights work because of a strange error that I cannot debug.

Please let me know if there is anything I can do to help you diagnose. Thank you!

@mxcl
Copy link
Owner

mxcl commented Aug 22, 2014

Can you show the code?

Nesting news is fine, PromiseKit itself does this all over the place.

Usually something is being deallocated because you need to keep a strong pointer to that thing around, and if I could see the code I would be able to advise.

@Glavin001
Copy link
Author

Here is the project I am working on, specifically this file: https://github.com/Streamlyne/Cocoa-SDK/blob/master/Common/SLStore.m

@mxcl
Copy link
Owner

mxcl commented Aug 22, 2014

Thanks, I'll check it out.

@mxcl
Copy link
Owner

mxcl commented Aug 22, 2014

As an FYI, this:

- (PMKPromise *) find:(Class)modelClass withQuery:(NSDictionary *)query;
{
    return [PMKPromise new:^(PMKPromiseFulfiller fulfiller, PMKPromiseRejecter rejecter) {
        [self.adapter findQuery:modelClass withQuery:query withStore:self]
        .then(^(NSDictionary *adapterPayload) {
            dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
                //Background Thread
                // FIXME: Should used a shared Serializer, etc.
                SLSerializer *serializer = [[SLSerializer alloc] init];
                // Extract from Payload
                NSArray *extractedPayload = [serializer extractArray:modelClass withPayload:adapterPayload withStore:self];
                dispatch_async(dispatch_get_main_queue(), ^(void){
                    //Run UI Updates
                    [self pushMany:modelClass withData:extractedPayload]
                    .then(^(NSArray *records) {
                        fulfiller(records);
                    })
                    .catch(rejecter);
                });
            });
        })
        .catch(rejecter);
    }];
}

Could be simpler:

- (PMKPromise *) find:(Class)modelClass withQuery:(NSDictionary *)query;
{
    id q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    return [self.adapter findQuery:modelClass withQuery:query withStore:self].thenOn(q, ^(NSDictionary *adapterPayload) {
        SLSerializer *serializer = [[SLSerializer alloc] init];
        NSArray *extractedPayload = [serializer extractArray:modelClass withPayload:adapterPayload withStore:self];
        return [self pushMany:modelClass withData:extractedPayload];
    });
}

You don't have to do any GCD with promises, just specify what queue you want in the continuation handlers.

@Glavin001
Copy link
Author

Even more specifically, around this: https://github.com/Streamlyne/Cocoa-SDK/blob/master/Common/SLStore.m#L226-L252
which calls https://github.com/Streamlyne/Cocoa-SDK/blob/master/Common/SLStore.m#L67-L94 and https://github.com/Streamlyne/Cocoa-SDK/blob/master/Common/SLStore.m#L278-L348

I hope that helps! Looking at my commits, I had about a lull of 4 hours last night/this morning where I was receiving this error and could not figure it out. And something as strange as commenting out the contents of the then block would allow it to not error, however, of course the program would not pass the tests because it was missing steps.

@mxcl
Copy link
Owner

mxcl commented Aug 22, 2014

Also there's an AFNetworking+PromiseKit pod now: https://github.com/csotiriou/AFNetworking-PromiseKit in case that is useful.

@Glavin001
Copy link
Author

Oh wow, that's awesome!! Thank you for that optimization, @mxcl! I am still learning all of the exciting PromiseKit tricks and you've been a great help so far.

@mxcl
Copy link
Owner

mxcl commented Aug 22, 2014

This:

- (PMKPromise *) push:(Class)modelClass withData:(NSDictionary *)datum
{
    return [PMKPromise new:^(PMKPromiseFulfiller fulfiller, PMKPromiseRejecter rejecter) {

        NSLog(@"push: %@",datum);
        SLNid nid = (NSString *) datum[@"nid"];

        [PMKPromise when:@[
                           [self record:modelClass forId:nid],
                           [self normalizeRelationships:modelClass withData:datum withStore:self]
                           ]
         ]
        .then(^(NSArray *results) {
            SLModel *record = results[0];
            NSDictionary *newDatum = results[1];
            NSLog(@"record: %@", record);
            NSLog(@"post normalizeRelationships datum: %@", newDatum);

            //
            [record setupData:newDatum];
            NSLog(@"Pushed record: %@", record);

            fulfiller(record);

        })
        .catch(rejecter);
    }];
}

Could be:

- (PMKPromise *) push:(Class)modelClass withData:(NSDictionary *)datum
{
    NSLog(@"push: %@",datum);
    SLNid nid = (NSString *) datum[@"nid"];

    return [PMKPromise when:@[
                       [self record:modelClass forId:nid],
                       [self normalizeRelationships:modelClass withData:datum withStore:self]
                       ]
     ]
    .then(^(NSArray *results) {
        SLModel *record = results[0];
        NSDictionary *newDatum = results[1];
        NSLog(@"record: %@", record);
        NSLog(@"post normalizeRelationships datum: %@", newDatum);

        //
        [record setupData:newDatum];
        NSLog(@"Pushed record: %@", record);

        return record; 
    });
}

There's no reason to nest things that return promises in a +new:.

Obviously it shouldn’t crash either way.

@Glavin001
Copy link
Author

I had been trying to go through and clean up when I read that if you return a Promise it will resolve it, so I could omit the [PMKPromise new..] part. That's really great how condensed it really can go!

@mxcl
Copy link
Owner

mxcl commented Aug 22, 2014

A pleasure to help as it enables me to reconsider the documentation so people are guided towards the more condensed versions in future.

@mxcl
Copy link
Owner

mxcl commented Aug 22, 2014

I have trouble running the tests so I can reproduce, is there some step I missed?

screen shot 2014-08-22 at 10 43 05 am

@Glavin001
Copy link
Author

I think I may have committed a typo in my morning rush to my office. I'll let you know once I've fixed that error. Working only now.

@Glavin001
Copy link
Author

Hey @mxcl. I apologize for not getting back to you sooner: I ended up having other high priorities to do at work and was unavailable for the weekend.

Would you be able to try running the tests again and let me know how it goes and potentially what it shows in the console logs? I will be pushing to master branch with what I have been working on and appears to work for me, so you can successfully run tests such as testFindAllAttributes and others.

@mxcl
Copy link
Owner

mxcl commented Aug 25, 2014

k np,, I'll look into it later today.

@Glavin001
Copy link
Author

I have stepped through the code and found for case where it breaks the sig is returned as nil. So when the following line below is executed it results in EXC_BAD_ACCESS (code=2 address=0x0):

// sig = nil
const char rtype = sig.methodReturnType[0];

See https://github.com/mxcl/PromiseKit/blob/master/objc/PMKPromise.m#L42-L44

Important

I may have forgot to mention: I am seeing the error on the front-end application for iOS I am developing, that uses the Streamlyne Cocoa SDK that is public. However, the iOS app I am developing is closed source, so I am unable to show you the complete source code.
I am hoping that you recognize why the sig would be nil and I could look into it further on your behalf and get back to you with any relevant information that would help debugging my usage.

@Glavin001
Copy link
Author

On my iOS app I have debugged it down to the following snippet of code:

    NSLog(@"Attempt login");
    [client authenticateWithUserEmail:email
                              withPassword:password
                          withOrganization:organization]
    .then(^(SLClient *client, SLUser *me) {
        NSLog(@"Logged in");
        // Removed for brevity
    }).catch(^(NSError *error) {
     // Removed for brevity
   });

It successfully logs Attempt login and crashes before Logged in. See authenticateWithUserEmail:withPassword:withOrganization here.

Here is a sample of my console log (I redacted the unimportant information, just wanted to show you the starting message of those logs so you know where it is flowing):

2014-08-25 13:06:09:112 Streamlyne[58980:60b] Attempt login
2014-08-25 13:06:09.113 Streamlyne[58980:60b] absPath: /api/v1/me
2014-08-25 13:06:09.113 Streamlyne[58980:60b] Full path: http://54.183.15.175:5000/api/v1/me
2014-08-25 13:06:09.113 Streamlyne[58980:60b] Secret: REDACTED
2014-08-25 13:06:09.114 Streamlyne[58980:60b] theParams is empty.
2014-08-25 13:06:09.114 Streamlyne[58980:60b] HMAC message: GET:/api/v1/me:1408982829:
2014-08-25 13:06:09.114 Streamlyne[58980:60b] HMAC: REDACTED
2014-08-25 13:06:09.115 Streamlyne[58980:60b] GET http://54.183.15.175:5000/api/v1/me
2014-08-25 13:06:09.333 Streamlyne[58980:60b] Success, JSON: {
    // REDACTED
}
2014-08-25 13:06:15.710 Streamlyne[58980:60b] normalizeRelationships Payload: {
    // REDACTED
}
2014-08-25 13:06:16.235 Streamlyne[58980:60b] push: {
    // REDACTED
}
2014-08-25 13:06:16.251 Streamlyne[58980:1303] record:forId:, before find node
2014-08-25 13:06:16.251 Streamlyne[58980:60b] normalizeRelationships data: {
    // REDACTED
}
(lldb) 

I hope that helps! I believe I have used PromiseKit inappropriately in the Streamlyne Cocoa SDK and hopefully it is a simple fix / typo.

@Glavin001
Copy link
Author

I believe I have found the issue! I am calling [PMKPromise when:arrayOfPromises], and in certain cases the array is initialled but empty (length of 0).
Looking deeper into the PromiseKit source code I found that you have already covered this case in + (PMKPromise *)all:(id<NSFastEnumeration, NSObject>)promises: https://github.com/mxcl/PromiseKit/blob/master/objc/PMKPromise%2BWhen.m#L24-L25

    if (count == 0)
        return [PMKPromise promiseWithValue:@[]];

That leads me to https://github.com/mxcl/PromiseKit/blob/master/objc/PMKPromise.m#L324-L329

+ (PMKPromise *)promiseWithValue:(id)value {
    PMKPromise *p = [PMKPromise alloc];
    p->_promiseQueue = PMKCreatePromiseQueue();
    p->_result = value ?: PMKNull;
    return p;
}

And that is as far as I have gotten so far to finding a solution. Manually adding checks in my SDK code to prevent from using PMKPromise when: with an empty array has resolved those cases thus far, however I feel that PromiseKit should handle this empty array of promises case, and I believe you are in agreement, since you've put the check in for length of 0.

I am going to try and look into how the promiseWithValue: works, however I may need help at this point. Hope what I've found is helpful! I think we are getting close to a resolution.

@mxcl
Copy link
Owner

mxcl commented Aug 28, 2014

Thanks for the assistance! I will really look at this soon. I've had a bunch of urgent client work that cannot wait sadly.

@Glavin001
Copy link
Author

Not a problem. Let me know when you're working on it so I can help out :).

@dbachrach
Copy link
Contributor

See #36 for the addition to all: to support empty promises array.

Relevant checkin: cbcbd9b

@Glavin001
Copy link
Author

Thanks @dbachrach! I did find that commit and even the passing test; I was writing a comment here about my findings, but must have forgot to hit send.
So looks like there is still another issue at play.. 😦

@mxcl
Copy link
Owner

mxcl commented Aug 29, 2014

I still cannot reproduce with your project:

Test Case '-[Streamlyne_iOS_SDKTests testPushAssetWithRelationships]' started.
2014-08-29 10:18:51.758 xctest[15678:3231337] setUp
2014-08-29 10:18:51.760 xctest[15678:3231362] *** Assertion failure in +[NSManagedObjectContext MR_defaultContext], /Users/mxcl/scratch/Cocoa-SDK/Pods/MagicalRecord/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalRecord.m:60
2014-08-29 10:18:51.761 xctest[15678:3231362] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Default Context is nil! Did you forget to initialize the Core Data Stack?'

Is there some setup step I have missed?

@Glavin001
Copy link
Author

Shoot. Sorry about that. Looks like I could be accessing a Core Data context from the wrong thread. I went through the other day and optimized most of the methods with the PromiseKit optimizations you suggested. I may have wrote another typo.. I'll take another look. Thanks

@Glavin001
Copy link
Author

If I run all of the tests together, it errors like you have; however if I run them individually it appears to work (sometimes intermittently).
image

I will have to take a closer look later. Must be some form of side-effect between the tests..

@mxcl
Copy link
Owner

mxcl commented Aug 29, 2014

How am I meant to reproduce this issue? I thought running the test cases would do it, but you just showed that they all pass.

@Glavin001
Copy link
Author

I am not entirely sure. The tests pass for the SDK, however when used in the closed source iOS application that uses this SDK they fail with something as simple as authenticateWithUserEmail:withPassword:withOrganization.
I was hoping, after reading the comment

  1. EXC_BAD_ACCESS
    ARC is pretty good, but in some cases it is possible for your Promise chain to be partially deallocated. Usually when wrapping delegate systems. PromiseKit itself has macros that force additional retains and releases to avoid this.

that there may be something I should wrap my usage in to prevent from deallocating and causing the EXC_BAD_ACCESS errors.

I was also wondering if it could be an issue between the Mac and iOS version of PromiseKit. Since I am using the Mac version for the SDK tests and the iOS for the breaking app.

I have narrowed it down to a basic call to authenticateWithUserEmail:withPassword:withOrganization:organization,
see #77 (comment)

I'm not sure how else I can help. I wish there was some way I could distribute the reproducing App to you so you can experience what I am, however it's company IP.

I think I will try to build a basic iOS app as an open source project, just to demo a reproduction of the error I am experiencing. I hope that will help.

@Glavin001
Copy link
Author

@mxcl here is a demo app that successfully reproduces the same error I am having: https://github.com/Streamlyne/cocoa-sdk-iso-demo

Let me know if there is anything else I can do! Thank you! :)

@mxcl
Copy link
Owner

mxcl commented Aug 30, 2014

K, if I force an anti-ARC retain on the block the crash doesn't happen. So I'm actually thinking we are exposing some bug in ARC here.

@mxcl mxcl closed this as completed in c61c557 Aug 30, 2014
mxcl added a commit that referenced this issue Aug 30, 2014
This is worrying, but this copy fixes the crash.

I think we probably need to do more copies.

Fixes #77.
@mxcl
Copy link
Owner

mxcl commented Aug 30, 2014

0.9.16.3 pushed to CocoaPods.

@Glavin001
Copy link
Author

It worked! Thank you!

@mxcl
Copy link
Owner

mxcl commented Sep 4, 2014

np. I should report this bug to Apple really. I'm pretty sure ARC is prematurely releasing the block based on my reading the spec.

@mxcl mxcl mentioned this issue Sep 5, 2014
mxcl added a commit that referenced this issue Sep 5, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants