1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Disassociate JNI db handles from the thread that created them, as it's no longer relevant.

FossilOrigin-Name: 8b78b737e66a399b04e555a8197f63a73198a4105cb2f37ffd5b0e6014302caf
This commit is contained in:
stephan
2023-08-22 18:36:30 +00:00
parent c675add616
commit 87bb103038
5 changed files with 52 additions and 80 deletions

View File

@ -380,10 +380,6 @@ struct S3JniHook{
*/
typedef struct S3JniDb S3JniDb;
struct S3JniDb {
JNIEnv *env /* Used for cleaning up all dbs owned by a given
** thread, noting that this ownership is an artificial
** one imposed by our threading constraints, not by
** the core library. */;
sqlite3 *pDb /* The associated db handle */;
jobject jDb /* A global ref of the output object which gets
returned from sqlite3_open(_v2)(). We need this in
@ -558,6 +554,9 @@ struct S3JniGlobalType {
static S3JniGlobalType S3JniGlobal = {};
#define SJG S3JniGlobal
/* Helpers for working with specific mutexes. */
#define MUTEX_ENV_ASSERT_LOCKED \
assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define MUTEX_ENV_ASSERT_LOCKER \
assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define MUTEX_ENV_ASSERT_NOTLOCKER \
@ -604,7 +603,7 @@ static S3JniGlobalType S3JniGlobal = {};
assert( 0 != SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" )
#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env)
static void s3jni_oom(JNIEnv * const env){
static inline void s3jni_oom(JNIEnv * const env){
(*env)->FatalError(env, "Out of memory.") /* does not return */;
}
@ -941,41 +940,15 @@ static void S3JniDb_set_aside(S3JniDb * const s){
//if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev));
}
}
/**
Cleans up all state in S3JniGlobal.perDb for th given JNIEnv.
Results are undefined if a Java-side db uses the API
from the given JNIEnv after this call.
*/
static void S3JniDb_free_for_env(JNIEnv *env){
S3JniDb * ps;
S3JniDb * pNext = 0;
MUTEX_PDB_ENTER;
ps = SJG.perDb.aUsed;
for( ; ps; ps = pNext ){
pNext = ps->pNext;
if(ps->env == env){
#ifndef NDEBUG
S3JniDb * const pPrev = ps->pPrev;
#endif
S3JniDb_set_aside(ps);
assert( pPrev ? pPrev->pNext==pNext : 1 );
assert( ps == SJG.perDb.aFree );
}
}
MUTEX_PDB_LEAVE;
}
/**
Uncache any state for the given JNIEnv, clearing all Java
references the cache owns. Returns true if env was cached and false
if it was not found in the cache.
Also passes env to S3JniDb_free_for_env() to free up
what would otherwise be stale references.
*/
static int S3JniGlobal_env_uncache(JNIEnv * const env){
struct S3JniEnv * row;
MUTEX_ENV_ASSERT_LOCKER;
MUTEX_ENV_ASSERT_LOCKED;
row = SJG.envCache.aHead;
for( ; row; row = row->pNext ){
if( row->env == env ){
@ -992,7 +965,6 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){
assert( !row->pPrev );
SJG.envCache.aHead = row->pNext;
}
S3JniDb_free_for_env(env);
memset(row, 0, sizeof(S3JniEnv));
row->pNext = SJG.envCache.aFree;
if( row->pNext ) row->pNext->pPrev = row;
@ -1130,7 +1102,6 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb,
}
rv->jDb = REF_G(jDb);
rv->pDb = pDb;
rv->env = env;
}
MUTEX_PDB_LEAVE;
return rv;

View File

@ -122,21 +122,13 @@ public final class SQLite3Jni {
uncacheJniEnv() when it is done with the library - either right
before it terminates or when it is finished using the SQLite API.
This will clean up any cached per-JNIEnv info. Calling into the
library again after that "should" re-initialize the cache on
demand, but that's untested.
library will re-initialize the cache on demand.
This call forcibly wipes out all cached information for the
current JNIEnv, a side-effect of which is that behavior is
undefined if any database objects are (A) still active at the
time it is called _and_ (B) calls are subsequently made into the
library with such a database. Doing so will, at best, lead to a
crash. At worst, it will lead to the db possibly misbehaving
because some of its Java-bound state has been cleared. There is
no immediate harm in (A) so long as condition (B) is not met.
This process does _not_ actually close any databases or finalize
any prepared statements. For proper library behavior, and to
avoid C-side leaks, be sure to close them before calling this
function.
This process does _not_ close any databases or finalize
any prepared statements because their ownership does not depend on
a given thread. For proper library behavior, and to
avoid C-side leaks, be sure to finalize all statements and close
all databases before calling this function.
Calling this from the main application thread is not strictly
required but is "polite." Additional threads must call this

View File

@ -24,6 +24,7 @@ import java.util.concurrent.Future;
public class Tester1 implements Runnable {
//! True when running in multi-threaded mode.
private static boolean mtMode = false;
private static boolean takeNaps = false;
private static final class Metrics {
int dbOpen;
@ -1167,32 +1168,38 @@ public class Tester1 implements Runnable {
outln("Woke up.");
}
private void nap() throws InterruptedException {
if( takeNaps ){
Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 28), 0);
}
}
private void runTests(boolean fromThread) throws Exception {
if(false) testCompileOption();
testToUtf8();
test1();
testOpenDb1();
testOpenDb2();
testCollation();
testPrepare123();
testBindFetchInt();
testBindFetchInt64();
testBindFetchDouble();
testBindFetchText();
testBindFetchBlob();
testSql();
testStatus();
testUdf1();
testUdfJavaObject();
testUdfAggregate();
testUdfWindow();
testTrace();
testProgress();
testCommitHook();
testRollbackHook();
testUpdateHook();
testAuthorizer();
testAutoExtension();
nap(); testOpenDb1();
nap(); testOpenDb2();
nap(); testCollation();
nap(); testPrepare123();
nap(); testBindFetchInt();
nap(); testBindFetchInt64();
nap(); testBindFetchDouble();
nap(); testBindFetchText();
nap(); testBindFetchBlob();
nap(); testSql();
nap(); testStatus();
nap(); testUdf1();
nap(); testUdfJavaObject();
nap(); testUdfAggregate();
nap(); testUdfWindow();
nap(); testTrace();
nap(); testProgress();
nap(); testCommitHook();
nap(); testRollbackHook();
nap(); testUpdateHook();
nap(); testAuthorizer();
nap(); testAutoExtension();
if(!fromThread){
testBusy();
if( !mtMode ){
@ -1227,6 +1234,8 @@ public class Tester1 implements Runnable {
nThread = Integer.parseInt(args[i++]);
}else if(arg.equals("r") || arg.equals("runs")){
nRepeat = Integer.parseInt(args[i++]);
}else if(arg.equals("naps")){
takeNaps = true;
}else{
throw new IllegalArgumentException("Unhandled flag:"+arg);
}
@ -1247,7 +1256,7 @@ public class Tester1 implements Runnable {
final ExecutorService ex = Executors.newFixedThreadPool( nThread );
//final List<Future<?>> futures = new ArrayList<>();
++nLoop;
out(nLoop+" ");
out((1==nLoop ? "" : " ")+nLoop);
for( int i = 0; i < nThread; ++i ){
ex.submit( new Tester1(i), i );
}