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

SQLite error when running tests #1986

Closed
pdegand opened this issue Aug 12, 2015 · 37 comments · Fixed by #3211
Closed

SQLite error when running tests #1986

pdegand opened this issue Aug 12, 2015 · 37 comments · Fixed by #3211

Comments

@pdegand
Copy link

pdegand commented Aug 12, 2015

Hello, I have troubles running my tests suite on Ubuntu (everything is running fine on my OSX environment).

When i execute my full tests suites, the first tests are doing good, but they start to randomly crash with the following error (see below).

Any hint on where this could come from ? Why is it happening only on Ubuntu and not on OSX ?

Thanks

Robolectric 3.0
Shadow support v4 3.0
Gradle Wrapper 2.6
android-gradle 1.3.0

java.lang.RuntimeException: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.AssertionError: DB[46] will not dispose from a non-confining thread
    at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:244)
    at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188)
    at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at   org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:105)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:56)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:64)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:50)
    at sun.reflect.GeneratedMethodAccessor158.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:106)
    at sun.reflect.GeneratedMethodAccessor157.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:360)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
    at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.AssertionError: DB[46] will not dispose from a non-confining thread
    at org.robolectric.shadows.ShadowSQLiteConnection$Connections.execute(ShadowSQLiteConnection.java:477)
    at org.robolectric.shadows.ShadowSQLiteConnection$Connections.close(ShadowSQLiteConnection.java:390)
    at org.robolectric.shadows.ShadowSQLiteConnection$Connections.reset(ShadowSQLiteConnection.java:402)
    at org.robolectric.shadows.ShadowSQLiteConnection.reset(ShadowSQLiteConnection.java:80)
    at org.robolectric.Shadows.reset(Shadows.java:1626)
    at org.robolectric.Robolectric.reset(Robolectric.java:22)
    at org.robolectric.internal.ParallelUniverse.resetStaticState(ParallelUniverse.java:43)
    at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:233)
    ... 33 more
Caused by: java.util.concurrent.ExecutionException: java.lang.AssertionError: DB[46] will not dispose from a non-confining thread
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:188)
    at org.robolectric.shadows.ShadowSQLiteConnection$Connections.execute(ShadowSQLiteConnection.java:462)
    ... 40 more
Caused by: java.lang.AssertionError: DB[46] will not dispose from a non-confining thread
    at com.almworks.sqlite4java.Internal.recoverableError(Internal.java:46)
    at com.almworks.sqlite4java.SQLiteConnection.dispose(SQLiteConnection.java:363)
    at org.robolectric.shadows.ShadowSQLiteConnection$Connections$3.call(ShadowSQLiteConnection.java:394)
    at org.robolectric.shadows.ShadowSQLiteConnection$Connections$6.call(ShadowSQLiteConnection.java:452)
    at org.robolectric.shadows.ShadowSQLiteConnection$Connections$6.call(ShadowSQLiteConnection.java:446)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    ... 3 more
@hidroh
Copy link
Contributor

hidroh commented Aug 13, 2015

We have been encountering this a lot as well, since upgrading to Robolectric 3.0. Seems to happen on both OSX and Ubuntu, but a lot more on Ubuntu, which powers our CI servers.

@pdegand
Copy link
Author

pdegand commented Aug 13, 2015

Did you find a workaround ?

I found myself disabling most of the unit tests build on my CI ...

@hidroh
Copy link
Contributor

hidroh commented Aug 13, 2015

Nope, on my lucky day it would be smooth, but sometimes it takes 5-10 retries to make a successful build.

@hidroh
Copy link
Contributor

hidroh commented Aug 13, 2015

Also my setup tests against API 18. Pointing it against 19 or 21 seems to resolve the problem, but we got hit with out of memory issues (which is even worse as it just hangs the build), so we decide to just test against 18.

@pdegand
Copy link
Author

pdegand commented Aug 13, 2015

I am having the SQLite error on API 21 tests

Le jeu. 13 août 2015 12:22, Ha Duy Trung notifications@github.com a
écrit :

Also my setup tests against API 18. Pointing it against 19 or 21 seems to
resolve the problem, but we got hit with out of memory issues (which is
even worse as it just hangs the build), so we decide to just test against
18.


