]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
JNI internal cleanups and correct two leaked db handles in test code.
authorstephan <stephan@noemail.net>
Tue, 22 Aug 2023 17:36:59 +0000 (17:36 +0000)
committerstephan <stephan@noemail.net>
Tue, 22 Aug 2023 17:36:59 +0000 (17:36 +0000)
FossilOrigin-Name: f927a30b5bba35991f472084ebaf02779e84c343a4e84f0efb3df7679ff212f8

ext/jni/src/c/sqlite3-jni.c
ext/jni/src/org/sqlite/jni/AutoExtension.java
ext/jni/src/org/sqlite/jni/SQLite3Jni.java
ext/jni/src/org/sqlite/jni/Tester1.java
manifest
manifest.uuid

index bce6a1a63c9ca992b39ea1670b8dafd8e0a213d6..8ae78ea3d81daf7945e07a1b63b209bd7f7216e6 100644 (file)
@@ -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;
index a2ab6a0f7554c26efc3eb22f7ec23d25db4d57c8..fcad273d3dac9e72fad08527e5ef7ce54ff12568 100644 (file)
@@ -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);
 }
index dc346941854627349fdefe5af8aca6d9b6d03615..cfca4fc77c15ca7fd8c234128910d301e922946d 100644 (file)
@@ -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();
 
index 1543a8005209447b0fd6bb56182efdc18134f1e4..fe8bc542c67be41aea4a9b016b9b8148b9c867f7 100644 (file)
@@ -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();
index 105f01ad919f4f1962b7301a98d92780f91f6adb..94ab0dbcd7e1bf8640408671646c2b8d3da87aec 100644 (file)
--- 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.
index 4a981ac71bc32e32bcc6e0849455103778f3ac5b..296aadaa8736a15ac763c1cdc05dfc503c7d9035 100644 (file)
@@ -1 +1 @@
-e209f56a9745695aadc04418c7bebe62b79e38e5aee26c3248a30f73bfa460c2
\ No newline at end of file
+f927a30b5bba35991f472084ebaf02779e84c343a4e84f0efb3df7679ff212f8
\ No newline at end of file