Refactor the per-JNIEnv cache from a fixed-size static array to a linked list of dynamically-allocated entries. Uncache all per-db state (which is necessarily JNIEnv-specific) when the corresponding JNIEnv is uncached.
FossilOrigin-Name: 9dd8b78419e19e88bc3fbff9bf200390b146b2461af2bb6b93d8467036619e33
This commit is contained in:
parent
67214a9594
commit
28e95830ad
@ -293,24 +293,13 @@ static const struct {
|
||||
|
||||
enum {
|
||||
/**
|
||||
Size of the per-JNIEnv cache. We have no way of knowing how many
|
||||
distinct JNIEnv's will be used in any given run, but know that it
|
||||
will normally be only 1. Perhaps (just speculating) different
|
||||
threads use separate JNIEnvs? If that's the case, we don't(?)
|
||||
have enough info to evict from the cache when those JNIEnvs
|
||||
expire.
|
||||
|
||||
If this ever fills up, we can refactor this to dynamically
|
||||
allocate them.
|
||||
*/
|
||||
JNIEnvCache_SIZE = 10,
|
||||
/**
|
||||
Need enough space for (only) the library's NativePointerHolder
|
||||
types, a fixed count known at build-time. If we add more than this
|
||||
a fatal error will be triggered with a reminder to increase this.
|
||||
This value needs to be exactly the number of entries in the
|
||||
S3ClassNames object. The S3ClassNames entries are the keys for
|
||||
this particular cache.
|
||||
Size of the NativePointerHolder cache. Need enough space for
|
||||
(only) the library's NativePointerHolder types, a fixed count
|
||||
known at build-time. If we add more than this a fatal error will
|
||||
be triggered with a reminder to increase this. This value needs
|
||||
to be exactly the number of entries in the S3ClassNames
|
||||
object. The S3ClassNames entries are the keys for this particular
|
||||
cache.
|
||||
*/
|
||||
NphCache_SIZE = sizeof(S3ClassNames) / sizeof(char const *)
|
||||
};
|
||||
@ -370,12 +359,8 @@ struct JNIEnvCacheLine {
|
||||
jfieldID fidB;
|
||||
} jPhraseIter;
|
||||
#endif
|
||||
#if 0
|
||||
/* TODO: refactor this cache as a linked list with malloc()'d entries,
|
||||
rather than a fixed-size array in S3Global.envCache */
|
||||
JNIEnvCacheLine * pPrev /* Previous entry in the linked list */;
|
||||
JNIEnvCacheLine * pNext /* Next entry in the linked list */;
|
||||
#endif
|
||||
/** TODO: NphCacheLine *pNphHit;
|
||||
|
||||
to help fast-track cache lookups, update this to point to the
|
||||
@ -384,41 +369,12 @@ struct JNIEnvCacheLine {
|
||||
*/
|
||||
struct NphCacheLine nph[NphCache_SIZE];
|
||||
};
|
||||
typedef struct JNIEnvCache JNIEnvCache;
|
||||
struct JNIEnvCache {
|
||||
struct JNIEnvCacheLine lines[JNIEnvCache_SIZE];
|
||||
unsigned int used;
|
||||
};
|
||||
|
||||
static void NphCacheLine_clear(JNIEnv * const env, NphCacheLine * const p){
|
||||
UNREF_G(p->klazz);
|
||||
memset(p, 0, sizeof(NphCacheLine));
|
||||
}
|
||||
|
||||
static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){
|
||||
JNIEnv * const env = p->env;
|
||||
if(env){
|
||||
int i;
|
||||
UNREF_G(p->globalClassObj);
|
||||
UNREF_G(p->globalClassLong);
|
||||
#ifdef SQLITE_ENABLE_FTS5
|
||||
UNREF_G(p->jFtsExt);
|
||||
UNREF_G(p->jPhraseIter.klazz);
|
||||
#endif
|
||||
for( i = 0; i < NphCache_SIZE; ++i ){
|
||||
NphCacheLine_clear(env, &p->nph[i]);
|
||||
}
|
||||
memset(p, 0, sizeof(JNIEnvCacheLine));
|
||||
}
|
||||
}
|
||||
|
||||
static void JNIEnvCache_clear(JNIEnvCache * const p){
|
||||
unsigned int i = 0;
|
||||
for( ; i < p->used; ++i ){
|
||||
JNIEnvCacheLine_clear( &p->lines[i] );
|
||||
}
|
||||
}
|
||||
|
||||
/** State for various hook callbacks. */
|
||||
typedef struct JniHookState JniHookState;
|
||||
struct JniHookState{
|
||||
@ -476,7 +432,10 @@ static struct {
|
||||
JNIEnv when necessary.
|
||||
*/
|
||||
JavaVM * jvm;
|
||||
struct JNIEnvCache envCache;
|
||||
struct {
|
||||
JNIEnvCacheLine * aHead /* Linked list of in-use instances */;
|
||||
JNIEnvCacheLine * aFree /* Linked list of free instances */;
|
||||
} envCache;
|
||||
struct {
|
||||
PerDbStateJni * aUsed /* Linked list of in-use instances */;
|
||||
PerDbStateJni * aFree /* Linked list of free instances */;
|
||||
@ -512,11 +471,6 @@ static void * s3jni_malloc(JNIEnv * const env, size_t n){
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void s3jni_free(void * const p){
|
||||
if(p) sqlite3_free(p);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** This function is NOT part of the sqlite3 public API. It is strictly
|
||||
** for use by the sqlite project's own Java/JNI bindings.
|
||||
@ -575,38 +529,183 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){
|
||||
/**
|
||||
Fetches the S3Global.envCache row for the given env, allocing
|
||||
a row if needed. When a row is allocated, its state is initialized
|
||||
insofar as possible. Calls (*env)->FatalError() if the cache
|
||||
fills up. That's hypothetically possible but "shouldn't happen."
|
||||
If it does, we can dynamically allocate these instead.
|
||||
insofar as possible. Calls (*env)->FatalError() if allocation of
|
||||
an entry fails. That's hypothetically possible but "shouldn't happen."
|
||||
*/
|
||||
FIXME_THREADING
|
||||
static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * const env){
|
||||
struct JNIEnvCacheLine * row = 0;
|
||||
int i = 0;
|
||||
for( ; i < JNIEnvCache_SIZE; ++i ){
|
||||
row = &S3Global.envCache.lines[i];
|
||||
if(row->env == env){
|
||||
static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){
|
||||
struct JNIEnvCacheLine * row = S3Global.envCache.aHead;
|
||||
for( ; row; row = row->pNext ){
|
||||
if( row->env == env ){
|
||||
++S3Global.metrics.envCacheHits;
|
||||
return row;
|
||||
}
|
||||
else if(!row->env) break;
|
||||
}
|
||||
++S3Global.metrics.envCacheMisses;
|
||||
if(i == JNIEnvCache_SIZE){
|
||||
(*env)->FatalError(env, "Maintenance required: JNIEnvCache is full.");
|
||||
return NULL;
|
||||
row = S3Global.envCache.aFree;
|
||||
if( row ){
|
||||
assert(!row->pPrev);
|
||||
S3Global.envCache.aFree = row->pNext;
|
||||
if( row->pNext ) row->pNext->pPrev = 0;
|
||||
}else{
|
||||
row = sqlite3_malloc(sizeof(JNIEnvCacheLine));
|
||||
if( !row ){
|
||||
(*env)->FatalError(env, "Maintenance required: JNIEnvCache is full.")
|
||||
/* Does not return, but cc doesn't know that */;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
memset(row, 0, sizeof(JNIEnvCacheLine));
|
||||
memset(row, 0, sizeof(*row));
|
||||
row->pNext = S3Global.envCache.aHead;
|
||||
if(row->pNext) row->pNext->pPrev = row;
|
||||
S3Global.envCache.aHead = row;
|
||||
row->env = env;
|
||||
row->globalClassObj = REF_G((*env)->FindClass(env,"java/lang/Object"));
|
||||
EXCEPTION_IS_FATAL("Error getting reference to Object class.");
|
||||
row->globalClassLong = REF_G((*env)->FindClass(env,"java/lang/Long"));
|
||||
EXCEPTION_IS_FATAL("Error getting reference to Long class.");
|
||||
row->ctorLong1 = (*env)->GetMethodID(env, row->globalClassLong,
|
||||
"<init>", "(J)V");
|
||||
++S3Global.envCache.used;
|
||||
//MARKER(("Added S3Global.envCache entry #%d.\n", S3Global.envCache.used));
|
||||
EXCEPTION_IS_FATAL("Error getting reference to Long constructor.");
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
Removes any Java references from s and clears its state. If
|
||||
doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's
|
||||
s is passed to s3jni_call_xDestroy() before any references are
|
||||
cleared. It is legal to call this when the object has no Java
|
||||
references.
|
||||
*/
|
||||
static void JniHookState_unref(JNIEnv * const env, JniHookState * const s, int doXDestroy){
|
||||
if(doXDestroy && s->klazz && s->jObj){
|
||||
s3jni_call_xDestroy(env, s->jObj, s->klazz);
|
||||
}
|
||||
UNREF_G(s->jObj);
|
||||
UNREF_G(s->klazz);
|
||||
memset(s, 0, sizeof(*s));
|
||||
}
|
||||
|
||||
/**
|
||||
Clears s's state and moves it to the free-list.
|
||||
*/
|
||||
FIXME_THREADING
|
||||
static void PerDbStateJni_set_aside(PerDbStateJni * const s){
|
||||
if(s){
|
||||
JNIEnv * const env = s->env;
|
||||
assert(s->pDb && "Else this object is already in the free-list.");
|
||||
//MARKER(("state@%p for db@%p setting aside\n", s, s->pDb));
|
||||
assert(s->pPrev != s);
|
||||
assert(s->pNext != s);
|
||||
assert(s->pPrev ? (s->pPrev!=s->pNext) : 1);
|
||||
if(s->pNext) s->pNext->pPrev = s->pPrev;
|
||||
if(s->pPrev) s->pPrev->pNext = s->pNext;
|
||||
else if(S3Global.perDb.aUsed == s){
|
||||
assert(!s->pPrev);
|
||||
S3Global.perDb.aUsed = s->pNext;
|
||||
}
|
||||
#define UNHOOK(MEMBER,XDESTROY) JniHookState_unref(env, &s->MEMBER, XDESTROY)
|
||||
UNHOOK(trace, 0);
|
||||
UNHOOK(progress, 0);
|
||||
UNHOOK(commitHook, 0);
|
||||
UNHOOK(rollbackHook, 0);
|
||||
UNHOOK(updateHook, 0);
|
||||
UNHOOK(collation, 1);
|
||||
UNHOOK(collationNeeded, 1);
|
||||
UNHOOK(busyHandler, 1);
|
||||
#undef UNHOOK
|
||||
UNREF_G(s->jDb);
|
||||
#ifdef SQLITE_ENABLE_FTS5
|
||||
UNREF_G(s->jFtsApi);
|
||||
#endif
|
||||
memset(s, 0, sizeof(PerDbStateJni));
|
||||
s->pNext = S3Global.perDb.aFree;
|
||||
if(s->pNext) s->pNext->pPrev = s;
|
||||
S3Global.perDb.aFree = s;
|
||||
//MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext));
|
||||
//if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Requires that p has been snipped from any linked list it is
|
||||
in. Clears all Java refs p holds and zeroes out p.
|
||||
*/
|
||||
static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){
|
||||
JNIEnv * const env = p->env;
|
||||
if(env){
|
||||
int i;
|
||||
UNREF_G(p->globalClassObj);
|
||||
UNREF_G(p->globalClassLong);
|
||||
#ifdef SQLITE_ENABLE_FTS5
|
||||
UNREF_G(p->jFtsExt);
|
||||
UNREF_G(p->jPhraseIter.klazz);
|
||||
#endif
|
||||
for( i = 0; i < NphCache_SIZE; ++i ){
|
||||
NphCacheLine_clear(env, &p->nph[i]);
|
||||
}
|
||||
memset(p, 0, sizeof(JNIEnvCacheLine));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Cleans up all state in S3Global.perDb for th given JNIEnv.
|
||||
Results are undefined if a Java-side db uses the API
|
||||
from the given JNIEnv after this call.
|
||||
*/
|
||||
FIXME_THREADING
|
||||
static void PerDbStateJni_free_for_env(JNIEnv *env){
|
||||
PerDbStateJni * ps = S3Global.perDb.aUsed;
|
||||
PerDbStateJni * pNext = 0;
|
||||
for( ; ps; ps = pNext ){
|
||||
pNext = ps->pNext;
|
||||
if(ps->env == env){
|
||||
PerDbStateJni * const pPrev = ps->pPrev;
|
||||
PerDbStateJni_set_aside(ps);
|
||||
assert(pPrev ? pPrev->pNext!=ps : 1);
|
||||
pNext = pPrev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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 PerDbStateJni_free_for_env() to free up
|
||||
what would otherwise be stale references.
|
||||
*/
|
||||
static int S3Global_JNIEnvCache_uncache(JNIEnv * const env){
|
||||
struct JNIEnvCacheLine * row = S3Global.envCache.aHead;
|
||||
for( ; row; row = row->pNext ){
|
||||
if( row->env == env ){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( !row ) return 0;
|
||||
if( row->pNext ) row->pNext->pPrev = row->pPrev;
|
||||
if( row->pPrev ) row->pPrev->pNext = row->pNext;
|
||||
if( S3Global.envCache.aHead == row ){
|
||||
assert( !row->pPrev );
|
||||
S3Global.envCache.aHead = row->pNext;
|
||||
}
|
||||
JNIEnvCacheLine_clear(row);
|
||||
assert( !row->pNext );
|
||||
assert( !row->pPrev );
|
||||
row->pNext = S3Global.envCache.aFree;
|
||||
if( row->pNext ) row->pNext->pPrev = row;
|
||||
S3Global.envCache.aFree = row;
|
||||
PerDbStateJni_free_for_env(env);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void S3Global_JNIEnvCache_clear(void){
|
||||
while( S3Global.envCache.aHead ){
|
||||
S3Global_JNIEnvCache_uncache( S3Global.envCache.aHead->env );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Searches the NativePointerHolder cache for the given combination.
|
||||
If it finds one, it returns it as-is. If it doesn't AND the cache
|
||||
@ -640,7 +739,7 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv * const env, const char *
|
||||
looking up class objects can be expensive, so they should be
|
||||
cached as well.
|
||||
*/
|
||||
struct JNIEnvCacheLine * const envRow = S3Global_env_cache(env);
|
||||
struct JNIEnvCacheLine * const envRow = S3Global_JNIEnvCache_cache(env);
|
||||
struct NphCacheLine * freeSlot = 0;
|
||||
struct NphCacheLine * cacheLine = 0;
|
||||
int i;
|
||||
@ -787,63 +886,6 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, job
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
Removes any Java references from s and clears its state. If
|
||||
doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's
|
||||
s is passed to s3jni_call_xDestroy() before any references are
|
||||
cleared. It is legal to call this when the object has no Java
|
||||
references.
|
||||
*/
|
||||
static void JniHookState_unref(JNIEnv * const env, JniHookState * const s, int doXDestroy){
|
||||
if(doXDestroy && s->klazz && s->jObj){
|
||||
s3jni_call_xDestroy(env, s->jObj, s->klazz);
|
||||
}
|
||||
UNREF_G(s->jObj);
|
||||
UNREF_G(s->klazz);
|
||||
memset(s, 0, sizeof(*s));
|
||||
}
|
||||
|
||||
/**
|
||||
Clears s's state and moves it to the free-list.
|
||||
*/
|
||||
FIXME_THREADING
|
||||
static void PerDbStateJni_set_aside(PerDbStateJni * const s){
|
||||
if(s){
|
||||
JNIEnv * const env = s->env;
|
||||
assert(s->pDb && "Else this object is already in the free-list.");
|
||||
//MARKER(("state@%p for db@%p setting aside\n", s, s->pDb));
|
||||
assert(s->pPrev != s);
|
||||
assert(s->pNext != s);
|
||||
assert(s->pPrev ? (s->pPrev!=s->pNext) : 1);
|
||||
if(s->pNext) s->pNext->pPrev = s->pPrev;
|
||||
if(s->pPrev) s->pPrev->pNext = s->pNext;
|
||||
else if(S3Global.perDb.aUsed == s){
|
||||
assert(!s->pPrev);
|
||||
S3Global.perDb.aUsed = s->pNext;
|
||||
}
|
||||
#define UNHOOK(MEMBER,XDESTROY) JniHookState_unref(env, &s->MEMBER, XDESTROY)
|
||||
UNHOOK(trace, 0);
|
||||
UNHOOK(progress, 0);
|
||||
UNHOOK(commitHook, 0);
|
||||
UNHOOK(rollbackHook, 0);
|
||||
UNHOOK(updateHook, 0);
|
||||
UNHOOK(collation, 1);
|
||||
UNHOOK(collationNeeded, 1);
|
||||
UNHOOK(busyHandler, 1);
|
||||
#undef UNHOOK
|
||||
UNREF_G(s->jDb);
|
||||
#ifdef SQLITE_ENABLE_FTS5
|
||||
UNREF_G(s->jFtsApi);
|
||||
#endif
|
||||
memset(s, 0, sizeof(PerDbStateJni));
|
||||
s->pNext = S3Global.perDb.aFree;
|
||||
if(s->pNext) s->pNext->pPrev = s;
|
||||
S3Global.perDb.aFree = s;
|
||||
//MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext));
|
||||
//if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev));
|
||||
}
|
||||
}
|
||||
|
||||
static void PerDbStateJni_dump(PerDbStateJni *s){
|
||||
MARKER(("PerDbStateJni->env @ %p\n", s->env));
|
||||
MARKER(("PerDbStateJni->pDb @ %p\n", s->pDb));
|
||||
@ -894,28 +936,6 @@ static PerDbStateJni * PerDbStateJni_for_db(JNIEnv * const env, jobject jDb,
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
Cleans up and frees all state in S3Global.perDb.
|
||||
*/
|
||||
FIXME_THREADING
|
||||
static void PerDbStateJni_free_all(void){
|
||||
PerDbStateJni * ps = S3Global.perDb.aUsed;
|
||||
PerDbStateJni * pSNext = 0;
|
||||
for( ; ps; ps = pSNext ){
|
||||
pSNext = ps->pNext;
|
||||
PerDbStateJni_set_aside(ps);
|
||||
assert(pSNext ? !pSNext->pPrev : 1);
|
||||
}
|
||||
assert( 0==S3Global.perDb.aUsed );
|
||||
ps = S3Global.perDb.aFree;
|
||||
S3Global.perDb.aFree = 0;
|
||||
pSNext = 0;
|
||||
for( ; ps; ps = pSNext ){
|
||||
pSNext = ps->pNext;
|
||||
s3jni_free(pSNext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Requires that jCx be a Java-side sqlite3_context wrapper for pCx.
|
||||
@ -1322,7 +1342,7 @@ static int udf_args(JNIEnv *env,
|
||||
*jArgv = 0;
|
||||
if(!jcx) goto error_oom;
|
||||
ja = (*env)->NewObjectArray(env, argc,
|
||||
S3Global_env_cache(env)->globalClassObj,
|
||||
S3Global_JNIEnvCache_cache(env)->globalClassObj,
|
||||
NULL);
|
||||
if(!ja) goto error_oom;
|
||||
for(i = 0; i < argc; ++i){
|
||||
@ -2015,7 +2035,7 @@ JDECL(jint,1finalize)(JENV_JSELF, jobject jpStmt){
|
||||
int rc = 0;
|
||||
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
|
||||
if( pStmt ){
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
jobject const pPrev = stmt_set_current(jc, jpStmt);
|
||||
rc = sqlite3_finalize(pStmt);
|
||||
setNativePointer(env, jpStmt, 0, S3ClassNames.sqlite3_stmt);
|
||||
@ -2086,7 +2106,7 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass
|
||||
sqlite3_stmt * pStmt = 0;
|
||||
const char * zTail = 0;
|
||||
jbyte * const pBuf = JBA_TOC(baSql);
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
jobject const pOldStmt = stmt_set_current(jc, jOutStmt);
|
||||
int rc = SQLITE_ERROR;
|
||||
assert(prepVersion==1 || prepVersion==2 || prepVersion==3);
|
||||
@ -2181,7 +2201,7 @@ JDECL(jint,1reset)(JENV_JSELF, jobject jpStmt){
|
||||
int rc = 0;
|
||||
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
|
||||
if( pStmt ){
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
jobject const pPrev = stmt_set_current(jc, jpStmt);
|
||||
rc = sqlite3_reset(pStmt);
|
||||
(void)stmt_set_current(jc, pPrev);
|
||||
@ -2374,11 +2394,9 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_JSELF, jobject jpDb, jlong rowId){
|
||||
}
|
||||
|
||||
JDECL(jint,1shutdown)(JENV_JSELF){
|
||||
PerDbStateJni_free_all();
|
||||
JNIEnvCache_clear(&S3Global.envCache);
|
||||
/* Do not clear S3Global.jvm or the global refs to specific classes:
|
||||
it's legal to call sqlite3_initialize() again to restart the
|
||||
lib. */
|
||||
S3Global_JNIEnvCache_clear();
|
||||
/* Do not clear S3Global.jvm: it's legal to call
|
||||
sqlite3_initialize() again to restart the lib. */
|
||||
return sqlite3_shutdown();
|
||||
}
|
||||
|
||||
@ -2386,7 +2404,7 @@ JDECL(jint,1step)(JENV_JSELF,jobject jStmt){
|
||||
int rc = SQLITE_MISUSE;
|
||||
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt);
|
||||
if(pStmt){
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
jobject const jPrevStmt = stmt_set_current(jc, jStmt);
|
||||
rc = sqlite3_step(pStmt);
|
||||
(void)stmt_set_current(jc, jPrevStmt);
|
||||
@ -2400,7 +2418,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
|
||||
jobject jX = NULL /* the tracer's X arg */;
|
||||
jobject jP = NULL /* the tracer's P arg */;
|
||||
jobject jPUnref = NULL /* potentially a local ref to jP */;
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
int rc;
|
||||
switch(traceflag){
|
||||
case SQLITE_TRACE_STMT:
|
||||
@ -2660,7 +2678,6 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){
|
||||
SO(JniHookState);
|
||||
SO(PerDbStateJni);
|
||||
SO(S3Global);
|
||||
SO(JNIEnvCache);
|
||||
SO(S3ClassNames);
|
||||
printf("\t(^^^ %u NativePointerHolder subclasses)\n",
|
||||
(unsigned)(sizeof(S3ClassNames) / sizeof(const char *)));
|
||||
@ -2782,7 +2799,7 @@ static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){
|
||||
instance, or NULL on OOM.
|
||||
*/
|
||||
static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){
|
||||
JNIEnvCacheLine * const row = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const row = S3Global_JNIEnvCache_cache(env);
|
||||
if( !row->jFtsExt ){
|
||||
row->jFtsExt = new_NativePointerHolder_object(env, S3ClassNames.Fts5ExtensionApi,
|
||||
s3jni_ftsext());
|
||||
@ -3056,7 +3073,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase,
|
||||
jobject jIter, jobject jOutCol,
|
||||
jobject jOutOff){
|
||||
Fts5ExtDecl;
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
Fts5PhraseIter iter;
|
||||
int rc, iCol = 0, iOff = 0;
|
||||
s3jni_phraseIter_init(env, jc, jIter);
|
||||
@ -3073,7 +3090,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase,
|
||||
JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase,
|
||||
jobject jIter, jobject jOutCol){
|
||||
Fts5ExtDecl;
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
Fts5PhraseIter iter;
|
||||
int rc, iCol = 0;
|
||||
s3jni_phraseIter_init(env, jc, jIter);
|
||||
@ -3089,7 +3106,7 @@ JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase,
|
||||
JDECLFtsXA(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter,
|
||||
jobject jOutCol, jobject jOutOff){
|
||||
Fts5ExtDecl;
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
Fts5PhraseIter iter;
|
||||
int iCol = 0, iOff = 0;
|
||||
if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/;
|
||||
@ -3104,7 +3121,7 @@ JDECLFtsXA(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter,
|
||||
JDECLFtsXA(void,xPhraseNextColumn)(JENV_JSELF,jobject jCtx, jobject jIter,
|
||||
jobject jOutCol){
|
||||
Fts5ExtDecl;
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
Fts5PhraseIter iter;
|
||||
int iCol = 0;
|
||||
if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/;
|
||||
@ -3158,7 +3175,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi,
|
||||
JDECLFtsXA(jint,xQueryPhrase)(JENV_JSELF,jobject jFcx, jint iPhrase,
|
||||
jobject jCallback){
|
||||
Fts5ExtDecl;
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
struct s3jni_xQueryPhraseState s;
|
||||
jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
|
||||
if( !klazz ){
|
||||
@ -3247,7 +3264,7 @@ static jint s3jni_fts5_xTokenize(JENV_JSELF, const char *zClassName,
|
||||
jint tokFlags, jobject jFcx,
|
||||
jbyteArray jbaText, jobject jCallback){
|
||||
Fts5ExtDecl;
|
||||
JNIEnvCacheLine * const jc = S3Global_env_cache(env);
|
||||
JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env);
|
||||
struct s3jni_xQueryPhraseState s;
|
||||
int rc = 0;
|
||||
jbyte * const pText = JBA_TOC(jbaText);
|
||||
@ -3327,32 +3344,7 @@ JDECLFtsXA(jobject,xUserData)(JENV_JSELF,jobject jFcx){
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JNIEnv * const env, jclass self){
|
||||
struct JNIEnvCacheLine * row = 0;
|
||||
int i;
|
||||
for( i = 0; i < JNIEnvCache_SIZE; ++i ){
|
||||
row = &S3Global.envCache.lines[i];
|
||||
if(row->env == env){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( i==JNIEnvCache_SIZE ){
|
||||
//MARKER(("The given JNIEnv is not currently cached.\n"));
|
||||
return JNI_FALSE;
|
||||
}
|
||||
//MARKER(("Uncaching S3Global.envCache entry #%d.\n", i));
|
||||
assert(S3Global.envCache.used >= i);
|
||||
JNIEnvCacheLine_clear(row);
|
||||
/**
|
||||
Move all entries down one slot. memmove() would be faster. We'll
|
||||
eventually turn this cache into a dynamically-allocated linked
|
||||
list, anyway, so this part will go away.
|
||||
*/
|
||||
for( ++i ; i < JNIEnvCache_SIZE; ++i ){
|
||||
S3Global.envCache.lines[i-i] = S3Global.envCache.lines[i];
|
||||
}
|
||||
memset(&S3Global.envCache.lines[i], 0, sizeof(JNIEnvCacheLine));
|
||||
--S3Global.envCache.used;
|
||||
return JNI_TRUE;
|
||||
return S3Global_JNIEnvCache_uncache(env) ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
|
||||
|
||||
@ -3413,13 +3405,18 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jclass klaz
|
||||
//jclass const klazz = (*env)->GetObjectClass(env, sJni);
|
||||
const ConfigFlagEntry * pConfFlag;
|
||||
memset(&S3Global, 0, sizeof(S3Global));
|
||||
(void)S3Global_env_cache(env);
|
||||
assert( 1 == S3Global.envCache.used );
|
||||
assert( env == S3Global.envCache.lines[0].env );
|
||||
assert( 0 != S3Global.envCache.lines[0].globalClassObj );
|
||||
if( (*env)->GetJavaVM(env, &S3Global.jvm) ){
|
||||
(*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible.");
|
||||
return;
|
||||
}
|
||||
(void)S3Global_JNIEnvCache_cache(env);
|
||||
if( !S3Global.envCache.aHead ){
|
||||
(*env)->FatalError(env, "Could not allocate JNIEnv-specific cache.");
|
||||
return;
|
||||
}
|
||||
assert( 1 == S3Global.metrics.envCacheMisses );
|
||||
assert( env == S3Global.envCache.aHead->env );
|
||||
assert( 0 != S3Global.envCache.aHead->globalClassObj );
|
||||
|
||||
for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){
|
||||
char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I";
|
||||
|
@ -79,9 +79,22 @@ public final class SQLite3Jni {
|
||||
library again after that "should" re-initialize the cache on
|
||||
demand, but that's untested.
|
||||
|
||||
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. It 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.
|
||||
|
||||
Calling this from the main application thread is not strictly
|
||||
required but is "polite." Additional threads must call this
|
||||
before ending or they will leak cache entries in the C memory,
|
||||
before ending or they will leak cache entries in the C heap,
|
||||
which in turn may keep numerous Java-side global references
|
||||
active.
|
||||
|
||||
|
14
manifest
14
manifest
@ -1,5 +1,5 @@
|
||||
C Add\sSQLite3Jni.uncacheJniEnv(),\sa\sway\sfor\sJava\sthreads\sto\sclear\stheir\sthread-specific\scached\sstate\sfrom\sthe\sJNI\sbindings\swhen\sthey're\sabout\sto\sterminate\s(or\sare\sotherwise\sdone\susing\sthe\slibrary).
|
||||
D 2023-08-05T20:19:45.125
|
||||
C Refactor\sthe\sper-JNIEnv\scache\sfrom\sa\sfixed-size\sstatic\sarray\sto\sa\slinked\slist\sof\sdynamically-allocated\sentries.\sUncache\sall\sper-db\sstate\s(which\sis\snecessarily\sJNIEnv-specific)\swhen\sthe\scorresponding\sJNIEnv\sis\suncached.
|
||||
D 2023-08-05T21:35:58.833
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
|
||||
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
|
||||
F ext/jni/GNUmakefile bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a
|
||||
F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf
|
||||
F ext/jni/src/c/sqlite3-jni.c 17389f38639294b6ace3030e04a91f8b2e938e191f4c853075d4f55e94665a0c
|
||||
F ext/jni/src/c/sqlite3-jni.c a894cb1b6a7479d376d98f7df0e29a713d2b6b6cbc0d4543505b90eb38593592
|
||||
F ext/jni/src/c/sqlite3-jni.h cd9b6367a260f55a14833861ceb1d87cb729c5414ba5d26fbb8854b0f22c7249
|
||||
F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c
|
||||
F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1
|
||||
@ -249,7 +249,7 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 013f2b5fe569d0585a695f5cfa605a3b
|
||||
F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc
|
||||
F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
|
||||
F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46
|
||||
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 213e7dfae620767deb421020cd705a131fd8ad3774e2bc9461eab46d12bf240c
|
||||
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java f8fec24eea4ff9a62467648f377391506883eff4bedfc50d69d8cb3dc78d1ab9
|
||||
F ext/jni/src/org/sqlite/jni/Tester1.java 868b5ea60b788a43f8b15c1b642015341fed8856abb1bb74e2eb4845ade50a4e
|
||||
F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee
|
||||
F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d
|
||||
@ -2081,8 +2081,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 7d4ac44ec419ed0474bdb9d237b97660cf0d8faba8fe686f6a914d7bc04dfa3b
|
||||
R 4e66fd3de8c1a8416110ff65fc063c39
|
||||
P 7468f8761bece58f7ced3d112bbe2fb454432d9c54c9b96cedb5a15bc2926d0f
|
||||
R c28f39c95821b6209d9e0ded55b1b7ca
|
||||
U stephan
|
||||
Z de9fd1b4a7e3cbc6e1092ca5c70548cf
|
||||
Z 6d5410b49d781e986fb3ebb97da80215
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
@ -1 +1 @@
|
||||
7468f8761bece58f7ced3d112bbe2fb454432d9c54c9b96cedb5a15bc2926d0f
|
||||
9dd8b78419e19e88bc3fbff9bf200390b146b2461af2bb6b93d8467036619e33
|
Loading…
Reference in New Issue
Block a user