Reply to this email directly or view it on GitHub
#1986 (comment)
.

Pierre Degand

@jongerrish
Copy link
Contributor

Are you leaking resources between tests?
On Aug 13, 2015 3:49 AM, "Pierre Degand" notifications@github.com wrote:

I am having thé SQLite error on API 21 tests

Le jeu. 13 août 2015 12:22, Ha Duy Trung notifications@github.com a
écrit :

Also my setup tests against API 18. Pointing it against 19 or 21 seems
to
resolve the problem, but we got hit with out of memory issues (which is
even worse as it just hangs the build), so we decide to just test
against
18.


Reply to this email directly or view it on GitHub
<
#1986 (comment)

.

Pierre Degand


Reply to this email directly or view it on GitHub.

@hidroh
Copy link
Contributor

hidroh commented Aug 14, 2015

@jongerrish I suspect that would the case. But the problem only starts to surface since Robolectric 3.0, and unfortunately I couldn't pinpoint the culprit(s). I wish there was a guidelines on cleaning up test resources to avoid/mitigate leaks.

For the SQLite problem, I notice this change is where the exception starts to happen.

@ndahlquist
Copy link
Contributor

This happened to us too. I don't have time to investigate thoroughly, but here is my quick hack of a workaround:

@@ -399,7 +399,12 @@ public class ShadowSQLiteConnection {
         @Override
         public Object call() throws Exception {
           SQLiteConnection connection = getConnection(ptr);
-          connection.dispose();
+          try {
+            connection.dispose();
+          } catch (java.lang.AssertionError e) { // DB[109] will not dispose from a non-confining thread
+            // Hacky patch for https://github.com/robolectric/robolectric/issues/1986.
+            e.printStackTrace();
+          }
           return null;
         }
       });

(Yup, that's some professional-grade code right there...)


I suspect that the issue may be a race condition in the setup / teardown of dbExecutor (doesn't look particularly thread-safe to me, but I haven't identified a specific case that would break it).

@hidroh
Copy link
Contributor

hidroh commented Sep 3, 2015

Hi any update/confirmation on this issue? It's getting worse for us now once we upgrade to build tool 1.3.1. Still seems to happening only in Ubuntu.

@supoved
Copy link

supoved commented Nov 24, 2015

Just updated from robolectric 2.4 to 3.0. And it works without any issue on my dev windows machine, but Ubuntu build machine now dies with out of memory exception during execution of tests.
I tried to increase MaxPermSize to 1024m from 512m (which used to help with out of memory on 2.4 version), but with no result.
Any solutions?

@sethgho
Copy link

sethgho commented Dec 1, 2015

+1. This has dramatically ramped up our test flakiness since recently introducing SQLite into our app.

Are you leaking resources between tests?

I think the static ShadowSQLiteConnection.CONNECTIONS pool is resource that is leaking between tests. I've got some very basic unit tests failing with that stack trace that have nothing to do with SQLite.

@lordcodes
Copy link
Contributor

Is there any ideas on this? This error is making testing very difficult for us. It happens on some machines and not on others.

@tmack8001
Copy link

I'm having the same issue as others since I'm migrating my application over to be built on Ubuntu based servers for CI builds, attempting to build on Centos/Redhat the same doesn't happen. Also building on Mac OS X (yosemite) isn't a problem either just Ubuntu so guessing something low level is the culprit. Anyone find a workaround to this issue or a way to find the cause?

@aromeroavila
Copy link

Same here. The same problem occurs intermittently on CI (Windows machine) but never on dev machines (Mac).

No workaround or fix found for it.

@tmack8001
Copy link

I added the following for all my unit tests and got around the SQLite error for Centos7

@After
public void tearDown() throws Exception {
    ShadowSQLiteConnection.reset();
}

Since the connections are the problem this clears / resets the SQLite connections after each and every test. This worked for my use cases and the errors I was seeing.

UPDATE: well it worked briefly... or it was just a random build that didn't run into this race condition

@lordcodes
Copy link
Contributor

ShadowSQLiteConnection.reset() is called by Robolectric after each test already.

Also, the error that is occurring happens when ShadowSQLiteConnection.reset() is called, so calling it again won't fix it. The error occurs when ShadowSQLiteConnection tries to close all of its connections.

@tmack8001
Copy link

Any idea what might be happening here and a solution to this issue?

@vvsevolodovich
Copy link

Hello! Any updates here? This issue is really annoying and as long builds are getting longer to complete the more terrific is getting exception due to the problem in robolectric.

@aromeroavila
Copy link

Running the tests on a higher spec machine hid the problem for me.

@garrickjubiao
Copy link

Everyone has the best solution about this problem ?

@realumy
Copy link

realumy commented Nov 8, 2016

Hello! Any updates here ? I got the same problem on robolectric tests with the following exceptions :

java.lang.RuntimeException
Caused by: java.lang.RuntimeException
Caused by: java.util.concurrent.ExecutionException
Caused by: java.lang.AssertionError

And :
java.util.ServiceConfigurationError

@ndahlquist
Copy link
Contributor

I can confirm that the workaround I mentioned earlier works:

@@ -399,7 +399,12 @@ public class ShadowSQLiteConnection {
         @Override
         public Object call() throws Exception {
           SQLiteConnection connection = getConnection(ptr);
-          connection.dispose();
+          try {
+            connection.dispose();
+          } catch (java.lang.AssertionError e) { // DB[109] will not dispose from a non-confining thread
+            // Hacky patch for https://github.com/robolectric/robolectric/issues/1986.
+            e.printStackTrace();
+          }
           return null;
         }
       });

