From: stephan Date: Tue, 22 Aug 2023 17:36:59 +0000 (+0000) Subject: JNI internal cleanups and correct two leaked db handles in test code. X-Git-Tag: version-3.44.0~305^2~14 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a7e3a1c09b9d0d6a8f4da623012d3dae0d8f64a7;p=thirdparty%2Fsqlite.git JNI internal cleanups and correct two leaked db handles in test code. FossilOrigin-Name: f927a30b5bba35991f472084ebaf02779e84c343a4e84f0efb3df7679ff212f8 --- diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index bce6a1a63c..8ae78ea3d8 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -348,15 +348,15 @@ enum { typedef struct S3JniNphClass S3JniNphClass; struct S3JniNphClass { volatile const S3NphRef * pRef /* Entry from S3NphRefs. */; - jclass klazz /* global ref to the concrete - NativePointerHolder subclass represented by - zClassName */; + jclass klazz /* global ref to the concrete + ** NativePointerHolder subclass represented by + ** zClassName */; volatile jmethodID midCtor /* klazz's no-arg constructor. Used by - new_NativePointerHolder_object(). */; + ** new_NativePointerHolder_object(). */; volatile jfieldID fidValue /* NativePointerHolder.nativePointer or - OutputPointer.T.value */; + ** OutputPointer.T.value */; volatile jfieldID fidAggCtx /* sqlite3_context::aggregateContext. Used only - by the sqlite3_context binding. */; + ** by the sqlite3_context binding. */; }; /** State for various hook callbacks. */ @@ -364,13 +364,13 @@ typedef struct S3JniHook S3JniHook; struct S3JniHook{ jobject jObj /* global ref to Java instance */; jmethodID midCallback /* callback method. Signature depends on - jObj's type */; + ** jObj's type */; jclass klazz /* global ref to jObj's class. Only needed - by hooks which have an xDestroy() method. - We can probably eliminate this and simply - do the class lookup at the same - (deferred) time we do the xDestroy() - lookup. */; + ** by hooks which have an xDestroy() method. + ** We can probably eliminate this and simply + ** do the class lookup at the same + ** (deferred) time we do the xDestroy() + ** lookup. */; }; /* @@ -380,7 +380,10 @@ struct S3JniHook{ */ typedef struct S3JniDb S3JniDb; struct S3JniDb { - JNIEnv *env /* The associated JNIEnv handle */; + JNIEnv *env /* Used for cleaning up all dbs owned by a given + ** thread, noting that this ownership is an artificial + ** one imposed by our threading constraints, not by + ** the core library. */; sqlite3 *pDb /* The associated db handle */; jobject jDb /* A global ref of the output object which gets returned from sqlite3_open(_v2)(). We need this in @@ -528,6 +531,8 @@ struct S3JniGlobalType { volatile unsigned nMutexPerDb /* number of times perDb.mutex was entered */; volatile unsigned nMutexAutoExt /* number of times autoExt.mutex was entered */; volatile unsigned nDestroy /* xDestroy() calls across all types */; + volatile unsigned nPdbAlloc /* Number of S3JniDb alloced. */; + volatile unsigned nPdbRecycled /* Number of S3JniDb reused. */; struct { /* Number of calls for each type of UDF callback. */ volatile unsigned nFunc; @@ -689,6 +694,16 @@ static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * } return jba; } +static JNIEnv * s3jni_get_env(void){ + JNIEnv * env = 0; + if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env, + JNI_VERSION_1_8) ){ + fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n"); + abort(); + } + return env; +} +#define LocalJniGetEnv JNIEnv * const env = s3jni_get_env() /** Uses the java.lang.String(byte[],Charset) constructor to create a @@ -705,7 +720,7 @@ static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc, const char * const z, int n){ jstring rv = NULL; - JNIEnv * const env = jc->env; + LocalJniGetEnv; if( 0==n || (n<0 && z && !z[0]) ){ /* Fast-track the empty-string case via the MUTF-8 API. We could hypothetically do this for any strings where n<4 and z is @@ -741,7 +756,7 @@ static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc, */ static char * s3jni_jstring_to_utf8(S3JniEnv * const jc, jstring jstr, int *nLen){ - JNIEnv * const env = jc->env; + LocalJniGetEnv; jbyteArray jba; jsize nBa; char *rv; @@ -893,7 +908,7 @@ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDest */ static void S3JniDb_set_aside(S3JniDb * const s){ if(s){ - JNIEnv * const env = s->env; + LocalJniGetEnv; MUTEX_PDB_ASSERT_LOCKED; //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); assert(s->pPrev != s); @@ -1087,8 +1102,6 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, MUTEX_PDB_ENTER; 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)); SJG.perDb.aFree = rv->pNext; assert(rv->pNext != rv); assert(rv->pPrev != rv); @@ -1099,11 +1112,13 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, rv->pNext->pPrev = 0; rv->pNext = 0; } + ++SJG.metrics.nPdbRecycled; }else{ rv = s3jni_malloc(env, sizeof(S3JniDb)); //MARKER(("state@%p for db allocating for db@%p from heap\n", rv, pDb)); if(rv){ memset(rv, 0, sizeof(S3JniDb)); + ++SJG.metrics.nPdbAlloc; } } if(rv){ @@ -1357,7 +1372,7 @@ static int encodingTypeIsValid(int eTextRep){ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, int nRhs, const void *rhs){ S3JniDb * const ps = pArg; - JNIEnv * env = ps->env; + LocalJniGetEnv; jint rc = 0; jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL; @@ -1380,56 +1395,17 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, /* Collation finalizer for use by the sqlite3 internals. */ static void CollationState_xDestroy(void *pArg){ S3JniDb * const ps = pArg; - S3JniHook_unref( ps->env, &ps->collation, 1 ); + S3JniHook_unref( s3jni_get_env(), &ps->collation, 1 ); } -/* State for sqlite3_result_java_object() and - sqlite3_value_java_object(). */ +/* +** State for sqlite3_result_java_object() and +** sqlite3_value_java_object(). +** +** TODO: this middle-man struct is no longer necessary. Conider +** removing it and passing around jObj itself instead. +*/ typedef struct { - /* The JNI docs say: - - https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html - - > The VM is guaranteed to pass the same interface pointer to a - native method when it makes multiple calls to the native method - from the same Java thread. - - Per the accompanying diagram, the "interface pointer" is the - pointer-to-pointer which is passed to all JNI calls - (`JNIEnv *env`), implying that we need to be caching that. The - verbiage "interface pointer" implies, however, that we should be - storing the dereferenced `(*env)` pointer. - - This posts claims it's unsafe to cache JNIEnv at all, even when - it's always used in the same thread: - - https://stackoverflow.com/questions/12420463 - - And this one seems to contradict that: - - https://stackoverflow.com/questions/13964608 - - For later reference: - - https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1242 - - https://developer.android.com/training/articles/perf-jni - - The later has the following say about caching: - - > If performance is important, it's useful to look the - [class/method ID] values up once and cache the results in your - native code. Because there is a limit of one JavaVM per - process, it's reasonable to store this data in a static local - structure. ... The class references, field IDs, and method IDs - are guaranteed valid until the class is unloaded. Classes are - only unloaded if all classes associated with a ClassLoader can - be garbage collected, which is rare but will not be impossible - in Android. Note however that the jclass is a class reference - and must be protected with a call to NewGlobalRef (see the next - section). - */ - JNIEnv * env; jobject jObj; } ResultJavaVal; @@ -1439,7 +1415,6 @@ typedef struct { static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal)); if(rv){ - rv->env = env; rv->jObj = jObj ? REF_G(jObj) : 0; } return rv; @@ -1448,7 +1423,8 @@ static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ static void ResultJavaVal_finalizer(void *v){ if(v){ ResultJavaVal * const rv = (ResultJavaVal*)v; - if(rv->jObj) (*(rv->env))->DeleteGlobalRef(rv->env, rv->jObj); + LocalJniGetEnv; + UNREF_G(rv->jObj); sqlite3_free(rv); } } @@ -1516,7 +1492,6 @@ typedef void (*udf_xFinal_f)(sqlite3_context*); */ typedef struct S3JniUdf S3JniUdf; struct S3JniUdf { - JNIEnv * env; /* env registered from */; jobject jObj /* SQLFunction instance */; jclass klazz /* jObj's class */; char * zFuncName /* Only for error reporting and debug logging */; @@ -1537,7 +1512,6 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ const char * zFV = /* signature for xFinal, xValue */ "(Lorg/sqlite/jni/sqlite3_context;)V"; memset(s, 0, sizeof(S3JniUdf)); - s->env = env; s->jObj = REF_G(jObj); s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); #define FGET(FuncName,FuncType,Field) \ @@ -1560,7 +1534,7 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ } static void S3JniUdf_free(S3JniUdf * s){ - JNIEnv * const env = s->env; + LocalJniGetEnv; if(env){ //MARKER(("UDF cleanup: %s\n", s->zFuncName)); s3jni_call_xDestroy(env, s->jObj, s->klazz); @@ -1589,10 +1563,6 @@ typedef struct { 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. - - TODO: see what we can do to optimize the - new_sqlite3_value_wrapper() call. e.g. find the ctor a single time - and call it here, rather than looking it up repeatedly. */ static int udf_args(JNIEnv *env, sqlite3_context * const cx, @@ -1652,9 +1622,9 @@ static int udf_xFSI(sqlite3_context* pCx, int argc, S3JniUdf * s, jmethodID xMethodID, const char * zFuncType){ - JNIEnv * const env = s->env; + LocalJniGetEnv; udf_jargs args = {0,0}; - int rc = udf_args(s->env, pCx, argc, argv, &args.jcx, &args.jargv); + int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv); //MARKER(("%s.%s() pCx = %p\n", s->zFuncName, zFuncType, pCx)); if(rc) return rc; //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); @@ -1679,8 +1649,8 @@ static int udf_xFSI(sqlite3_context* pCx, int argc, static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, jmethodID xMethodID, const char *zFuncType){ - JNIEnv * const env = s->env; - jobject jcx = new_sqlite3_context_wrapper(s->env, cx); + LocalJniGetEnv; + jobject jcx = new_sqlite3_context_wrapper(env, cx); int rc = 0; //MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx)); if(!jcx){ @@ -1768,16 +1738,6 @@ WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type) WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype) WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) -static JNIEnv * s3jni_get_env(void){ - JNIEnv * env = 0; - if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env, - JNI_VERSION_1_8) ){ - fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n"); - abort(); - } - return env; -} - /* Central auto-extension handler. */ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, const struct sqlite3_api_routines *ignored){ @@ -1958,7 +1918,7 @@ static int s3jni_busy_handler(void* pState, int n){ S3JniDb * const ps = (S3JniDb *)pState; int rc = 0; if( ps->busyHandler.jObj ){ - JNIEnv * const env = ps->env; + LocalJniGetEnv; rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, ps->busyHandler.midCallback, (jint)n); IFTHREW{ @@ -2079,7 +2039,7 @@ static unsigned int s3jni_utf16_strlen(void const * z){ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, int eTextRep, const void * z16Name){ S3JniDb * const ps = pState; - JNIEnv * const env = ps->env; + LocalJniGetEnv; unsigned int const nName = s3jni_utf16_strlen(z16Name); jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName); IFTHREW{ @@ -2184,7 +2144,7 @@ JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ - JNIEnv * const env = ps->env; + LocalJniGetEnv; int rc = isCommit ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, ps->commitHook.midCallback) @@ -2749,7 +2709,7 @@ JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArra static int s3jni_progress_handler_impl(void *pP){ S3JniDb * const ps = (S3JniDb *)pP; - JNIEnv * const env = ps->env; + LocalJniGetEnv; int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj, ps->progress.midCallback); IFTHREW{ @@ -3001,7 +2961,7 @@ JDECL(jobject,1rollback_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, const char*z2,const char*z3){ S3JniDb * const ps = pState; - JNIEnv * const env = ps->env; + LocalJniGetEnv; S3JniEnv * const jc = S3JniGlobal_env_cache(env); S3JniHook const * const pHook = &ps->authHook; jstring const s0 = z0 ? s3jni_utf8_to_jstring(jc, z0, -1) : 0; @@ -3119,7 +3079,7 @@ JDECL(jint,1shutdown)(JENV_CSELF){ s3jni_reset_auto_extension(env); MUTEX_ENV_ENTER; while( SJG.envCache.aHead ){ - S3JniGlobal_env_uncache( SJG.envCache.aHead->env ); + S3JniGlobal_env_uncache( env );//SJG.envCache.aHead->env ); } MUTEX_ENV_LEAVE; /* Do not clear S3JniGlobal.jvm: it's legal to call @@ -3151,7 +3111,7 @@ JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ S3JniDb * const ps = (S3JniDb *)pC; - JNIEnv * const env = ps->env; + LocalJniGetEnv; 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 */; @@ -3230,7 +3190,7 @@ JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, const char *zTable, sqlite3_int64 nRowid){ S3JniDb * const ps = pState; - JNIEnv * const env = ps->env; + LocalJniGetEnv; S3JniEnv * const jc = S3JniGlobal_env_cache(env); jstring jDbName; jstring jTable; @@ -3423,6 +3383,10 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ "\n\tautoExt %u container access\n", SJG.metrics.nMutexEnv, SJG.metrics.nMutexEnv2, SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt); + printf("S3JniDb: %u alloced (*%u = %u bytes), %u recycled\n", + SJG.metrics.nPdbAlloc, (unsigned) sizeof(S3JniDb), + (unsigned)(SJG.metrics.nPdbAlloc * sizeof(S3JniDb)), + SJG.metrics.nPdbRecycled); puts("Java-side UDF calls:"); #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); @@ -3465,7 +3429,6 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ State for binding Java-side FTS5 auxiliary functions. */ typedef struct { - JNIEnv * env; /* env registered from */; jobject jObj /* functor instance */; jclass klazz /* jObj's class */; jobject jUserData /* 2nd arg to JNI binding of @@ -3478,7 +3441,7 @@ typedef struct { } Fts5JniAux; static void Fts5JniAux_free(Fts5JniAux * const s){ - JNIEnv * const env = s->env; + LocalJniGetEnv; if(env){ /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/ s3jni_call_xDestroy(env, s->jObj, s->klazz); @@ -3503,7 +3466,6 @@ static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ "Lorg/sqlite/jni/sqlite3_context;" "[Lorg/sqlite/jni/sqlite3_value;)V"; memset(s, 0, sizeof(Fts5JniAux)); - s->env = env; s->jObj = REF_G(jObj); s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig); @@ -3639,14 +3601,14 @@ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, int argc, sqlite3_value **argv){ Fts5JniAux * const pAux = pApi->xUserData(pFts); - JNIEnv *env; jobject jpCx = 0; jobjectArray jArgv = 0; jobject jpFts = 0; jobject jFXA; int rc; + LocalJniGetEnv; + assert(pAux); - env = pAux->env; jFXA = s3jni_getFts5ExensionApi(env); if( !jFXA ) goto error_oom; jpFts = new_Fts5Context_wrapper(env, pFts); @@ -3700,8 +3662,11 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, typedef struct S3JniFts5AuxData S3JniFts5AuxData; +/* +** TODO: this middle-man struct is no longer necessary. Conider +** removing it and passing around jObj itself instead. +*/ struct S3JniFts5AuxData { - JNIEnv *env; jobject jObj; }; @@ -3709,7 +3674,7 @@ static void S3JniFts5AuxData_xDestroy(void *x){ if(x){ S3JniFts5AuxData * const p = x; if(p->jObj){ - JNIEnv *env = p->env; + LocalJniGetEnv; s3jni_call_xDestroy(env, p->jObj, 0); UNREF_G(p->jObj); } @@ -3872,7 +3837,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, guaranteed to be the same one passed to xQueryPhrase(). If it's not, we'll have to create a new wrapper object on every call. */ struct s3jni_xQueryPhraseState const * s = pData; - JNIEnv * const env = s->env; + LocalJniGetEnv; int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, SJG.fts5.jFtsExt, s->jFcx); IFTHREW{ @@ -3931,7 +3896,6 @@ JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ } return SQLITE_NOMEM; } - pAux->env = env; pAux->jObj = REF_G(jAux); rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux, S3JniFts5AuxData_xDestroy); @@ -3944,8 +3908,8 @@ JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, int nZ, int iStart, int iEnd){ int rc; + LocalJniGetEnv; struct s3jni_xQueryPhraseState * const s = p; - JNIEnv * const env = s->env; jbyteArray jba; if( s->tok.zPrev == z && s->tok.nPrev == nZ ){ jba = s->tok.jba; diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java index a2ab6a0f75..fcad273d3d 100644 --- a/ext/jni/src/org/sqlite/jni/AutoExtension.java +++ b/ext/jni/src/org/sqlite/jni/AutoExtension.java @@ -18,14 +18,23 @@ package org.sqlite.jni; */ public interface AutoExtension { /** - Must function as described for the sqlite3_auto_extension(), - with the caveat that the signature is more limited. + Must function as described for a sqlite3_auto_extension() + callback, with the caveat that the signature is more limited. As an exception (as it were) to the callbacks-must-not-throw - rule, AutoExtensions may do so and the exception's error message + rule, AutoExtensions may throw and the exception's error message will be set as the db's error string. - Results are undefined if db is closed by an auto-extension. + Hints for implementations: + + - Opening a database from an auto-extension handler will lead to + an endless recursion of the auto-handler triggering itself + indirectly for each newly-opened database. + + - If this routine is stateful, it is a good idea to make the + overridden method synchronized. + + - Results are undefined if db is closed by an auto-extension. */ int xEntryPoint(sqlite3 db); } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index dc34694185..cfca4fc77c 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -173,9 +173,6 @@ public final class SQLite3Jni { on multiple factors). See the AutoExtension class docs for more information. - - Achtung: it is as yet unknown whether auto extensions registered - from one JNIEnv (thread) can be safely called from another. */ public static native int sqlite3_auto_extension(@NotNull AutoExtension callback); @@ -212,9 +209,11 @@ public final class SQLite3Jni { ); - /** A level of indirection required to ensure that the input to the - C-level function of the same name is a NUL-terminated UTF-8 - string. */ + /** + A level of indirection required to ensure that the input to the + C-level function of the same name is a NUL-terminated UTF-8 + string. + */ private static native int sqlite3_bind_parameter_index( @NotNull sqlite3_stmt stmt, byte[] paramName ); @@ -226,6 +225,15 @@ public final class SQLite3Jni { return sqlite3_bind_parameter_index(stmt, utf8); } + /** + Works like the C-level sqlite3_bind_text() but (A) assumes + SQLITE_TRANSIENT for the final parameter and (B) behaves like + sqlite3_bind_null() if the data argument is null. + */ + private static native int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes + ); + public static int sqlite3_bind_text( @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data ){ @@ -242,15 +250,6 @@ public final class SQLite3Jni { : sqlite3_bind_text(stmt, ndx, data, data.length); } - /** - Works like the C-level sqlite3_bind_text() but (A) assumes - SQLITE_TRANSIENT for the final parameter and (B) behaves like - sqlite3_bind_null() if the data argument is null. - */ - private static native int sqlite3_bind_text( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes - ); - public static native int sqlite3_bind_zeroblob( @NotNull sqlite3_stmt stmt, int ndx, int n ); @@ -931,7 +930,8 @@ public final class SQLite3Jni { If maxLength (in bytes, not characters) is larger than text.length, it is silently truncated to text.length. If it is - negative, results are undefined. + negative, results are undefined. If text is null, the following + arguments are ignored. */ private static native void sqlite3_result_text64( @NotNull sqlite3_context cx, @Nullable byte[] text, @@ -939,8 +939,9 @@ public final class SQLite3Jni { ); /** - Cleans up all per-JNIEnv and per-db state managed by the library - then calls the C-native sqlite3_shutdown(). + Cleans up all per-JNIEnv and per-db state managed by the library, + as well as any registered auto-extensions, then calls the + C-native sqlite3_shutdown(). */ public static synchronized native int sqlite3_shutdown(); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 1543a80052..fe8bc542c6 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -87,9 +87,9 @@ public class Tester1 implements Runnable { ++metrics.dbOpen; sqlite3 db = out.take(); if( 0!=rc ){ - final String msg = db.getNativePointer()==0 - ? sqlite3_errstr(rc) - : sqlite3_errmsg(db); + final String msg = + null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db); + sqlite3_close(db); throw new RuntimeException("Opening db failed: "+msg); } affirm( null == out.get() ); @@ -428,6 +428,7 @@ public class Tester1 implements Runnable { sqlite3_bind_text(stmt, 1, "hell😃"); affirm( "SELECT 'hell😃'".equals(sqlite3_expanded_sql(stmt)) ); sqlite3_finalize(stmt); + sqlite3_close(db); } private void testCollation(){ @@ -500,14 +501,12 @@ public class Tester1 implements Runnable { rc = sqlite3_collation_needed(db, null); affirm( 0 == rc ); sqlite3_close_v2(db); + affirm( 0 == db.getNativePointer() ); affirm(xDestroyCalled.value); } private void testToUtf8(){ /** - Java docs seem contradictory, claiming to use "modified UTF-8" - encoding while also claiming to export using RFC 2279: - https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html Let's ensure that we can convert to standard UTF-8 in Java code @@ -1106,6 +1105,7 @@ public class Tester1 implements Runnable { execSql(db, "ATTACH ':memory' as foo"); affirm( 4==val.value /* ATTACH uses the same connection, not sub-connections. */ ); sqlite3_close(db); + db = null; affirm( sqlite3_cancel_auto_extension(ax) ); affirm( !sqlite3_cancel_auto_extension(ax) ); @@ -1116,7 +1116,7 @@ public class Tester1 implements Runnable { Exception err = null; toss.value = "Throwing from AutoExtension."; try{ - createNewDb(); + sqlite3_close(createNewDb()); }catch(Exception e){ err = e; } @@ -1169,9 +1169,11 @@ public class Tester1 implements Runnable { private void runTests(boolean fromThread) throws Exception { if(false) testCompileOption(); + testToUtf8(); test1(); testOpenDb1(); testOpenDb2(); + testCollation(); testPrepare123(); testBindFetchInt(); testBindFetchInt64(); @@ -1179,8 +1181,6 @@ public class Tester1 implements Runnable { testBindFetchText(); testBindFetchBlob(); testSql(); - testCollation(); - testToUtf8(); testStatus(); testUdf1(); testUdfJavaObject(); diff --git a/manifest b/manifest index 105f01ad91..94ab0dbcd7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Move\sthe\sJNI\sper-thread\scache\sof\sNativePointerHolder\srefs\sinto\sglobal\sspace.\sThis\sallows\sbetter-targeted\smutex\slocks\sand\sincidentally\seliminates\sthe\slagginess\sand\spost-run\shangs\sin\sTester1's\smulti-thread\smode\s(presumably\scaused\sby\sdeadlocks). -D 2023-08-22T15:30:35.368 +C JNI\sinternal\scleanups\sand\scorrect\stwo\sleaked\sdb\shandles\sin\stest\scode. +D 2023-08-22T17:36:59.339 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -235,10 +235,10 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile 30f0926a69edbd9e9932283ec8e4cea02b785f373395f2093dbbc6d65866a196 F ext/jni/README.md 975b35173debbbf3a4ab7166e14d2ffa2bacff9b6850414f09cc919805e81ba4 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c ad002976687e294936ce8f50b9efb1e531fa1d78a076b8a09089328082b48af4 +F ext/jni/src/c/sqlite3-jni.c b54056176060ef68dba31baaee43ad90a8ac3d4e7a477224377026110bb213ac 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 +F ext/jni/src/org/sqlite/jni/AutoExtension.java 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8 @@ -255,8 +255,8 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 8c1ad92c35bcc1b2f7256cf6e229b31340ed6d1a404d487f0a9adb28ba7fc332 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5c469585946b63592cafe134b01af0b9144a12131f22ea352e12f4c3ec70efb2 -F ext/jni/src/org/sqlite/jni/Tester1.java 63e1e4285a0f050580490323f656809bdadc0f1f28c4454f5cca82a6ccdfaf0f +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 0eea21f1015704e495b1a47aa9c7c90d081f51777981fe3f07760486aed092d8 +F ext/jni/src/org/sqlite/jni/Tester1.java b6be63a8e80c7362073f2a799719315a1459d1eff97cebb944b1309522758de2 F ext/jni/src/org/sqlite/jni/TesterFts5.java c729d5b3cb91888b7e2a3a3ef450852f184697df78721574f6c0bf9043e4b84c F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -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 7342bf578790e1a87c128a7c1c7745fe2e7c442890370feb160d406597d4d8ec -R 2848048c8acd75d9850655e629905695 +P e209f56a9745695aadc04418c7bebe62b79e38e5aee26c3248a30f73bfa460c2 +R 187f43ad9bb697fc679503d683587e56 U stephan -Z 76a56828e8164766c79107d6b6e04f3f +Z 1a62043be3662d747eb273c051b3d9b0 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4a981ac71b..296aadaa87 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e209f56a9745695aadc04418c7bebe62b79e38e5aee26c3248a30f73bfa460c2 \ No newline at end of file +f927a30b5bba35991f472084ebaf02779e84c343a4e84f0efb3df7679ff212f8 \ No newline at end of file