realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.45k stars 1.75k forks source link

Rare nullpointer in Realm "setConstructionFinished" - Working as intended after clean & rebuild #4372

Closed zoltish closed 7 years ago

zoltish commented 7 years ago

I debated whether Id post this, but since it does come up once every 1-2 days I do think its important enough to at least mention. Hope my information can help you track it down, I appologize on beforehand for the slim data I can provide.

Setup: RxJava2 Dagger2 Kotlin

Crash log: Attempt to invoke virtual method 'void io.realm.ProxyState.setConstructionFinished()' on a null object reference

Scenario: Usually when Im refactoring code that relates to how and what Dagger will provide. Ive never touched any of my Realm related code when this issue has come up.

Solution: Id do one clean & build, nothing. Restart Android Studio. Clean & build, and voila; it works.

Config: Android Studio 2.3 Realm 3.0.0 (Sync feature disabled) Kotlin 1.1.1 Dagger 2.1.0 RxJava 2.0.7 Tested on a Google Pixel, API 24

I dont really expect any miracle, seeings how rare it is and the slim data I can provide, but I hope it can provide some value to you in assisting with fixing it. Thanks for all your work, and let me know if theres anything else I can do to help with this.

karagraysen commented 7 years ago

Hi @zoltish. Thanks for reaching out. I'm not experienced in this area so I can't say if it will be but we appreciate you filing either way (if it's difficult to find/fix or easy to). I'll have someone on the Java team look at this and see what we can do to get it fixed. Or at the very least leave it open until we can. Thanks again for filing!

Zhuinden commented 7 years ago

Interesting, maybe @zaki50 knows.

zaki50 commented 7 years ago

@zoltish Are you using Instant Run?

zoltish commented 7 years ago

@zaki50 Nope, no instant run.

zoltish commented 7 years ago

Just ran into it again, heres a longer stacktrace!

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void io.realm.ProxyState.setConstructionFinished()' on a null object reference                                                     
   at io.realm.EquipmentRealmProxy.<init>(EquipmentRealmProxy.java:76)                                                     
   at io.realm.DefaultRealmModuleMediator.newInstance(DefaultRealmModuleMediator.java:416)                                                     
   at io.realm.BaseRealm.get(BaseRealm.java:488)                                                     
   at io.realm.OrderedRealmCollectionImpl$RealmCollectionIterator.convertRowToObject(OrderedRealmCollectionImpl.java:525)                                                     
   at io.realm.OrderedRealmCollectionImpl$RealmCollectionIterator.convertRowToObject(OrderedRealmCollectionImpl.java:518)                                                     
   at io.realm.internal.Collection$Iterator.get(Collection.java:163)                                                     
   at io.realm.internal.Collection$Iterator.next(Collection.java:130)                                                     
   at core.data.transformer.Transformer$listTransformer$1.apply(Transformer.kt:37)

This is all part of an Observable<EquipmentModel> and the crash happens in the next .map(transformer) call. I can run the code infinite amount of times and it will always crash, and as mentioned it will simply go away once Ive rebuilt the project once or twice.

cmelchior commented 7 years ago

@zoltish It could be connected to how your initialize your Realm models, especially what kind of work you do in your constructors.

Can you post your entire Equipment class?

Zhuinden commented 7 years ago

What we can see from the stack trace is that this error occurred in a constructor while iterating a managed collection with an Iterator

cmelchior commented 7 years ago

Yes, but you can see it fails in a newInstance() call, because it is trying to create a new proxy object. The initializers for the proxies are quite complex because they need to take into account multiple ways of setting "default" values in model classes.

Especially we still have this outstanding issue: https://github.com/realm/realm-java/pull/3812

zoltish commented 7 years ago

@cmelchior They are pretty plain and boring.

open class Equipment : EquipmentModel, RealmObject() {
    @PrimaryKey @EquipmentKey
    var id: String? = null
    var name: String? = null

    override fun _getId(): String = id!!
    override fun _getName(): String = name!!
}

The _methods are simply "proxy" getters so that my pure java module can access the model fields.

cmelchior commented 7 years ago

Interesting. Have you seen it on anything but the Pixel? From your description I would say it sounds like a Instant Run bug, but if you say you don't use it 🤔 . If cleaning works it could also be some bug in the Google gradle plugin, but it is hard to tell.

zoltish commented 7 years ago

@cmelchior Just for sanitys sake, I just checked and I am in fact, not using instant run 👍

I do most of my testing on a pixel, but Ill give another phone a go over the next couple of days just to see if it comes up on there too. Honestly I dont think it has much to do with the device itself though, but rather something with the annotation processing or something along those lines (or the gradle plugin, as you mention). The issue always goes away after a full clean, in which case all the generated code is recreated, and working again. Is the nullpointer above part of the code that gets generated? If so, I could also check the source when the issue comes up again to see if theres any difference in it after cleaning again.

cmelchior commented 7 years ago

Yes, it is coming from generated code. Our Bytecode transformer is injecting a call to realm$injectObjectContext() into all constructors: https://github.com/realm/realm-java/blob/master/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy#L88

You can see it setting the proxyState reference here: https://github.com/realm/realm-java/blob/master/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java#L117

The error you are seeing, indicate that the bytecode transformer wasn't applied correctly for some reason.

zoltish commented 7 years ago

@cmelchior Makes sense. The issue just came up again on a Nexus 6P, API 24 this morning. This time I did refactor a realm model class, after which Realm was looking for code that had changed (e.g. still referencing the old model class name and fields in a class thats no longer relevant) - I cant build the project at this point, so I clean it to delete and regen files, I have to do this a couple of times before it catches up and starts working again (ie above nullpointer stops being thrown).

Edit: Issue just came back in after "just" editing a layout xml file. Doesnt seem to matter what I change, as long as the project is being (re)compiled due to it. Seen it about 3-5 times in the past 2 hours now. Might try updating to the preview version of Android Studio (2.4) to see if its any better on there.

zoltish commented 7 years ago

Jumping back down to gradle 2.2.0 from 2.3.0 seems to have fixed the issue. Will report back if I see it again!

zoltish commented 7 years ago

Update: Happens on Gradle 2.2.0 as well :(

nhachicha commented 7 years ago

@zoltish just to make sure the the Bytecode transformer runs correctly, can you please, use Gradle with the --debug option?

we have a couple logging messages inside the transformer, that you can see/filter via the Gradle Console with the tag realm-logger

screen shot 2017-03-27 at 14 01 02

Note: you can change the Gradle logger level here

screen shot 2017-03-27 at 13 56 28
zoltish commented 7 years ago

@nhachicha Sorry, didnt see your post til now! It seems to be running, however I can see two things that may be misbehaving:

1) [DEBUG] [realm-logger] Proxy Mediator Classes: [] I feel like this should have a value? 2) Perhaps this is intended, but it seems that processing of all Dagger classes (and other annotation processing tasks in the project) are labeled under the realm-logger.