We have a unit test suite of many thousands of tests. We've run it tens of thousands of times since introducing this patch, and I've never noticed any related errors.

I'm hesitant to open a PR since this is obviously a hack, and it's unclear to me if there are any undesirable implications. If a Robolectric maintainer wants to incorporate this though, I'm happy to open a PR.

@vvsevolodovich
Copy link

@ndahlquist how did you manage to incorporate the fix locally? Just built robolectric locally on your own?

@vvsevolodovich
Copy link

Hello! I was able to resolve the issue with the following steps:
1.

`package org.robolectric.shadows;

import org.robolectric.annotation.Implements;

@implements(ShadowSQLiteConnection.Connections.class)
public class MyShadowSQLiteConnections extends ShadowSQLiteConnection.Connections {

@OverRide
public void close(long ptr) {
com.almworks.sqlite4java.SQLiteConnection connection = getConnection(ptr);
connection.dispose();
try {
connection.dispose();
} catch (java.lang.AssertionError e) { // DB[109] will not dispose from a non-confining thread
// Hacky patch for #1986.
e.printStackTrace();
}
}
}`

`package android.database.sqlite;

import com.almworks.sqlite4java.SQLiteConnection;

import org.robolectric.annotation.Implements;
import org.robolectric.shadows.MyShadowSQLiteConnections;
import org.robolectric.shadows.ShadowSQLiteConnection;

import java.lang.reflect.Field;

@implements(value = SQLiteConnection.class, isInAndroidSdk = false)
public class MyShadowSqliteConnection extends ShadowSQLiteConnection {

public MyShadowSqliteConnection() {
    super();
    try {
        Field declaredField = ShadowSQLiteConnection.class.getDeclaredField("");
        declaredField.setAccessible(true);
        declaredField.set(null, new MyShadowSQLiteConnections());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}
`

  1. Add to your base db test the following line:

@config(constants = BuildConfig.class, sdk = 18, shadows = {ShadowApplication.class, MyShadowSqliteConnection.class})

ShadowApplication is optional of course.

@ndahlquist
Copy link
Contributor

Ah, we built locally, but I like your solution better! Thanks for sharing!

@realumy
Copy link

realumy commented Nov 14, 2016

Hello !
@vlivanov I tried your solution but i got some problems :/
Which BuildConfig do you use ? (android.support.v7.appcompat or your own in your project)
I explain myself, when I try to run my tests with the following line :

@Config(constants = BuildConfig.class, sdk = 19, shadows = {ShadowApplication.class, MyShadowSqliteConnection.class})
some of my tests failed, the ones which contain :

activity = Robolectric.buildActivity(Activity.class)
                              .withIntent(intent)
                              .create()
                              .get();

I got the following exception :

