Cancelling or Monitoring Queries

One of our queries is taking/can take a very long time and while we have moved other database access attempts off the UI thread, we were hoping to improve overall responsiveness of the app so that instead of waiting for the query to be done, we could just cancel the offending query and start the query indicated by user activity.

Is there a way to get the status of a query or if the database is currently held by a query? Is there a good way to cancel a query that is currently running?

I think that you should simply implement a query timeout and react on the timeouts

You haven’t said anything that could guide a response: what platform are you using? what version are you using? what device and OS are you using? Etc. Throw us a bone, here? :stuck_out_tongue_winking_eye:

I will mention that multi-threading has almost no affect on the speed of access. The persistence mechanism that underlies CouchbaseLite (I’m guessing you are using CouchbaseLite!) is based on file-system operations. Those are, necessarily, single threaded.

Sorry Blake. We are on an Android S10 currently using CouchbaseLite 3.0.2.

Yea the data count query does take a long time but that we suspect is based on database size instead of the multithreading. The issue, I use this lightly since this isn’t an error or bug, is that since each query seizes control of the database, it does block other subsequent queries until it has completed. For example, if the data count query is running and the user goes to delete the database, couchbase rightfully will return an exception which we return to the user as an error. Aside from changing our exception handling, we were wondering if there was an alternate approach where, before we actually attempt to delete the database, we could check to see if there was a query or other connection to the database that would prevent the deletion or alternatively, we could cancel any existing queries in order to delete the database.

each query seizes control of the database, it does block other subsequent queries until it has completed

Yes. That’s just how it works, I’m afraid.

If you are doing multiple read operations, you might be able to speed things up by opening two completely separate instances of the database, e.g:

final Database db1 = new Database("myDb");
final Database db2 = new Database("myDb");

Any write operations on the same database (“myDb”, not just the instances db1 and db2), though, will block and be blocked by operations on either of those instances.

A couple thoughts:

  • Android, man! Do NOT be doing any db stuff on the UI thread. You totally know better! :stuck_out_tongue_winking_eye:
  • Deleting the DB will complete, eventually, when the query completes. Perhaps you can just wait for it?
  • There is no way to cancel the query. I don’t think it would be too hard to count the ones that were in flight… but you can’t cancel them before they complete.

Thanks Blake! We never thought about a second instance of the db for reads. Yea the UI crashing was a sign we were doing something wrong before we moved all the db stuff to a background thread.

What you mentioned about the deleting db is interesting. If we make a database.delete call and another query has control of the database, even if an exception is thrown, the delete call will still finish on it’s own in the background whenever the other query finishes?

@dshyu : If there’s an exception, something has failed. I’m not sure what exception you are talking about, though. The DB will wait for quite a while (minutes) before failing to close. Is that what is happening?

… and, just to repeat: a second db instance, for reads, won’t help if any other instance is doing writes…

Sorry for the delay @blake.meike, I was traveling.

The main exception we see now is

com.couchbase.lite.CouchbaseLiteException: Can’t delete db file while other connections are open. The open connections are tagged appOpened, appOpened.

We have also seen exceptions about being unable to perform operations on a closed database but the above one is the most common.

I believe the connections exception does block the deletion but if we try again, it seems the deletion will work. If we see this exception, will the delete call wait until the database is relinquished by the other connection and then complete the deletion on it’s own without input from the user? Or will this drop the delete request?

I’m going to need a lot more than that one line, to make any guesses about what is going on. Can I get an adb logcat log, including the full exception stack trace?

As I mentioned previously, the database will try to close for quite a while, before it gives up and throws an exception. I believe that the version you are running (3.0.2?) times out after 30 seconds.

The database tries to stop all outstanding processes. If it fails (throws an exception) it will not make further attempts to close. The outstanding processes, however, have been notified that they must stop, and may, eventually, terminate.

Sorry @blake.meike I’ll try and get some logs to you. The exception isn’t easily reproduced.

com.couchbase.lite.CouchbaseLiteException: Can’t delete db file while other connections are open. The open connections are tagged appOpened, appOpened.
(CouchbaseLite Android v3.0.2-11@28 (CE/release, Commit/unofficial@647027507d3c Core/3.0.2 (11) at 2022-07-07T23:44:49.000285Z) on Java; Android 10; SM-G973U1)
at com.couchbase.lite.AbstractDatabase.shutdown(Unknown Source:130)
at com.couchbase.lite.AbstractDatabase.delete(Unknown Source:22)
at com.couchbase.lite.Database.delete(Unknown Source:0)

This is a bit more of the stack trace. I’m sorry I can’t produce more since our release version uses code obfuscation and the rest of the stack trace is pretty uninformative. We don’t see the exception very often but I’ll try in our debug version.

I’m also sorry if I misled you to think there was a serious issue that needed addressing. We’re pretty confident that we are mishandling the database when we try to delete so we just wanted a better understanding of how it should behave.

If there is a query that takes more than 30 seconds, if we attempt to delete the database during the time the query has control of the database, the delete call will time out (since the database will not close) and an exception will be thrown. Is that correct? If so, what is the exception message we would expect to see?

If the database fails to stop outstanding processes (I’m assuming this means things like the replicator), what is the exception message we would expect to see?

I know without a full log, it is difficult to know what is happening, but with the exception that I have shown, can you speculate on what could be going on?

Thanks!

LOL! Yeah: “Unknown Source: 130” is not a huge help.

It sounds like you are calling Database.close or Database.delete from more than one place. I can’t tell from the bit of stacktrace that you’ve shown, here, which call this is.

I can tell you, flat out, that trying to delete the database from a long running query is nearly always going to fail, like this. The only occasions on which it will succeed are those in which the query happens to complete: you will not speed anything up. I would say that doing this is just a really bad idea.

What I think is happening is that one call to close the database is racing the other. That’s just a wild guess but the stacktrace that you quote above suggests that the Java code thinks that all the long-running processes have been terminated successfully but that the LiteCore engine thinks otherwise. That would be unusual, to say the least.

What other functions would make a close call besides delete? We only attempt to delete on demand of user input however, we have other background threads that access the database (specifically the count query that can take a long time to complete) which I’m assuming could lead to the 30 second timeout exception. Would that timeout exception be different from the one I quoted? Or is that exception strictly for attempting to delete/close from more than one place?

Just to chime in here, the error message there indicates that you have two other open connections from your app (as opposed to one that the library has opened for its own purposes such as replication) to the same database file. You must close all the other ones before you can delete the database, because the library will refuse to delete the database if other handles are using it. So if you’ve followed the advice above and added a second db instance for reads, you will need to close that too before you can delete. If you use Windows, you can think of it like the “cannot delete file because it is being used” error that happens frequently.

Thanks @borrrden. Can you be more specific about what a connection would be? Does that include queries that are actively working and queries that are waiting to work or is it specifically a “new Database” call? We haven’t implemented a second db connection just for reads yet so was just wondering what else could be causing two connections. Thanks!

Any time you create a new database object that is considered a connection (or handle) to the database that remains active until you close it. All of them must be closed prior to deletion.