Parse-SDK-iOS-OSX icon indicating copy to clipboard operation
Parse-SDK-iOS-OSX copied to clipboard

objectId should be non nil

Open carbonimax opened this issue 4 years ago • 21 comments

Hi,

Since a few weeks, at app launch, for some users (±0.1%), the app crashes on PFRESTObjectCommand.m line 21 with this error:

Fatal Exception: NSInvalidArgumentException
objectId should be non nil

At launch, we sync the current PFUser and we call PFUser.enableAutomaticUser(). I don't know which object could have a nil objectId. If I add some debug, currentUser.objectId and currentUser.session are OK.

PFObject._validateFetchAsync is called just before Just before PFRestObjectCommand.fetchObjectCommandForObjectState:withSessionToken and it doesn't catch some invalid objectId.

A re-installation of the app correct the problem.

Do you have any idea where this might be coming from?

carbonimax avatar Jul 09 '20 12:07 carbonimax

trace

objectId should be non nil
Fatal Exception: NSInvalidArgumentException
0  CoreFoundation                 0x1a3488300 __exceptionPreprocess
1  libobjc.A.dylib                0x1a319cc1c objc_exception_throw
2  CoreFoundation                 0x1a3377e68 -[NSCache init]
3  Parse                          0x101423cc8 +[PFRESTObjectCommand fetchObjectCommandForObjectState:withSessionToken:] + 21 (PFRESTObjectCommand.m:21)
4  Parse                          0x1013eff8c __56-[PFObjectController fetchObjectAsync:withSessionToken:]_block_invoke.10 + 55 (PFObjectController.m:55)
5  Bolts                          0x100ea1f94 __62-[BFTask continueWithExecutor:successBlock:cancellationToken:]_block_invoke + 398 (BFTask.m:398)
6  Bolts                          0x100ea198c __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke + 331 (BFTask.m:331)
7  Bolts                          0x100e9f5ac __29+[BFExecutor defaultExecutor]_block_invoke_2 + 66 (BFExecutor.m:66)
8  Bolts                          0x100e9fad8 -[BFExecutor execute:] + 131 (BFExecutor.m:131)
9  Bolts                          0x100ea158c -[BFTask runContinuations] + 308 (BFTask.m:308)
10 Bolts                          0x100ea10f4 -[BFTask trySetResult:] + 247 (BFTask.m:247)
11 Bolts                          0x100ea25bc -[BFTaskCompletionSource setResult:] + 45 (BFTaskCompletionSource.m:45)
12 Bolts                          0x100ea1b90 __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke_2 + 340 (BFTask.m:340)
13 Bolts                          0x100ea1a40 __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke + 348 (BFTask.m:348)
14 libdispatch.dylib              0x1a3126ec4 _dispatch_call_block_and_release
15 libdispatch.dylib              0x1a312833c _dispatch_client_callout
16 libdispatch.dylib              0x1a312a7b4 _dispatch_queue_override_invoke
17 libdispatch.dylib              0x1a31375c0 _dispatch_root_queue_drain
18 libdispatch.dylib              0x1a3137d9c _dispatch_worker_thread2
19 libsystem_pthread.dylib        0x1a318f6d8 _pthread_wqthread
20 libsystem_pthread.dylib        0x1a31959c8 start_wqthread

carbonimax avatar Jul 09 '20 12:07 carbonimax