android.content.res.Resources$NotFoundException: can't find file for ResName{package:drawable/abc_edit_text_material}
at org.robolectric.shadows.Converter.convertAndFill(Converter.java:73)
    at org.robolectric.shadows.ShadowAssetManager.attrsToTypedArray(ShadowAssetManager.java:539)
    at org.robolectric.shadows.ShadowResources$ShadowTheme.obtainStyledAttributes(ShadowResources.java:213)
    at org.robolectric.shadows.ShadowResources$ShadowTheme.obtainStyledAttributes(ShadowResources.java:208)
    at org.robolectric.shadows.ShadowResources$ShadowTheme.obtainStyledAttributes(ShadowResources.java:203)
    at android.content.res.Resources$Theme.obtainStyledAttributes(Resources.java)
    at android.content.Context.obtainStyledAttributes(Context.java:389)
    at android.support.v7.app.AppCompatDelegateImplV7.createSubDecor(AppCompatDelegateImplV7.java:339)
    at android.support.v7.app.AppCompatDelegateImplV7.ensureSubDecor(AppCompatDelegateImplV7.java:312)
    at android.support.v7.app.AppCompatDelegateImplV7.setContentView(AppCompatDelegateImplV7.java:277)
    at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:140)

I tried with both BuildConfig mentioned previously but it doesn't work ..
Maybe some one encounter the same problem or knows how to solve it ?

Thanks :)

@vvsevolodovich
Copy link

@realumy I used the BuildConfig of my application. I haven't encountered this problem, sorry you have it.

@StanisVS
Copy link

StanisVS commented Nov 28, 2016

Hello!
Took me awhile to get @vlivanov solution's idea here some comments:

If copy-paste this code as is neither MyShadowSqliteConnection or MyShadowSQLiteConnections are never loaded.

Maybe I'm not quite understand how shadowing work in robolectric, but my guess is that shadows of classes that used only in shadows (like ShadowSQLiteConnection) never trigger.

Further, if try to call MyShadowSqliteConnection constructor explicitly test failed with NoSuchFieldException (because ShadowSQLiteConnection really don't have "" field)

I guess it means to reset "CONNECTIONS" field, but it's final so you have to workaround with that.

And finally you call connection.dispose() twice and at first attempt is unsafe as it not wrapped into try-catch

I'm still find some strange behaviour when CONNECTIONS field are some how reset (my guess it's related to custom classloading in robolectric) but it seems that change this field via reflection in test runner is enough to make it work

@StanisVS
Copy link

Ok now I tested solution and that one is worked for me

package org.robolectric.shadows;

import com.almworks.sqlite4java.SQLiteConnection;

import org.robolectric.util.ReflectionHelpers;

/**
 * hack to fix DB[*] will not dispose from a non-confining thread crash
 *
 * @see <a href="https://github.com/robolectric/robolectric/issues/1986#issuecomment-259374817">base solution on github</a>
 */
public class MyShadowSQLiteConnections extends ShadowSQLiteConnection.Connections {

    private final static MyShadowSQLiteConnections CUSTOM_CONNECTIONS_INSTANCE = new MyShadowSQLiteConnections();

    @Override
    public void close(long ptr) {
        execute("close connection", () -> {
            SQLiteConnection connection = getConnection(ptr);
            try {
                connection.dispose();
            } catch (AssertionError e) { // DB[*] will not dispose from a non-confining thread
                // Hacky patch for #1986.
            }
            return null;
        });
    }

    public static void resetConnectionsInShadowSQLiteConnection() {
        ReflectionHelpers.setStaticField(ShadowSQLiteConnection.class, "CONNECTIONS", CUSTOM_CONNECTIONS_INSTANCE);
    }
}