Heres a gist of the whole thing:
https://gist.github.com/anonymous/672b53e7ee6c88de97b481fa20ee6229

nhachicha commented 7 years ago

Hi @zoltish thanks for the log,

I'm curious about EquipmentRealmProxy being absent from the model https://gist.github.com/anonymous/672b53e7ee6c88de97b481fa20ee6229#file-realm-L3228

Model Classes: should list all the models defined in your app, the byte code transformer will then apply the different transformations to this model ex: add Realm accessor, and most importantly inject the object context, which is missing and causes the NPE in your case.

@zaki50 I think you're more familiar with this code path, could you pls have a look.

zaki50 commented 7 years ago

@zoltish I guess all model classes are in a library project and you only applied Realm plugin to the library project. Is that true?

zoltish commented 7 years ago

@zaki50 Theyre stored in an android library, but both the library and main android module have the realm plugin applied to them.

zaki50 commented 7 years ago

@zoltish In the library project, are you defining some modules which have library = true?

Something like:

@RealmModule(library = true, allClasses = true)
public class ZooAnimalsModule {
}
zoltish commented 7 years ago

@zaki50 Nope, just plain models extending RealmObject() and implementing interfaces. I dont think the interfaces play any role in this? They just define the variables so that my pure java module can use the models independantly from Android.

