-
Notifications
You must be signed in to change notification settings - Fork 308
Description
Describe the bug
We have an app with around 50-60 instrumentation tests. Most of them use an underlying and temporary ObjectBox database. After a certain amount of tests in a row, we run reproducibly into a JNI Error. This error halts our test suite and therefore our whole CI pipeline. The error occurs on different tests but always after a certain number of instrumentation tests.
We assume that this JNI Error is caused by ObjectBox as we do not handle any native code and most of the classes from the log have something to do with this otherwise awesome library, e.g. entities or property converters. It seems that ObjectBox does hold some global references that are not being freed between tests.
We tried to setup/teardown the in-memory database before for every test and the whole test suite (via the CustomTestRunner down below), but this did not solve our problem.
Basic info:
- ObjectBox version: 3.1.2
- Android Gradle Plugin version: 7.1.3
- Other potentially relevant dependencies: Kotlin 1.6.21, Hilt 2.41, Jacoco 0.8.7
- Reproducibility: always after having executed a higher number of tests
- Device: Multiple emulators and devices, e.g. Pixel 6 Pro
- OS: Android 10, 11, 12, 13
To Reproduce
Steps to reproduce the behavior:
- Have an Android project with around 50-60 instrumentation tests that use ObjectBox under the hood
- Run every test, one after another
- A certain amount of tests runs succesfully
- After a certain amount one test fails with the error below
- Test suite stops and remaining tests will not be executed
Expected behavior
Less unique instances of ObjectBox-related classes and therefore no global reference table overflow anymore when running many instrumentation tests.
Code
// build.gradle
plugins {
id('com.android.application')
id('kotlin-android')
id('kotlin-kapt')
id("dagger.hilt.android.plugin")
id('de.mannodermaus.android-junit5')
}
android {
compileSdk 32
defaultConfig {
applicationId "de.thermomess.field"
minSdk 21
targetSdk 32
versionCode 137
versionName "5.0.0"
testInstrumentationRunner "de.custom.CustomTestRunner"
}
testOptions {
animationsDisabled = true
unitTests {
includeAndroidResources = true
all {
testLogging {
outputs.upToDateWhen { false }
events "passed", "failed", "standardError"
showCauses true
showExceptions true
}
}
}
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
}
[...]
}
// Test Runner which setups up and tears down in-memory database
class CustomTestRunner : AndroidJUnitRunner() {
// Would normally be injected via dependency injection but left here to simplify code example
var database: BoxStore? = null
override fun onCreate(arguments: Bundle?) {
super.onCreate(arguments)
val directory = checkNotNull(InstrumentationRegistry.getInstrumentation().targetContext.getExternalFilesDir("objectbox-test/test-db"))
database = MyObjectBox.builder().androidContext(InstrumentationRegistry.getInstrumentation().targetContext)
.directory(directory)
.maxSizeInKByte(5000)
.maxReaders(1)
.initialDbFile {
BufferedInputStream(InstrumentationRegistry.getInstrumentation().targetContext.resources.openRawResource(R.raw.data))
}
.debugFlags(DebugFlags.LOG_QUERIES or DebugFlags.LOG_QUERY_PARAMETERS)
.build()
}
override fun onDestroy() {
super.onDestroy()
database?.close()
database?.deleteAllFiles()
database = null
}
}
Logs, stack traces
JNI ERROR (app bug): global reference table overflow (max=51200)global reference table dump:
Last 10 entries (of 51200):
51199: 0x13193a88 java.lang.Class<j$.time.LocalDateTime>
51198: 0x131939c8 java.lang.Class<de.custom.LocalDateTimePropertyConverter>
51197: 0x1336f9f8 de.custom.LocalDateTimePropertyConverter
51196: 0x13193a88 java.lang.Class<j$.time.LocalDateTime>
51195: 0x131939c8 java.lang.Class<de.custom.LocalDateTimePropertyConverter>
51194: 0x13197e90 java.lang.Class<de.custom.SomeObjectBoxEntity>
51193: 0x1336f990 de.custom.SomeEnumPropertyConverter
51192: 0x13197d80 java.lang.Class<de.custom.SomeEnum>
51191: 0x131941c8 java.lang.Class<de.custom.SomeEnumPropertyConverter>
51190: 0x1336f988 de.custom.SomeEnumPropertyConverter
Summary:
34560 of java.lang.Class (450 unique instances)
7680 of de.custom.LocalDateTimePropertyConverter (7680 unique instances)
7478 of de.custom.SomeEnumPropertyConverter (7478 unique instances)
288 of java.nio.DirectByteBuffer (288 unique instances) 272 of io.objectbox.BoxStore (52 unique instances)
208 of de.custom.SomeOtherEnumPropertyConverter (208 unique instances)
208 of de.custom.SomeOtherOtherEnumPropertyConverter (208 unique instances)
105 of de.custom.SomeOtherOtherOtherEnumPropertyConverter (105 unique instances)
104 of de.custom.SomeOtherOtherOtherOtherEnumPropertyConverter (104 unique instances)
52 of de.custom.SomeOtherOtherOtherOtherOtherEnumPropertyConverter (52 unique instances)
52 of de.custom.SomeOtherOtherOtherOtherOtherOtherEnumPropertyConverter (52 unique instances)
52 of de.custom.SomeOtherOtherOtherOtherOtherOtherOtherEnumPropertyConverter (52 unique instances)
20 of android.app.assist.AssistStructure$SendChannel (20 unique instances)
17 of android.view.autofill.AutofillManager$AutofillManagerClient (17 unique instances)
15 of android.view.ViewRootImpl$W (15 unique instances) 15 of android.view.accessibility.AccessibilityManager$1 (15 unique instances)
Additional context
Our current workaround is to run our instrumentation tests in chunks, so that the JNI Error does not appear after a certain amount of tests.
Thanks in advance and best regards!