call resetConnectionsInShadowSQLiteConnection() in constructor of your TestLifecycleApplication implementation will do the work. (maybe beforeTest is also ok but I haven't checked)

@luckcoolla
Copy link

thanks, @StanisVS, your workaround solved my issue: I saw time-to-time 30% of 200 tests failed; Robolectric 3.0. Now all seems to be ok.

@sleeke
Copy link

sleeke commented Mar 17, 2017

thanks also, @StanisVS, I added your workaround and called it in the @After of my base test class. Works like a charm 😀

@vvsevolodovich
Copy link

@StanisVS thanks for providing a full working solution and sorry for mistakes in my pasted code, I see I really failed to mention CONNECTIONS field.

@matthewhubblerose
Copy link
Contributor

I've encountered this problem when running tests on a Linux CI runner, it doesn't happen every time but when it does it causes many tests to fail. Seems to not happen on macOS (using robolecttic 3.4-rc2), or if it does happen then it's extremely rare.

Really don't know why this at least seems to behave differently on different OS's, but it looks like the fact the AssertionError is thrown from the sqlite4java library is a mistake to me. This is the root exception of the failure a test:

Caused by: java.lang.AssertionError: DB[109] will not dispose from a non-confining thread
	at com.almworks.sqlite4java.Internal.recoverableError(Internal.java:46)
	at com.almworks.sqlite4java.SQLiteConnection.dispose(SQLiteConnection.java:363)
	at org.robolectric.shadows.ShadowSQLiteConnection$Connections$3.call(ShadowSQLiteConnection.java:538)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	... 3 more

After looking at the release notes and history of sqlite4java, it seems that no exception should be raised.

From the Change Log:

Build 201
SQLiteConnection.dispose() method contract changed: it is now not allowed to call dispose from a different (non-confining) thread, due to possible JVM crash (#18). If called from different thread, the method will fail softly.

robolectric is using build 282 (the release after build 201) so should have that fix. However, when you look at commit for the fix, this documentation was added to the dispose() method:

* If called from a different thread rather from the thread where the connection was opened, this method
* does nothing. (It used to attempt connection disposal anyway, but that could lead to JVM crash.)

And this is the code that was added to dispose():

Thread confinement = myConfinement;
if (confinement != null && confinement != Thread.currentThread()) {
    Internal.recoverableError(this, "will not dispose from a non-confining thread", true);
    return;
}

That true parameter passed into Internal.recoverableError() toggles whether an AssertionError is thrown, the comment, release notes and return statement all imply this was not the intended behaviour. Changing the line to:

Internal.recoverableError(this, "will not dispose from a non-confining thread", false);

would mean no AssertionError would be thrown.

The last commit to this project was in January 2016 so it's not looking too active. A fix for this problem could be just changing that flag, releasing sqlite4java and having robolectric use the latest version.

@matthewhubblerose
Copy link
Contributor

After some more investigation, it would seem that this may be a symptom of another problem.

This exception gets thrown because SQLiteConnection::dispose is called on a different thread to SQLiteConnection::open. The robolectric code interacts with SQLiteConnection by submitting Callables to an ExecutorService instance, using the execute function:

public <T> T execute(final String comment, final Callable<T> work) {
  try {
    return Uninterruptibles.getUninterruptibly(dbExecutor.submit(work));
  } catch (ExecutionException e) {
    // Snip...
  }
}

dbExecutor is initialised with:

private ExecutorService dbExecutor = Executors.newSingleThreadExecutor();

So all the work should be on the same thread. However, the documentation for newSingleThreadExecutor says:

Creates an Executor that uses a single worker thread operating off an unbounded queue. (Note however that if this single thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks.)

So it's probably the case that at some point an unhandled exception is thrown during execution of one of the Callables submitted to dbExecutor, which causes a new thread to be created which ends up trying to call dispose on the SQLiteConnection, which then throws an exception as it's not been called on the right thread.

Should try and find out if it's the case that a new thread is being used for dbExecutor, and if it is, what caused the original thread to terminate.

@matthewhubblerose
Copy link
Contributor

I was wrong in my previous 2 comments, I believe this error is the result of a race condition, I've opened a pull request to try and fix it (see pull request #3211 for an explanation of the issue and proposed fix).

I've not been able to test my changes with a locally built snapshot of robolectric on my machine, would be great if someone could walk me through how to do it. Thanks!

@hoisie
Copy link
Contributor

hoisie commented Jun 28, 2017

@matthewhubblerose try applying #3212 on your local checkout, running ./gradlew install, adding mavenLocal() to your repositories, and using

testCompile 'org.robolectric:robolectric:3.4-SNAPSHOT'.

@matthewhubblerose
Copy link
Contributor

Looks like the fix for this was released with 3.4 RC5

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

Successfully merging a pull request may close this issue.