Move most of the per-JNIEnv global Java class refs into the global state, saving a bit of per-thread overhead.

FossilOrigin-Name: 7342bf578790e1a87c128a7c1c7745fe2e7c442890370feb160d406597d4d8ec
This commit is contained in:
stephan 2023-08-22 11:34:34 +00:00
parent 484f9bed4e
commit 0a102087d8
3 changed files with 254 additions and 266 deletions

View File

@ -386,15 +386,16 @@ typedef struct S3JniDb S3JniDb;
struct S3JniDb {
JNIEnv *env /* The associated JNIEnv handle */;
sqlite3 *pDb /* The associated db handle */;
jobject jDb /* A global ref of the object which was passed to
sqlite3_open(_v2)(). We need this in order to have
an object to pass to sqlite3_collation_needed()'s
callback, or else we have to dynamically create one
for that purpose, which would be fine except that
it would be a different instance (and maybe even a
different class) than the one the user may expect
to receive. */;
char * zMainDbName /* Holds any string allocated on behave of
jobject jDb /* A global ref of the output object which gets
returned from sqlite3_open(_v2)(). We need this in
order to have an object to pass to routines like
sqlite3_collation_needed()'s callback, or else we
have to dynamically create one for that purpose,
which would be fine except that it would be a
different instance (and maybe even a different
class) than the one the user may expect to
receive. */;
char * zMainDbName /* Holds the string allocated on behalf of
SQLITE_DBCONFIG_MAINDBNAME. */;
S3JniHook busyHandler;
S3JniHook collation;
@ -413,77 +414,48 @@ struct S3JniDb {
};
/*
** Cache for per-JNIEnv data.
**
** Potential TODO: move the jclass entries to global space because,
** per https://developer.android.com/training/articles/perf-jni:
**
** > once you have a valid jclass global reference you can use it from
** any attached thread.
**
** Whereas we cache new refs for each thread.
** Cache for per-JNIEnv (i.e. per-thread) data.
*/
typedef struct S3JniEnv S3JniEnv;
struct S3JniEnv {
JNIEnv *env /* env in which this cache entry was created */;
//! The various refs to global classes might be cacheable a single
// time globally. Information online seems inconsistent on that
// point.
struct {
jclass cObj /* global ref to java.lang.Object */;
jclass cLong /* global ref to java.lang.Long */;
jclass cString /* global ref to java.lang.String */;
jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */;
jmethodID ctorLong1 /* the Long(long) constructor */;
jmethodID ctorStringBA /* the String(byte[],Charset) constructor */;
jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
} g /* refs to global Java state */;
/*
** pdbOpening is used to coordinate the Java/DB connection of a
** being-open()'d db in the face of auto-extensions. "The problem"
** is that auto-extensions run before we can bind the C db to its
** Java representation, but auto-extensions require that binding. We
** handle this as follows:
**
** - In the JNI side of sqlite3_open(), allocate the Java side of
** that connection and set pdbOpening to point to that
** object. Note that it's per-thread, and we remain in that
** thread until after the auto-extensions are run.
**
** - Call sqlite3_open(), which triggers the auto-extension
** handler. That handler uses pdbOpening to connect the native
** db handle which it receives with pdbOpening.
**
** - When sqlite3_open() returns, check whether pdbOpening->pDb is
** NULL. If it isn't, auto-extension handling set it up. If it
** is, complete the Java/C binding unless sqlite3_open() returns
** a NULL db, in which case free pdbOpening.
** pdbOpening is used to coordinate the Java/DB connection of a
** being-open()'d db in the face of auto-extensions. "The problem"
** is that auto-extensions run before we can bind the C db to its
** Java representation, but auto-extensions require that binding. We
** handle this as follows:
**
** - In the JNI side of sqlite3_open(), allocate the Java side of
** that connection and set pdbOpening to point to that
** object. Note that it's per-thread, and we remain in that
** thread until after the auto-extensions are run, so we don't
** need to mutex-lock this.
**
** - Call sqlite3_open(), which triggers the auto-extension
** handler. That handler uses pdbOpening to connect the native
** db handle which it receives with pdbOpening.
**
** - When sqlite3_open() returns, check whether pdbOpening->pDb is
** NULL. If it isn't, auto-extension handling set it up. If it
** is, complete the Java/C binding unless sqlite3_open() returns
** a NULL db, in which case free pdbOpening.
*/
S3JniDb * pdbOpening;
#ifdef SQLITE_ENABLE_FTS5
jobject jFtsExt /* Global ref to Java singleton for the
Fts5ExtensionApi instance. */;
struct {
jclass klazz /* Global ref to the Fts5Phrase iter class */;
jfieldID fidA /* Fts5Phrase::a member */;
jfieldID fidB /* Fts5Phrase::b member */;
} jPhraseIter;
#endif
S3JniEnv * pPrev /* Previous entry in the linked list */;
S3JniEnv * pNext /* Next entry in the linked list */;
/*
** Cache of Java refs/IDs for NativePointerHolder subclasses.
** Cache of Java refs/IDs for NativePointerHolder subclasses. We
** "could" move this into S3JniGLobal but doing so would require
** adding a mutex for this state in several places. It might be a
** win, though, as it would eliminate many locks of of the
** S3JniGlobal.envCache.mutex.
*/
S3JniNphClass nph[NphCache_SIZE];
};
/*
** Whether auto extensions are feasible here is currently unknown due
** to...
**
** JNIEnv/threading issues. A db instance is mapped to a specific
** JNIEnv object but auto extensions may be added from any thread. In
** such contexts, which JNIEnv do we use for the JNI APIs?
** State for proxying sqlite3_auto_extension() in Java.
*/
typedef struct S3JniAutoExtension S3JniAutoExtension;
struct S3JniAutoExtension {
@ -494,7 +466,8 @@ struct S3JniAutoExtension {
/*
** Global state, e.g. caches and metrics.
*/
static struct {
typedef struct S3JniGlobalType S3JniGlobalType;
struct S3JniGlobalType {
/*
** According to: https://developer.ibm.com/articles/j-jni/
**
@ -523,6 +496,30 @@ static struct {
always have this set to the current JNIEnv
object. Used only for sanity checking. */;
} perDb;
/*
** Refs to global classes and methods. Obtained during static init
** and never released.
*/
struct {
jclass cObj /* global ref to java.lang.Object */;
jclass cLong /* global ref to java.lang.Long */;
jclass cString /* global ref to java.lang.String */;
jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */;
jmethodID ctorLong1 /* the Long(long) constructor */;
jmethodID ctorStringBA /* the String(byte[],Charset) constructor */;
jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
} g /* refs to global Java state */;
#ifdef SQLITE_ENABLE_FTS5
struct {
volatile jobject jFtsExt /* Global ref to Java singleton for the
Fts5ExtensionApi instance. */;
struct {
volatile jclass klazz /* Global ref to the Fts5Phrase iter class */;
jfieldID fidA /* Fts5Phrase::a member */;
jfieldID fidB /* Fts5Phrase::b member */;
} jPhraseIter;
} fts5;
#endif
/* Internal metrics. */
struct {
volatile unsigned envCacheHits;
@ -552,41 +549,43 @@ static struct {
int nExt /* number of active entries in pExt. */;
sqlite3_mutex * mutex /* mutex for manipulation/traversal of pExt */;
} autoExt;
} S3JniGlobal;
};
static S3JniGlobalType S3JniGlobal = {};
#define SJG S3JniGlobal
#define MUTEX_ASSERT_LOCKER_ENV \
assert( (env) == S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define MUTEX_ASSERT_NOTLOCKER_ENV \
assert( (env) != S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define MUTEX_ENV_ENTER \
MUTEX_ASSERT_NOTLOCKER_ENV; \
sqlite3_mutex_enter( S3JniGlobal.envCache.mutex ); \
/*MARKER(("Entered ENV mutex@%p %s.\n", env, __func__));*/ \
++S3JniGlobal.metrics.nMutexEnv; \
S3JniGlobal.envCache.locker = env
/*MARKER(("Entering ENV mutex@%p %s.\n", env, __func__));*/ \
sqlite3_mutex_enter( SJG.envCache.mutex ); \
++SJG.metrics.nMutexEnv; \
SJG.envCache.locker = env
#define MUTEX_ENV_LEAVE \
/*MARKER(("Leaving ENV mutex @%p %s.\n", env, __func__));*/ \
MUTEX_ASSERT_LOCKER_ENV; \
S3JniGlobal.envCache.locker = 0; \
sqlite3_mutex_leave( S3JniGlobal.envCache.mutex )
SJG.envCache.locker = 0; \
sqlite3_mutex_leave( SJG.envCache.mutex )
#define MUTEX_ASSERT_LOCKED_PDB \
assert( 0 != S3JniGlobal.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" )
assert( 0 != SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" )
#define MUTEX_PDB_ENTER \
/*MARKER(("Entering PerDb mutex@%p %s.\n", env, __func__));*/ \
sqlite3_mutex_enter( S3JniGlobal.perDb.mutex ); \
++S3JniGlobal.metrics.nMutexPerDb; \
S3JniGlobal.perDb.locker = env;
sqlite3_mutex_enter( SJG.perDb.mutex ); \
++SJG.metrics.nMutexPerDb; \
SJG.perDb.locker = env;
#define MUTEX_PDB_LEAVE \
/*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \
S3JniGlobal.perDb.locker = 0; \
sqlite3_mutex_leave( S3JniGlobal.perDb.mutex )
SJG.perDb.locker = 0; \
sqlite3_mutex_leave( SJG.perDb.mutex )
#define MUTEX_EXT_ENTER \
/*MARKER(("Entering autoExt mutex@%p %s.\n", env, __func__));*/ \
sqlite3_mutex_enter( S3JniGlobal.autoExt.mutex ); \
++S3JniGlobal.metrics.nMutexAutoExt
sqlite3_mutex_enter( SJG.autoExt.mutex ); \
++SJG.metrics.nMutexAutoExt
#define MUTEX_EXT_LEAVE \
/*MARKER(("Leaving autoExt mutex@%p %s.\n", env, __func__));*/ \
sqlite3_mutex_leave( S3JniGlobal.autoExt.mutex )
sqlite3_mutex_leave( SJG.autoExt.mutex )
#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env)
static void s3jni_oom(JNIEnv * const env){
@ -614,64 +613,29 @@ static void * s3jni_malloc(JNIEnv * const env, size_t n){
static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){
struct S3JniEnv * row;
MUTEX_ENV_ENTER;
row = S3JniGlobal.envCache.aHead;
row = SJG.envCache.aHead;
for( ; row; row = row->pNext ){
if( row->env == env ){
++S3JniGlobal.metrics.envCacheHits;
++SJG.metrics.envCacheHits;
MUTEX_ENV_LEAVE;
return row;
}
}
++S3JniGlobal.metrics.envCacheMisses;
row = S3JniGlobal.envCache.aFree;
++SJG.metrics.envCacheMisses;
row = SJG.envCache.aFree;
if( row ){
assert(!row->pPrev);
S3JniGlobal.envCache.aFree = row->pNext;
SJG.envCache.aFree = row->pNext;
if( row->pNext ) row->pNext->pPrev = 0;
}else{
row = s3jni_malloc(env, sizeof(S3JniEnv));
}
memset(row, 0, sizeof(*row));
row->pNext = S3JniGlobal.envCache.aHead;
row->pNext = SJG.envCache.aHead;
if(row->pNext) row->pNext->pPrev = row;
S3JniGlobal.envCache.aHead = row;
SJG.envCache.aHead = row;
row->env = env;
//MARKER(("Initalizing cache for JNIEnv@%p\n", env));
/* Grab references to various global classes and objects... */
row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object"));
EXCEPTION_IS_FATAL("Error getting reference to Object class.");
row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long"));
EXCEPTION_IS_FATAL("Error getting reference to Long class.");
row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong,
"<init>", "(J)V");
EXCEPTION_IS_FATAL("Error getting reference to Long constructor.");
row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String"));
EXCEPTION_IS_FATAL("Error getting reference to String class.");
row->g.ctorStringBA =
(*env)->GetMethodID(env, row->g.cString,
"<init>", "([BLjava/nio/charset/Charset;)V");
EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor.");
row->g.stringGetBytes =
(*env)->GetMethodID(env, row->g.cString,
"getBytes", "(Ljava/nio/charset/Charset;)[B");
EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset).");
{ /* StandardCharsets.UTF_8 */
jfieldID fUtf8;
jclass const klazzSC =
(*env)->FindClass(env,"java/nio/charset/StandardCharsets");
EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class.");
fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8",
"Ljava/nio/charset/Charset;");
EXCEPTION_IS_FATAL("Error getting StandardCharsets.UTF_8 field.");
row->g.oCharsetUtf8 =
REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8));
EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8.");
}
MUTEX_ENV_LEAVE;
return row;
}
@ -740,8 +704,8 @@ static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc,
if( n<0 ) n = sqlite3Strlen30(z);
jba = s3jni_new_jbyteArray(env, (unsigned const char *)z, (jsize)n);
if( jba ){
rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA,
jba, jc->g.oCharsetUtf8);
rv = (*env)->NewObject(env, SJG.g.cString, SJG.g.ctorStringBA,
jba, SJG.g.oCharsetUtf8);
UNREF_L(jba);
}
}
@ -771,8 +735,8 @@ static char * s3jni_jstring_to_utf8(S3JniEnv * const jc,
char *rv;
if(!jstr) return 0;
jba = (*env)->CallObjectMethod(env, jstr, jc->g.stringGetBytes,
jc->g.oCharsetUtf8);
jba = (*env)->CallObjectMethod(env, jstr, SJG.g.stringGetBytes,
SJG.g.oCharsetUtf8);
if( (*env)->ExceptionCheck(env) || !jba
/* order of these checks is significant for -Xlint:jni */ ) {
EXCEPTION_REPORT;
@ -885,7 +849,7 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){
method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V");
//MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method));
if(method){
++S3JniGlobal.metrics.nDestroy;
++SJG.metrics.nDestroy;
(*env)->CallVoidMethod(env, jObj, method);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback");
@ -926,9 +890,9 @@ static void S3JniDb_set_aside(S3JniDb * const 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(S3JniGlobal.perDb.aUsed == s){
else if(SJG.perDb.aUsed == s){
assert(!s->pPrev);
S3JniGlobal.perDb.aUsed = s->pNext;
SJG.perDb.aUsed = s->pNext;
}
sqlite3_free( s->zMainDbName );
#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY)
@ -943,13 +907,10 @@ static void S3JniDb_set_aside(S3JniDb * const s){
UNHOOK(busyHandler, 1);
#undef UNHOOK
UNREF_G(s->jDb);
#ifdef SQLITE_ENABLE_FTS5
UNREF_G(s->jFtsApi);
#endif
memset(s, 0, sizeof(S3JniDb));
s->pNext = S3JniGlobal.perDb.aFree;
s->pNext = SJG.perDb.aFree;
if(s->pNext) s->pNext->pPrev = s;
S3JniGlobal.perDb.aFree = s;
SJG.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));
}
@ -963,7 +924,7 @@ static void S3JniDb_free_for_env(JNIEnv *env){
S3JniDb * ps;
S3JniDb * pNext = 0;
MUTEX_PDB_ENTER;
ps = S3JniGlobal.perDb.aUsed;
ps = SJG.perDb.aUsed;
for( ; ps; ps = pNext ){
pNext = ps->pNext;
if(ps->env == env){
@ -972,7 +933,7 @@ static void S3JniDb_free_for_env(JNIEnv *env){
#endif
S3JniDb_set_aside(ps);
assert( pPrev ? pPrev->pNext==pNext : 1 );
assert( ps == S3JniGlobal.perDb.aFree );
assert( ps == SJG.perDb.aFree );
}
}
MUTEX_PDB_LEAVE;
@ -990,7 +951,7 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){
struct S3JniEnv * row;
int i;
MUTEX_ASSERT_LOCKER_ENV;
row = S3JniGlobal.envCache.aHead;
row = SJG.envCache.aHead;
for( ; row; row = row->pNext ){
if( row->env == env ){
break;
@ -1002,26 +963,18 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){
//MARKER(("Uncaching JNIEnv@%p\n", env));
if( row->pNext ) row->pNext->pPrev = row->pPrev;
if( row->pPrev ) row->pPrev->pNext = row->pNext;
if( S3JniGlobal.envCache.aHead == row ){
if( SJG.envCache.aHead == row ){
assert( !row->pPrev );
S3JniGlobal.envCache.aHead = row->pNext;
SJG.envCache.aHead = row->pNext;
}
S3JniDb_free_for_env(env);
UNREF_G(row->g.cObj);
UNREF_G(row->g.cLong);
UNREF_G(row->g.cString);
UNREF_G(row->g.oCharsetUtf8);
#ifdef SQLITE_ENABLE_FTS5
UNREF_G(row->jFtsExt);
UNREF_G(row->jPhraseIter.klazz);
#endif
for( i = 0; i < NphCache_SIZE; ++i ){
S3JniNphClass_clear(env, &row->nph[i]);
}
memset(row, 0, sizeof(S3JniEnv));
row->pNext = S3JniGlobal.envCache.aFree;
row->pNext = SJG.envCache.aFree;
if( row->pNext ) row->pNext->pPrev = row;
S3JniGlobal.envCache.aFree = row;
SJG.envCache.aFree = row;
return 1;
}
@ -1136,11 +1089,11 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb,
jobject jDb){
S3JniDb * rv;
MUTEX_PDB_ENTER;
if(S3JniGlobal.perDb.aFree){
rv = S3JniGlobal.perDb.aFree;
if(SJG.perDb.aFree){
rv = SJG.perDb.aFree;
//MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb));
//MARKER(("%p->pPrev@%p, pNext@%p\n", rv, rv->pPrev, rv->pNext));
S3JniGlobal.perDb.aFree = rv->pNext;
SJG.perDb.aFree = rv->pNext;
assert(rv->pNext != rv);
assert(rv->pPrev != rv);
assert(rv->pPrev ? (rv->pPrev!=rv->pNext) : 1);
@ -1158,8 +1111,8 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb,
}
}
if(rv){
rv->pNext = S3JniGlobal.perDb.aUsed;
S3JniGlobal.perDb.aUsed = rv;
rv->pNext = SJG.perDb.aUsed;
SJG.perDb.aUsed = rv;
if(rv->pNext){
assert(!rv->pNext->pPrev);
rv->pNext->pPrev = rv;
@ -1200,7 +1153,7 @@ static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){
S3JniDb * s = 0;
if(jDb || pDb){
MUTEX_PDB_ENTER;
s = S3JniGlobal.perDb.aUsed;
s = SJG.perDb.aUsed;
if(!pDb){
assert( jDb );
pDb = PtrGet_sqlite3(jDb);
@ -1556,6 +1509,7 @@ static jobject new_NativePointerHolder_object(JNIEnv * const env, S3NphRef const
assert(ctor);
rv = (*env)->NewObject(env, klazz, ctor);
EXCEPTION_IS_FATAL("No-arg constructor threw.");
OOM_CHECK(rv);
if(rv) NativePointerHolder_set(env, rv, pNative, pRef);
return rv;
}
@ -1680,7 +1634,7 @@ static int udf_args(JNIEnv *env,
*jArgv = 0;
if(!jcx) goto error_oom;
ja = (*env)->NewObjectArray(env, argc,
S3JniGlobal_env_cache(env)->g.cObj,
SJG.g.cObj,
NULL);
if(!ja) goto error_oom;
for(i = 0; i < argc; ++i){
@ -1779,29 +1733,29 @@ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
static void udf_xFunc(sqlite3_context* cx, int argc,
sqlite3_value** argv){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
++S3JniGlobal.metrics.udf.nFunc;
++SJG.metrics.udf.nFunc;
udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc");
}
static void udf_xStep(sqlite3_context* cx, int argc,
sqlite3_value** argv){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
++S3JniGlobal.metrics.udf.nStep;
++SJG.metrics.udf.nStep;
udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep");
}
static void udf_xFinal(sqlite3_context* cx){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
++S3JniGlobal.metrics.udf.nFinal;
++SJG.metrics.udf.nFinal;
udf_xFV(cx, s, s->jmidxFinal, "xFinal");
}
static void udf_xValue(sqlite3_context* cx){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
++S3JniGlobal.metrics.udf.nValue;
++SJG.metrics.udf.nValue;
udf_xFV(cx, s, s->jmidxValue, "xValue");
}
static void udf_xInverse(sqlite3_context* cx, int argc,
sqlite3_value** argv){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
++S3JniGlobal.metrics.udf.nInverse;
++SJG.metrics.udf.nInverse;
udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse");
}
@ -1845,7 +1799,7 @@ WRAP_INT_SVALUE(1value_1type, sqlite3_value_type)
static JNIEnv * s3jni_get_env(void){
JNIEnv * env = 0;
if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env,
if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env,
JNI_VERSION_1_8) ){
fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n");
abort();
@ -1861,7 +1815,7 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
JNIEnv * env = 0;
S3JniDb * ps;
S3JniEnv * jc;
if( 0==S3JniGlobal.autoExt.nExt ) return 0;
if( 0==SJG.autoExt.nExt ) return 0;
env = s3jni_get_env();
jc = S3JniGlobal_env_cache(env);
ps = jc->pdbOpening;
@ -1875,11 +1829,11 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
for( i = 0; go && 0==rc; ++i ){
S3JniAutoExtension const * ax;
MUTEX_EXT_ENTER;
if( i >= S3JniGlobal.autoExt.nExt ){
if( i >= SJG.autoExt.nExt ){
ax = 0;
go = 0;
}else{
ax = &S3JniGlobal.autoExt.pExt[i];
ax = &SJG.autoExt.pExt[i];
}
MUTEX_EXT_LEAVE;
if( ax && ax->jObj ){
@ -1907,30 +1861,30 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){
if( !jAutoExt ) return SQLITE_MISUSE;
MUTEX_EXT_ENTER;
for( i = 0; i < S3JniGlobal.autoExt.nExt; ++i ){
for( i = 0; i < SJG.autoExt.nExt; ++i ){
/* Look for match or first empty slot. */
ax = &S3JniGlobal.autoExt.pExt[i];
ax = &SJG.autoExt.pExt[i];
if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
MUTEX_EXT_LEAVE;
return 0 /* this as a no-op. */;
}
}
if(i == S3JniGlobal.autoExt.nExt ){
assert( S3JniGlobal.autoExt.nExt <= S3JniGlobal.autoExt.nAlloc );
if( S3JniGlobal.autoExt.nExt == S3JniGlobal.autoExt.nAlloc ){
unsigned n = 1 + S3JniGlobal.autoExt.nAlloc;
if(i == SJG.autoExt.nExt ){
assert( SJG.autoExt.nExt <= SJG.autoExt.nAlloc );
if( SJG.autoExt.nExt == SJG.autoExt.nAlloc ){
unsigned n = 1 + SJG.autoExt.nAlloc;
S3JniAutoExtension * const aNew =
sqlite3_realloc( S3JniGlobal.autoExt.pExt,
sqlite3_realloc( SJG.autoExt.pExt,
n * sizeof(S3JniAutoExtension) );
if( !aNew ){
rc = SQLITE_NOMEM;
}else{
S3JniGlobal.autoExt.pExt = aNew;
++S3JniGlobal.autoExt.nAlloc;
SJG.autoExt.pExt = aNew;
++SJG.autoExt.nAlloc;
}
}
if( 0==rc ){
ax = &S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nExt];
ax = &SJG.autoExt.pExt[SJG.autoExt.nExt];
rc = S3JniAutoExtension_init(env, ax, jAutoExt);
assert( rc ? 0==ax->jObj : 0!=ax->jObj );
}
@ -1944,7 +1898,7 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){
}
}
if( 0==rc ){
++S3JniGlobal.autoExt.nExt;
++SJG.autoExt.nExt;
}
}
MUTEX_EXT_LEAVE;
@ -2079,16 +2033,16 @@ JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){
int i;
MUTEX_EXT_ENTER;
/* This algo mirrors the one in the core. */
for( i = S3JniGlobal.autoExt.nExt-1; i >= 0; --i ){
ax = &S3JniGlobal.autoExt.pExt[i];
for( i = SJG.autoExt.nExt-1; i >= 0; --i ){
ax = &SJG.autoExt.pExt[i];
if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
S3JniAutoExtension_clear(env, ax);
/* Move final entry into this slot. */
--S3JniGlobal.autoExt.nExt;
*ax = S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nExt];
memset(&S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nExt], 0,
--SJG.autoExt.nExt;
*ax = SJG.autoExt.pExt[SJG.autoExt.nExt];
memset(&SJG.autoExt.pExt[SJG.autoExt.nExt], 0,
sizeof(S3JniAutoExtension));
assert(! S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nExt].jObj );
assert(! SJG.autoExt.pExt[SJG.autoExt.nExt].jObj );
rc = JNI_TRUE;
break;
}
@ -2874,10 +2828,10 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){
static void s3jni_reset_auto_extension(JNIEnv *env){
int i;
MUTEX_EXT_ENTER;
for( i = 0; i < S3JniGlobal.autoExt.nExt; ++i ){
S3JniAutoExtension_clear( env, &S3JniGlobal.autoExt.pExt[i] );
for( i = 0; i < SJG.autoExt.nExt; ++i ){
S3JniAutoExtension_clear( env, &SJG.autoExt.pExt[i] );
}
S3JniGlobal.autoExt.nExt = 0;
SJG.autoExt.nExt = 0;
MUTEX_EXT_LEAVE;
}
@ -3189,8 +3143,8 @@ JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){
JDECL(jint,1shutdown)(JENV_CSELF){
s3jni_reset_auto_extension(env);
MUTEX_ENV_ENTER;
while( S3JniGlobal.envCache.aHead ){
S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env );
while( SJG.envCache.aHead ){
S3JniGlobal_env_uncache( SJG.envCache.aHead->env );
}
MUTEX_ENV_LEAVE;
/* Do not clear S3JniGlobal.jvm: it's legal to call
@ -3237,7 +3191,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
createStmt = 1;
break;
case SQLITE_TRACE_PROFILE:
jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1,
jX = (*env)->NewObject(env, SJG.g.cLong, SJG.g.ctorLong1,
(jlong)*((sqlite3_int64*)pX));
// hmm. ^^^ (*pX) really is zero.
// MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX)));
@ -3471,6 +3425,8 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){
puts("sizeofs:");
#define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T))
SO(void*);
SO(jmethodID);
SO(jfieldID);
SO(S3JniEnv);
SO(S3JniHook);
SO(S3JniDb);
@ -3482,18 +3438,18 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){
SO(S3JniUdf);
printf("Cache info:\n");
printf("\tJNIEnv cache %u misses, %u hits\n",
S3JniGlobal.metrics.envCacheMisses,
S3JniGlobal.metrics.envCacheHits);
SJG.metrics.envCacheMisses,
SJG.metrics.envCacheHits);
printf("Mutex entry:\n\t%u env\n\t%u perDb\n\t%u autoExt\n",
S3JniGlobal.metrics.nMutexEnv,
S3JniGlobal.metrics.nMutexPerDb,
S3JniGlobal.metrics.nMutexAutoExt);
SJG.metrics.nMutexEnv,
SJG.metrics.nMutexPerDb,
SJG.metrics.nMutexAutoExt);
puts("Java-side UDF calls:");
#define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3JniGlobal.metrics.udf.n##T)
#define UDF(T) printf("\t%-8s = %u\n", "x" #T, SJG.metrics.udf.n##T)
UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse);
#undef UDF
printf("xDestroy calls across all callback types: %u\n",
S3JniGlobal.metrics.nDestroy);
SJG.metrics.nDestroy);
#undef SO
}
@ -3598,13 +3554,20 @@ 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){
S3JniEnv * const row = S3JniGlobal_env_cache(env);
if( !row->jFtsExt ){
row->jFtsExt = new_NativePointerHolder_object(env, &S3NphRefs.Fts5ExtensionApi,
s3jni_ftsext());
if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt);
if( !SJG.fts5.jFtsExt ){
jobject pNPH = new_NativePointerHolder_object(
env, &S3NphRefs.Fts5ExtensionApi, s3jni_ftsext()
);
MUTEX_ENV_ENTER;
if( pNPH ){
if( !SJG.fts5.jFtsExt ){
SJG.fts5.jFtsExt = REF_G(pNPH);
}
UNREF_L(pNPH);
}
MUTEX_ENV_LEAVE;
}
return row->jFtsExt;
return SJG.fts5.jFtsExt;
}
/*
@ -3820,41 +3783,28 @@ JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){
return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx));
}
/**
Initializes jc->jPhraseIter if it needed it.
*/
static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnv * const jc,
jobject jIter){
if(!jc->jPhraseIter.klazz){
jclass klazz = (*env)->GetObjectClass(env, jIter);
jc->jPhraseIter.klazz = REF_G(klazz);
jc->jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J");
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field.");
jc->jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "a", "J");
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field.");
}
}
/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */
static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnv const * const jc,
Fts5PhraseIter const * const pSrc,
jobject jIter){
assert(jc->jPhraseIter.klazz);
(*env)->SetLongField(env, jIter, jc->jPhraseIter.fidA, (jlong)pSrc->a);
static void s3jni_phraseIter_NToJ(JNIEnv *const env,
Fts5PhraseIter const * const pSrc,
jobject jIter){
S3JniGlobalType * const g = &S3JniGlobal;
assert(g->fts5.jPhraseIter.klazz);
(*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidA, (jlong)pSrc->a);
EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field.");
(*env)->SetLongField(env, jIter, jc->jPhraseIter.fidB, (jlong)pSrc->b);
(*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB, (jlong)pSrc->b);
EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.b field.");
}
/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */
static void s3jni_phraseIter_JToN(JNIEnv *const env, S3JniEnv const * const jc,
jobject jIter, Fts5PhraseIter * const pDest){
assert(jc->jPhraseIter.klazz);
static void s3jni_phraseIter_JToN(JNIEnv *const env, jobject jIter,
Fts5PhraseIter * const pDest){
S3JniGlobalType * const g = &S3JniGlobal;
assert(g->fts5.jPhraseIter.klazz);
pDest->a =
(const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidA);
(const unsigned char *)(*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA);
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field.");
pDest->b =
(const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidB);
(const unsigned char *)(*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidB);
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field.");
}
@ -3862,16 +3812,14 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase,
jobject jIter, jobject jOutCol,
jobject jOutOff){
Fts5ExtDecl;
S3JniEnv * const jc = S3JniGlobal_env_cache(env);
Fts5PhraseIter iter;
int rc, iCol = 0, iOff = 0;
s3jni_phraseIter_init(env, jc, jIter);
rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase,
&iter, &iCol, &iOff);
if( 0==rc ){
OutputPointer_set_Int32(env, jOutCol, iCol);
OutputPointer_set_Int32(env, jOutOff, iOff);
s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
s3jni_phraseIter_NToJ(env, &iter, jIter);
}
return rc;
}
@ -3879,15 +3827,13 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase,
JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase,
jobject jIter, jobject jOutCol){
Fts5ExtDecl;
S3JniEnv * const jc = S3JniGlobal_env_cache(env);
Fts5PhraseIter iter;
int rc, iCol = 0;
s3jni_phraseIter_init(env, jc, jIter);
rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase,
&iter, &iCol);
if( 0==rc ){
OutputPointer_set_Int32(env, jOutCol, iCol);
s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
s3jni_phraseIter_NToJ(env, &iter, jIter);
}
return rc;
}
@ -3895,29 +3841,26 @@ JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase,
JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter,
jobject jOutCol, jobject jOutOff){
Fts5ExtDecl;
S3JniEnv * const jc = S3JniGlobal_env_cache(env);
Fts5PhraseIter iter;
int iCol = 0, iOff = 0;
if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/;
s3jni_phraseIter_JToN(env, jc, jIter, &iter);
fext->xPhraseNext(PtrGet_Fts5Context(jCtx),
&iter, &iCol, &iOff);
if(!SJG.fts5.jPhraseIter.klazz) return /*SQLITE_MISUSE*/;
s3jni_phraseIter_JToN(env, jIter, &iter);
fext->xPhraseNext(PtrGet_Fts5Context(jCtx), &iter, &iCol, &iOff);
OutputPointer_set_Int32(env, jOutCol, iCol);
OutputPointer_set_Int32(env, jOutOff, iOff);
s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
s3jni_phraseIter_NToJ(env, &iter, jIter);
}
JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter,
jobject jOutCol){
Fts5ExtDecl;
S3JniEnv * const jc = S3JniGlobal_env_cache(env);
Fts5PhraseIter iter;
int iCol = 0;
if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/;
s3jni_phraseIter_JToN(env, jc, jIter, &iter);
if(!SJG.fts5.jPhraseIter.klazz) return /*SQLITE_MISUSE*/;
s3jni_phraseIter_JToN(env, jIter, &iter);
fext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol);
OutputPointer_set_Int32(env, jOutCol, iCol);
s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
s3jni_phraseIter_NToJ(env, &iter, jIter);
}
@ -3952,7 +3895,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi,
struct s3jni_xQueryPhraseState const * s = pData;
JNIEnv * const env = s->env;
int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
s->jc->jFtsExt, s->jFcx);
SJG.fts5.jFtsExt, s->jFcx);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase callback");
EXCEPTION_CLEAR;
@ -4391,6 +4334,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){
{0,0}
};
jfieldID fieldId;
jclass klazz;
const ConfigFlagEntry * pConfFlag;
if( 0==sqlite3_threadsafe() ){
@ -4398,26 +4342,70 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){
return;
}
memset(&S3JniGlobal, 0, sizeof(S3JniGlobal));
if( (*env)->GetJavaVM(env, &S3JniGlobal.jvm) ){
if( (*env)->GetJavaVM(env, &SJG.jvm) ){
(*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible.");
return;
}
S3JniGlobal.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
OOM_CHECK( S3JniGlobal.envCache.mutex );
S3JniGlobal.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
OOM_CHECK( S3JniGlobal.perDb.mutex );
S3JniGlobal.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
OOM_CHECK( S3JniGlobal.autoExt.mutex );
/* Grab references to various global classes and objects... */
SJG.g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object"));
EXCEPTION_IS_FATAL("Error getting reference to Object class.");
SJG.g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long"));
EXCEPTION_IS_FATAL("Error getting reference to Long class.");
SJG.g.ctorLong1 = (*env)->GetMethodID(env, SJG.g.cLong,
"<init>", "(J)V");
EXCEPTION_IS_FATAL("Error getting reference to Long constructor.");
SJG.g.cString = REF_G((*env)->FindClass(env,"java/lang/String"));
EXCEPTION_IS_FATAL("Error getting reference to String class.");
SJG.g.ctorStringBA =
(*env)->GetMethodID(env, SJG.g.cString,
"<init>", "([BLjava/nio/charset/Charset;)V");
EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor.");
SJG.g.stringGetBytes =
(*env)->GetMethodID(env, SJG.g.cString,
"getBytes", "(Ljava/nio/charset/Charset;)[B");
EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset).");
{ /* StandardCharsets.UTF_8 */
jfieldID fUtf8;
jclass const klazzSC =
(*env)->FindClass(env,"java/nio/charset/StandardCharsets");
EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class.");
fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8",
"Ljava/nio/charset/Charset;");
EXCEPTION_IS_FATAL("Error getting StandardCharsets.UTF_8 field.");
SJG.g.oCharsetUtf8 =
REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8));
EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8.");
}
#ifdef SQLITE_ENABLE_FTS5
klazz = (*env)->FindClass(env, "org/sqlite/jni/Fts5PhraseIter");
EXCEPTION_IS_FATAL("Error getting reference to org.sqlite.jni.Fts5PhraseIter.");
SJG.fts5.jPhraseIter.klazz = REF_G(klazz);
SJG.fts5.jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J");
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field.");
SJG.fts5.jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "a", "J");
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field.");
#endif
SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
OOM_CHECK( SJG.envCache.mutex );
SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
OOM_CHECK( SJG.perDb.mutex );
SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
OOM_CHECK( SJG.autoExt.mutex );
#if 0
/* Just for sanity checking... */
(void)S3JniGlobal_env_cache(env);
if( !S3JniGlobal.envCache.aHead ){
if( !SJG.envCache.aHead ){
(*env)->FatalError(env, "Could not allocate JNIEnv-specific cache.");
return;
}
assert( 1 == S3JniGlobal.metrics.envCacheMisses );
assert( env == S3JniGlobal.envCache.aHead->env );
assert( 0 != S3JniGlobal.envCache.aHead->g.cObj );
assert( 1 == SJG.metrics.envCacheMisses );
assert( env == SJG.envCache.aHead->env );
#endif
for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){

View File

@ -1,5 +1,5 @@
C Minor\sJNI\scleanups.
D 2023-08-21T23:45:19.079
C Move\smost\sof\sthe\sper-JNIEnv\sglobal\sJava\sclass\srefs\sinto\sthe\sglobal\sstate,\ssaving\sa\sbit\sof\sper-thread\soverhead.
D 2023-08-22T11:34:34.096
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -235,7 +235,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3
F ext/jni/GNUmakefile 4849b0ac41c3a92777aebf0ec3d51c2be7c78d3ea9b91ece03ade6f9fa13d99a
F ext/jni/README.md 975b35173debbbf3a4ab7166e14d2ffa2bacff9b6850414f09cc919805e81ba4
F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3
F ext/jni/src/c/sqlite3-jni.c 77ff2a7d608737aef5734d60881cd167d4b5916bcca8669067aa13afd641ba6e
F ext/jni/src/c/sqlite3-jni.c 96252788a8eea13e8da997103c05b8c1d2c878eb2c5a71a123baeb2bd5e91027
F ext/jni/src/c/sqlite3-jni.h 8b0ab1a3f0f92b75d4ff50db4a88b66a137cfb561268eb15bb3993ed174dbb74
F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892
F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093
@ -2092,8 +2092,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 0a84131008a2e7886dac64a3545dea634811f6eac2b90885ec9c61ed1e6544c3
R e61cca732e3141bc7655ea7dcc403579
P b88910aaaaaaa0936974379bb3eb8a5a3a634395b14e67cc9030f8a520f471f1
R 76968b303e9bd6c480af577a480bf905
U stephan
Z 1c9a32335d26ec39640641e263699a74
Z edf907aa4738f3b50b53a088953efc54
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
b88910aaaaaaa0936974379bb3eb8a5a3a634395b14e67cc9030f8a520f471f1
7342bf578790e1a87c128a7c1c7745fe2e7c442890370feb160d406597d4d8ec