1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

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.