Factor out an unnecessary struct member. JNI sqlite3_shutdown() now frees up the various object-recycling bins. Doc touchups.

FossilOrigin-Name: bae4d022aad9bbeb78cb027ecad799af87afe331e697add44ec22297c873141d
This commit is contained in:
stephan 2023-08-27 09:12:50 +00:00
parent 60aca33a8b
commit 32a79760b5
4 changed files with 119 additions and 69 deletions

View File

@ -511,7 +511,7 @@ struct S3JniUdf {
jmethodID jmidxFinal /* Java ID of xFinal method */;
jmethodID jmidxValue /* Java ID of xValue method */;
jmethodID jmidxInverse /* Java ID of xInverse method */;
S3JniUdf * pNext /* Next entry in free-list. */;
S3JniUdf * pNext /* Next entry in SJG.udf.aFree. */;
};
#if !defined(SQLITE_JNI_OMIT_METRICS) && !defined(SQLITE_JNI_ENABLE_METRICS)
@ -584,25 +584,24 @@ struct S3JniGlobalType {
object. Used only for sanity checking. */;
} perDb;
struct {
S3JniUdf * aFree /* Head of the free-item list. Guarded by global
mutex. */;
S3JniUdf * aFree /* Head of the free-item list. Guarded by global
mutex. */;
} udf;
/*
** 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 */;
/**
The list of bound auto-extensions (Java-side:
org.sqlite.jni.auto_extension objects).
} g;
/*
** The list of Java-side auto-extensions
** (org.sqlite.jni.AutoExtensionCallback objects).
*/
struct {
S3JniAutoExtension *aExt /* The auto-extension list. It is
@ -661,7 +660,8 @@ struct S3JniGlobalType {
volatile unsigned nValue;
volatile unsigned nInverse;
} udf;
unsigned nMetrics /* Total number of mutex-locked metrics increments. */;
unsigned nMetrics /* Total number of mutex-locked
metrics increments. */;
#if S3JNI_METRICS_MUTEX
sqlite3_mutex * mutex;
#endif
@ -1124,11 +1124,37 @@ static void S3JniHook_localdup( JNIEnv * const env, S3JniHook const * const src,
S3JniMutex_S3JniDb_leave;
}
/*
** Clears all of s's state. Requires that that the caller has locked
** S3JniGlobal.perDb.mutex. Make sure to do anything needed with
** s->pNext and s->pPrev before calling this, as this clears them.
*/
static void S3JniDb_clear(JNIEnv * const env, S3JniDb * const s){
S3JniMutex_S3JniDb_assertLocker;
sqlite3_free( s->zMainDbName );
#define UNHOOK(MEMBER) S3JniHook_unref(env, &s->hooks.MEMBER, 0)
UNHOOK(auth);
UNHOOK(busyHandler);
UNHOOK(collation);
UNHOOK(collationNeeded);
UNHOOK(commit);
UNHOOK(progress);
UNHOOK(rollback);
UNHOOK(trace);
UNHOOK(update);
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
UNHOOK(preUpdate);
#endif
#undef UNHOOK
S3JniUnrefGlobal(s->jDb);
memset(s, 0, sizeof(S3JniDb));
}
/*
** Clears s's state and moves it to the free-list. Requires that that the
** caller has locked S3JniGlobal.perDb.mutex.
*/
static void S3JniDb_set_aside(JNIEnv * env, S3JniDb * const s){
static void S3JniDb_set_aside(JNIEnv * const env, S3JniDb * const s){
if( s ){
S3JniMutex_S3JniDb_enter;
assert(s->pPrev != s);
@ -1140,23 +1166,7 @@ static void S3JniDb_set_aside(JNIEnv * env, S3JniDb * const s){
assert(!s->pPrev);
SJG.perDb.aHead = s->pNext;
}
sqlite3_free( s->zMainDbName );
#define UNHOOK(MEMBER) S3JniHook_unref(env, &s->hooks.MEMBER, 0)
UNHOOK(auth);
UNHOOK(busyHandler);
UNHOOK(collation);
UNHOOK(collationNeeded);
UNHOOK(commit);
UNHOOK(progress);
UNHOOK(rollback);
UNHOOK(trace);
UNHOOK(update);
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
UNHOOK(preUpdate);
#endif
#undef UNHOOK
S3JniUnrefGlobal(s->jDb);
memset(s, 0, sizeof(S3JniDb));
S3JniDb_clear(env, s);
s->pNext = SJG.perDb.aFree;
if(s->pNext) s->pNext->pPrev = s;
SJG.perDb.aFree = s;
@ -1169,6 +1179,8 @@ static void S3JniDb_set_aside(JNIEnv * env, S3JniDb * const s){
** references the cache owns. Returns true if env was cached and false
** if it was not found in the cache. Ownership of the given object is
** passed over to this function, which makes it free for re-use.
**
** Requires that the Env mutex be locked.
*/
static int S3JniEnv_uncache(JNIEnv * const env){
struct S3JniEnv * row;
@ -1688,23 +1700,33 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
return s;
}
static void S3JniUdf_free(S3JniUdf * s){
S3JniDeclLocal_env;
/*
** Frees up all resources owned by s, clears its state, then either
** caches it for reuse (if cacheIt is true) or frees it. The former
** requires locking the global mutex, so it must not be held when this
** is called.
*/
static void S3JniUdf_free(JNIEnv * const env, S3JniUdf * const s,
int cacheIt){
assert( !s->pNext );
s3jni_call_xDestroy(env, s->jObj);
S3JniUnrefGlobal(s->jObj);
sqlite3_free(s->zFuncName);
assert( !s->pNext );
memset(s, 0, sizeof(*s));
S3JniMutex_Global_enter;
s->pNext = S3JniGlobal.udf.aFree;
S3JniGlobal.udf.aFree = s;
S3JniMutex_Global_leave;
if( cacheIt ){
S3JniMutex_Global_enter;
s->pNext = S3JniGlobal.udf.aFree;
S3JniGlobal.udf.aFree = s;
S3JniMutex_Global_leave;
}else{
sqlite3_free( s );
}
}
/* Finalizer for sqlite3_create_function() and friends. */
static void S3JniUdf_finalizer(void * s){
S3JniUdf_free((S3JniUdf*)s);
S3JniUdf_free(s3jni_env(), (S3JniUdf*)s, 1);
}
/*
@ -1712,14 +1734,14 @@ static void S3JniUdf_finalizer(void * s){
** (sqlite3_context*,int,sqlite3_value**).
*/
typedef struct {
jobject jcx /* sqlite3_context object */;
jobject jcx /* sqlite3_context */;
jobjectArray jargv /* sqlite3_value[] */;
} udf_jargs;
/**
Converts the given (cx, argc, argv) into arguments for the given
UDF, placing the result in the final argument. Returns 0 on
success, SQLITE_NOMEM on allocation error.
/*
** Converts the given (cx, argc, argv) into arguments for the given
** UDF, placing the result in the final argument. Returns 0 on
** success, SQLITE_NOMEM on allocation error.
*/
static int udf_args(JNIEnv *env,
sqlite3_context * const cx,
@ -1728,19 +1750,19 @@ static int udf_args(JNIEnv *env,
jobjectArray ja = 0;
jobject jcx = new_sqlite3_context_wrapper(env, cx);
jint i;
S3JniNphClass * const pNC =
S3JniGlobal_nph(env, &S3NphRefs.sqlite3_value);
*jCx = 0;
*jArgv = 0;
if( !jcx ) goto error_oom;
ja = (*env)->NewObjectArray(env, argc,
SJG.g.cObj,
NULL);
ja = (*env)->NewObjectArray(env, argc, pNC->klazz, NULL);
s3jni_oom_check( ja );
if( !ja ) goto error_oom;
for(i = 0; i < argc; ++i){
jobject jsv = new_sqlite3_value_wrapper(env, argv[i]);
if( !jsv ) goto error_oom;
(*env)->SetObjectArrayElement(env, ja, i, jsv);
S3JniUnrefLocal(jsv)/*array has a ref*/;
S3JniUnrefLocal(jsv)/*ja has a ref*/;
}
*jCx = jcx;
*jArgv = ja;
@ -2018,8 +2040,8 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
}
jc->pdbOpening = 0;
assert( !ps->pDb && "it's still being opened" );
ps->pDb = pDb;
assert( ps->jDb );
ps->pDb = pDb;
NativePointerHolder_set(env, &S3NphRefs.sqlite3, ps->jDb, pDb)
/* As of here, the Java/C connection is complete */;
for( i = 0; go && 0==rc; ++i ){
@ -2057,7 +2079,6 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
S3JniApi(sqlite3_auto_extension(),jint,1auto_1extension)(
JniArgsEnvClass, jobject jAutoExt
){
static int once = 0;
int i;
S3JniAutoExtension * ax;
int rc = 0;
@ -2065,16 +2086,18 @@ S3JniApi(sqlite3_auto_extension(),jint,1auto_1extension)(
if( !jAutoExt ) return SQLITE_MISUSE;
S3JniMutex_Ext_enter;
for( i = 0; i < SJG.autoExt.nExt; ++i ){
/* Look for match or first empty slot. */
/* Look for a match. */
ax = &SJG.autoExt.aExt[i];
if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
/* same object, so this is a no-op. */
S3JniMutex_Ext_leave;
return 0 /* this as a no-op. */;
return 0;
}
}
if( i == SJG.autoExt.nExt ){
assert( SJG.autoExt.nExt <= SJG.autoExt.nAlloc );
if( SJG.autoExt.nExt == SJG.autoExt.nAlloc ){
/* Allocate another slot. */
unsigned n = 1 + SJG.autoExt.nAlloc;
S3JniAutoExtension * const aNew =
s3jni_realloc( SJG.autoExt.aExt, n * sizeof(*ax) );
@ -2093,8 +2116,14 @@ S3JniApi(sqlite3_auto_extension(),jint,1auto_1extension)(
}
}
if( 0==rc ){
static int once = 0;
if( 0==once && ++once ){
rc = sqlite3_auto_extension( (void(*)(void))s3jni_run_java_auto_extensions );
rc = sqlite3_auto_extension(
(void(*)(void))s3jni_run_java_auto_extensions
/* Reminder: the JNI binding of sqlite3_reset_auto_extension()
** does not call the core-lib impl. It only clears Java-side
** auto-extensions. */
);
if( rc ){
assert( ax );
S3JniAutoExtension_clear(ax);
@ -2720,13 +2749,13 @@ S3JniApi(sqlite3_create_function() sqlite3_create_function_v2() sqlite3_create_w
if( UDF_UNKNOWN_TYPE==s->type ){
rc = s3jni_db_error(pDb, SQLITE_MISUSE,
"Cannot unambiguously determine function type.");
S3JniUdf_free(s);
S3JniUdf_free(env, s, 1);
goto error_cleanup;
}
zFuncName = s3jni_jstring_to_utf8(env,jFuncName,0);
if( !zFuncName ){
rc = SQLITE_NOMEM;
S3JniUdf_free(s);
S3JniUdf_free(env, s, 1);
goto error_cleanup;
}
if( UDF_WINDOW == s->type ){
@ -3815,16 +3844,38 @@ S3JniApi(sqlite3_shutdown(),jint,1shutdown)(
JniArgsEnvClass
){
s3jni_reset_auto_extension(env);
/* Free up env cache. */
S3JniMutex_Env_enter;
while( SJG.envCache.aHead ){
S3JniEnv_uncache( SJG.envCache.aHead->env );
}
S3JniMutex_Env_leave;
/* Free up S3JniUdf recycling bin. */
S3JniMutex_Global_enter;
while( S3JniGlobal.udf.aFree ){
S3JniUdf * const u = S3JniGlobal.udf.aFree;
S3JniGlobal.udf.aFree = u->pNext;
u->pNext = 0;
S3JniUdf_free(env, u, 0);
}
S3JniMutex_Global_leave;
/* Free up S3JniDb recycling bin. */
S3JniMutex_S3JniDb_enter;
while( S3JniGlobal.perDb.aFree ){
S3JniDb * const d = S3JniGlobal.perDb.aFree;
S3JniGlobal.perDb.aFree = d->pNext;
d->pNext = 0;
S3JniDb_clear(env, d);
sqlite3_free(d);
}
S3JniMutex_S3JniDb_leave;
#if 0
/*
** Is this a good idea? We will get rid of the perDb list once
** sqlite3 gets a per-db client state, at which point we won't have
** a central list of databases to close.
** Is automatically closing any still-open dbs a good idea? We will
** get rid of the perDb list once sqlite3 gets a per-db client
** state, at which point we won't have a central list of databases
** to close.
*/
S3JniMutex_S3JniDb_enter;
while( SJG.perDb.pHead ){
@ -3832,6 +3883,7 @@ S3JniApi(sqlite3_shutdown(),jint,1shutdown)(
}
S3JniMutex_S3JniDb_leave;
#endif
/* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */
return sqlite3_shutdown();
}
@ -3857,7 +3909,6 @@ S3JniApi(sqlite3_sql(),jstring,1sql)(
const char * zSql = 0;
zSql = sqlite3_sql(pStmt);
rv = s3jni_utf8_to_jstring(env, zSql, -1);
s3jni_oom_check(rv);
}
return rv;
}
@ -4972,8 +5023,6 @@ Java_org_sqlite_jni_SQLite3Jni_init(JniArgsEnvClass){
}
/* Grab references to various global classes and objects... */
SJG.g.cObj = S3JniRefGlobal((*env)->FindClass(env,"java/lang/Object"));
S3JniExceptionIsFatal("Error getting reference to Object class.");
SJG.g.cLong = S3JniRefGlobal((*env)->FindClass(env,"java/lang/Long"));
S3JniExceptionIsFatal("Error getting reference to Long class.");

View File

@ -1149,9 +1149,10 @@ public final class SQLite3Jni {
/**
Cleans up all stale per-thread state managed by the library, as
well as any registered auto-extensions, then calls the C-native
sqlite3_shutdown(). Calling this while database handles or
In addition to calling the C-level sqlite3_shutdown(), the JNI
binding also cleans up all stale per-thread state managed by the
library, as well as any registered auto-extensions and free up
various bits of memory. Calling this while database handles or
prepared statements are still active will leak resources. Trying
to use those objects after this routine is called invoked
undefined behavior.

View File

@ -1,5 +1,5 @@
C Factor\sout\sa\ssuperfluous\sJNI\sclass.\sDoc\sand\scode\sstyle\scleanups.
D 2023-08-27T08:10:59.533
C Factor\sout\san\sunnecessary\sstruct\smember.\sJNI\ssqlite3_shutdown()\snow\sfrees\sup\sthe\svarious\sobject-recycling\sbins.\sDoc\stouchups.
D 2023-08-27T09:12:50.224
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -236,7 +236,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3
F ext/jni/GNUmakefile 05a756bb7a579b7d6570cb590567e9f0d12270529a2e7e50523284e5a3684838
F ext/jni/README.md 1332b1fa27918bd5d9ca2d0d4f3ac3a6ab86b9e3699dc5bfe32904a027f3d2a9
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
F ext/jni/src/c/sqlite3-jni.c 9a07c95d570c1a46fff1db5383d0d247ef946b8c010a8e392045e350bf22e28f
F ext/jni/src/c/sqlite3-jni.c b0b86b214477ee1604caf358fd1f232ee6649d327527063dddc22216026c20d2
F ext/jni/src/c/sqlite3-jni.h a410d05ca47a676b75ff7b8980e75ad604ea15f3c29965f88989703abc2eeaf6
F ext/jni/src/org/sqlite/jni/AggregateFunction.java 0a5a74bea5ee12a99407e9432d0ca393525af912c2b0ca55c7ee5dbd019c00ef
F ext/jni/src/org/sqlite/jni/AuthorizerCallback.java c374bb76409cce7a0bdba94877706b59ac6127fa5d9e6af3e8058c99ce99c030
@ -262,7 +262,7 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c
F ext/jni/src/org/sqlite/jni/RollbackHookCallback.java be7f7a26d1102fb514d835e11198d51302af8053d97188bfb2e34c2133208568
F ext/jni/src/org/sqlite/jni/SQLFunction.java d060f302b2cc4cf7a4f5a6b2d36458a2e6fc9648374b5d09c36a43665af41207
F ext/jni/src/org/sqlite/jni/SQLite3CallbackProxy.java 13c4ea6f35871261eba63fa4117715515e0beecbdebfb879ec5b1f340ed36904
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java d640493f52c04e03f9c0525f16dcec6b718638ace22101c0ccfc014883a15b0b
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1ae75149383cd8b9fde175aa87855d18e425e32af2bc5e81bf56a95e92155195
F ext/jni/src/org/sqlite/jni/ScalarFunction.java 21301a947e49f0dd9c682dfe2cc8a6518226c837253dd791cd512f847eeca52c
F ext/jni/src/org/sqlite/jni/Tester1.java 37b46dc15ac8fbeb916dcf1f7771023d2be025d05422d725d5891935eda506ac
F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629
@ -2103,8 +2103,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 deed5797de65a25896e991a441f0d05eb92662536296485920fb081e84ad5d32
R 1286cf04d67bcef64c621ffe2a87fc44
P 0f37f27148dfa93ecc42381ad3455a9059285d1af2df027429044942dc4d861b
R e62c13b2db81e23df4398b126d6aa9e9
U stephan
Z 113b1efbfce3141a69775192a9b6c79b
Z aca6efed8b8717dbec6b53005671b2cd
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
0f37f27148dfa93ecc42381ad3455a9059285d1af2df027429044942dc4d861b
bae4d022aad9bbeb78cb027ecad799af87afe331e697add44ec22297c873141d