open class Equipment : EquipmentModel, RealmObject() {
    @PrimaryKey @EquipmentKey
    override var id: String = EMPTY

    @Required
    override var name: String = EMPTY
}
interface EquipmentModel {
    var id: String
    var name: String
}
zaki50 commented 7 years ago

I don't think any interfaces affect this issue.

zaki50 commented 7 years ago

Are you using Kotlin's incremental build? Could you try to set kotlin.incremental=false in your grade.properties?

Querschlag commented 7 years ago

Same issue. Realm 3.1.3, Kotlin project, Pixel as test device Clean + Rebuild resolved it

kneth commented 7 years ago

@zoltish Can you confirm that a clean and rebuild help?

zoltish commented 7 years ago

@kneth Yup, that does the trick! Might be worth noting that I havent seen this issue in a good while now (hopefully me saying this wont jinx it). Besides a lot of new code, Ive upgraded Android Studio and the gradle plugin - which goes inline with the earlier suspicion about this all being a bug triggered by the gradle plugin.

kneth commented 7 years ago

@zoltish Great to hear!

akent commented 7 years ago

To be clear, the problem is solved temporarily by a clean rebuild, but it comes right back again every time you change a RealmObject derived class.

Clean rebuild is not the solution; something is clearly stale and not being properly rebuilt automatically.

zoltish commented 7 years ago

@akent Yes; however the issue seems to come up "randomly" and not at all correlating with changes to RealmObjects.

akent commented 7 years ago

I can repro it every time with any change to the Chat.kt class in the demo repo linked in #4579. Let me turn on debug and see if I can diagnose further.

akent commented 7 years ago

Debug gradle log from realm-demo project from a clean state: https://gist.githubusercontent.com/akent/b5f5e1b8bc52a6948cbb0b78ce1a4438/raw/63aac0e2ad2006e78726c5337a76a93ec89e51e2/build-from-clean.log

Debug gradle log after a minor change to Chat.kt: https://gist.githubusercontent.com/akent/e3cb5181223f5ce5d2a4194c7106cc6a/raw/39008c8c96f7ed3e94f15f1311e80b69cbbe1a30/build-after-change.log

akent commented 7 years ago

Notable difference in the "after change" rebuild:

[DEBUG] [realm-logger] Proxy Mediator Classes: []
[DEBUG] [realm-logger] Model Classes: []
[DEBUG] [realm-logger] Managed Fields: []

vs clean:

[DEBUG] [realm-logger] Proxy Mediator Classes: [io.realm.DefaultRealmModuleMediator]
[DEBUG] [realm-logger]   Realm: Marking as transformed DefaultRealmModuleMediator
[DEBUG] [realm-logger] Model Classes: [com.ghostflying.realmdemo.Chat]
[DEBUG] [realm-logger] Managed Fields: [chatId, chatTitle]
akent commented 7 years ago

OK I did some more reading related to the problem and... drumroll

The answer (for me at least) is to include apply plugin: 'kotlin-kapt' in the module level build.gradle.

I think this has been an issue since Kotlin 1.1.1 because incremental builds were enabled by default.

itaispector commented 6 years ago

@akent I don't want to be the party pooper, but I've included apply plugin: 'kotlin-kapt', and it still happens...

Zhuinden commented 6 years ago

I've tried Realm with Kotlin and it worked just fine if Realm-android was applied after kotlin-kapt.

itaispector commented 6 years ago

@Zhuinden Indeed, I followed your answer on a different thread, cheers