@TomWFox @mtrezza have you already seen something like this? Why is there even a [BFTask continueWithExecutor before the _validateFetchAsync test whereas it's a sync test (just a if)? Can't we removed that to avoid any potential race condition issue? BTW, the validation test the existence of the objectId whereas the assert test the length of that objectId, so that a weird "" objectId could pass the first test, but fail on the assert (I don't know how we could have a "" objectId of course)

SebC99 avatar Jul 09 '20 14:07 SebC99

If I add some debug, currentUser.objectId and currentUser.session are OK.

Where are they 'okay'? Are they are non-nil when the check claims they're nil?

drdaz avatar Jul 09 '20 16:07 drdaz

In application:didFinishLaunchingWithOptions, after Parse init, objectIds are never nil and sessions are valid (except for new users but it is normal and it doesn't crash). In all debug prints in app, they are never nil and always valid before the crash, from what I can see.

carbonimax avatar Jul 09 '20 19:07 carbonimax

@carbonimax That sounds pretty strange. The only times I recall seeing similar behaviour (and not in Parse) involved threading issues. Where values were being set on one thread and tested in another.

I'm not sure that's what you're seeing here (I recall PFObjects being thread-safe in fact), but it's the first thing that jumps to mind.

drdaz avatar Jul 10 '20 07:07 drdaz

Since a few weeks, at app launch, for some users (±0.1%), the app crashes on PFRESTObjectCommand.m line 21

Does the date coincide with an app update / release date (mind the rollout duration)? Then again, "some users (±0.1%)" doesn't sound like a significant volume :)

have you already seen something like this?

I have seen a similar issue regarding PFUser.currentUser() on iOS, I assumed a threading issue. That is what I would be looking at here at first. For example when the current user is logged in or out, while there are requests being made on different threads.

mtrezza avatar Jul 10 '20 10:07 mtrezza

Does someone have any idea of the purpose of the first BFTask in:

- (BFTask *)fetchObjectAsync:(PFObject *)object withSessionToken:(NSString *)sessionToken {
    @weakify(self);
    return [[[[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{
        return [object _validateFetchAsync];
    }] continueWithSuccessBlock:^id(BFTask *task) {
        @strongify(self);
        PFRESTCommand *command = [PFRESTObjectCommand fetchObjectCommandForObjectState:[object._state copy]
                                                                      withSessionToken:sessionToken];
        return [self _runFetchCommand:command forObject:object];
    }] continueWithSuccessBlock:^id(BFTask *task) {
        @strongify(self);
        PFCommandResult *result = task.result;
        return [self processFetchResultAsync:result.result forObject:object];
    }] continueWithSuccessResult:object];
}

The call to [object _validateFetchAsync] is a simple (sync) test on !self._state.objectId so I don't see why there's a BFTask involved. Even the change of queue is unnecessary as it will be done in the [self _runFetchCommand:command forObject:object]; call.

SebC99 avatar Jul 10 '20 14:07 SebC99

Does someone have any idea of the purpose of the first BFTask in:

- (BFTask *)fetchObjectAsync:(PFObject *)object withSessionToken:(NSString *)sessionToken {
    @weakify(self);
    return [[[[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{
        return [object _validateFetchAsync];
    }] continueWithSuccessBlock:^id(BFTask *task) {
        @strongify(self);
        PFRESTCommand *command = [PFRESTObjectCommand fetchObjectCommandForObjectState:[object._state copy]
                                                                      withSessionToken:sessionToken];
        return [self _runFetchCommand:command forObject:object];
    }] continueWithSuccessBlock:^id(BFTask *task) {
        @strongify(self);
        PFCommandResult *result = task.result;
        return [self processFetchResultAsync:result.result forObject:object];
    }] continueWithSuccessResult:object];
}

The call to [object _validateFetchAsync] is a simple (sync) test on !self._state.objectId so I don't see why there's a BFTask involved. Even the change of queue is unnecessary as it will be done in the [self _runFetchCommand:command forObject:object]; call.

Maybe consistency? The SDK is big on BFTask. Or maybe there are historical reasons it's like that?

But I agree it seems like less fancy could do the same job.

drdaz avatar Jul 10 '20 18:07 drdaz

This issue has been automatically marked as stale because it has not had recent activity. If you believe it should stay open, please let us know! As always, we encourage contributions, check out the Contributing Guide

stale[bot] avatar Aug 29 '20 10:08 stale[bot]

I'm closing this as it seems to be resolved. Feel free to comment if you have any questions and we can re-open this issue.

mtrezza avatar Sep 05 '20 19:09 mtrezza

Unfortunately it's not...

Fatal Exception: NSInvalidArgumentException
objectId should be non nil

Fatal Exception: NSInvalidArgumentException
0  CoreFoundation                 0x182676d8c __exceptionPreprocess
1  libobjc.A.dylib                0x1818305ec objc_exception_throw
2  CoreFoundation                 0x182676c6c -[NSException initWithCoder:]
3  Parse                          0x101053cc8 +[PFRESTObjectCommand fetchObjectCommandForObjectState:withSessionToken:] + 21 (PFRESTObjectCommand.m:21)
4  Parse                          0x10101ff8c __56-[PFObjectController fetchObjectAsync:withSessionToken:]_block_invoke.10 + 55 (PFObjectController.m:55)
5  Bolts                          0x100b71f94 __62-[BFTask continueWithExecutor:successBlock:cancellationToken:]_block_invoke + 398 (BFTask.m:398)
6  Bolts                          0x100b7198c __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke + 331 (BFTask.m:331)
7  Bolts                          0x100b6f5ac __29+[BFExecutor defaultExecutor]_block_invoke_2 + 66 (BFExecutor.m:66)
8  Bolts                          0x100b6fad8 -[BFExecutor execute:] + 131 (BFExecutor.m:131)
9  Bolts                          0x100b7158c -[BFTask runContinuations] + 308 (BFTask.m:308)
10 Bolts                          0x100b710f4 -[BFTask trySetResult:] + 247 (BFTask.m:247)
11 Bolts                          0x100b725bc -[BFTaskCompletionSource setResult:] + 45 (BFTaskCompletionSource.m:45)
12 Bolts                          0x100b71b90 __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke_2 + 340 (BFTask.m:340)
13 Bolts                          0x100b71a40 __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke + 348 (BFTask.m:348)
14 libdispatch.dylib              0x181f68aa0 _dispatch_call_block_and_release
15 libdispatch.dylib              0x181f68a60 _dispatch_client_callout
16 libdispatch.dylib              0x181f6fb84 _dispatch_queue_override_invoke$VARIANT$mp
17 libdispatch.dylib              0x181f75cac _dispatch_root_queue_drain
18 libdispatch.dylib              0x181f759fc _dispatch_worker_thread3
19 libsystem_pthread.dylib        0x18229bfac _pthread_wqthread
20 libsystem_pthread.dylib        0x18229bb08 start_wqthread

SebC99 avatar Sep 09 '20 08:09 SebC99

@SebC99 Could this be a threading issue in your code, as mentioned above? However, I think regardless of that, the SDK should not crash but the operation should fail with some error.

mtrezza avatar Sep 09 '20 09:09 mtrezza

Hard to tell, but it doesn't seem so (@carbonimax ?) In any case, Parse SDK shouldn't throw an assert just after supposedly validating the value one line before ;)

SebC99 avatar Sep 09 '20 09:09 SebC99

@mtrezza Apparently there's no thread issue But we do log 0 length objectId values for those users (objectId: "") which seems very weird.

And as the SDK Code test for nil objectId only (here) it tries to start the RESTObjectCommand where the assert tests for 0 length objectId (here) Which then crash...

Any idea why the SDK would create "" values for objectId? Of course we don't have this kind of objectId in DB ;)

SebC99 avatar Sep 26 '20 11:09 SebC99

PS: we use the enableAutomaticUser setting to allow anonymous parse users

SebC99 avatar Sep 26 '20 12:09 SebC99

we do log 0 length objectId values

To be exact, are you logging zero length String values?

Can you go up the chain and log where these zero length String values occur? Maybe fork the SDK and implement a log in the getter / setter method of objectId and log the stack trace as well?

mtrezza avatar Sep 26 '20 13:09 mtrezza

Yes string values being "" (length = 0)

Not really easy to do so in production (we can't reproduce the bug, but have thousands of users with it). We can try to find a way... but between the state.objectId, the localId, the self.objectId, and the method to create an object with an objectId, it seems there's a lot of way to set an objectId. We would need to implement a new setter to catch all in pfobject and in the state objects?

SebC99 avatar Sep 26 '20 20:09 SebC99

So after a lot of logging and testing, it appears the bug is for the _Installation class. For unknown reason, the PFInstallation persistent file doesn't include the objectId of that installation, which causes the fetch to fail.

Capture d’écran 2020-10-22 à 17 12 48

If we try to detect it and save the PFInstallation.current() instead of fetching it first, it kinda works... as it doesn't crash and succeeds to save on the server (even without the objectId, so I guess it uses the installation to find the right _Installation object). But even if the save on the server succeeds, the objectId is not set on the local PFInstallation object, and is still not saved on disk. So it will start again at the next launch and will never allow any fetch...

Any idea how we could set the objectId again? or why the save result objectId is not merged with the local object?

SebC99 avatar Oct 22 '20 21:10 SebC99

This issue has been automatically marked as stale because it has not had recent activity. If you believe it should stay open, please let us know! As always, we encourage contributions, check out the Contributing Guide

stale[bot] avatar Dec 12 '20 20:12 stale[bot]

Reopening, issue not resolved.

mtrezza avatar Dec 21 '20 12:12 mtrezza

This issue has been automatically marked as stale because it has not had recent activity. If you believe it should stay open, please let us know! As always, we encourage contributions, check out the Contributing Guide

stale[bot] avatar Jul 21 '21 01:07 stale[bot]