From 0c07549fd6b88c2b62b8c49c6409ee2e784bf239 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 12 Aug 2023 23:47:58 +0000 Subject: [PATCH 01/37] Bind sqlite3_interrupt() and sqlite3_is_interrupted() to JNI but with caveats regarding mutexing of the JNIEnv cache. Add a loud warning to the JNI 'dist' target that it should be built with JDK8 (a.k.a. Java 1.8) for compatibility reasons. FossilOrigin-Name: fbf99a2423dd20e4544bdeea85f714e9368ce3b92fefe97efb39a0fb4a557abe --- ext/jni/GNUmakefile | 4 ++++ ext/jni/src/c/sqlite3-jni.c | 14 ++++++++++++++ ext/jni/src/c/sqlite3-jni.h | 18 +++++++++++++++++- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 19 +++++++++++++++++++ ext/jni/src/org/sqlite/jni/Tester1.java | 7 +++++++ manifest | 21 ++++++++++----------- manifest.uuid | 2 +- 7 files changed, 72 insertions(+), 13 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 36ef42d31a..22301a3245 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -317,6 +317,10 @@ dist: \ $(bin.version-info) $(sqlite3.canonical.c) \ $(package.jar) $(MAKEFILE) @echo "Making end-user deliverables..." + @echo "****************************************************************************"; \ + echo "*** WARNING: be sure to build this with JDK8 (javac 1.8) for compatibility."; \ + echo "*** reasons!"; $$($(bin.javac) -version); \ + echo "****************************************************************************" @rm -fr $(dist-dir.top) @mkdir -p $(dist-dir.src) @cp -p $(dist.top.extras) $(dist-dir.top)/. diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index b28ea71144..6c60aeaf3a 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2624,6 +2624,20 @@ JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ return rc; } +JDECL(void,1interrupt)(JENV_CSELF, jobject jpDb){ + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + if( pDb ) sqlite3_interrupt(pDb); +} + +JDECL(jboolean,1is_1interrupted)(JENV_CSELF, jobject jpDb){ + int rc = 0; + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + if( pDb ){ + rc = sqlite3_is_interrupted(pDb); + } + return rc ? JNI_TRUE : JNI_FALSE; +} + JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index bf2d5527b0..6722085fdd 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1120,7 +1120,7 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename * Method: sqlite3_db_config * Signature: (Lorg/sqlite/jni/sqlite3;IILorg/sqlite/jni/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2 (JNIEnv *, jclass, jobject, jint, jint, jobject); /* @@ -1211,6 +1211,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1finalize JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1initialize (JNIEnv *, jclass); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_interrupt + * Signature: (Lorg/sqlite/jni/sqlite3;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1interrupt + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_is_interrupted + * Signature: (Lorg/sqlite/jni/sqlite3;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1is_1interrupted + (JNIEnv *, jclass, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_last_insert_rowid diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 9b2a176504..adad718145 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -557,6 +557,25 @@ public final class SQLite3Jni { public static synchronized native int sqlite3_initialize(); + /** + Design note/FIXME: we have a problem vis-a-vis 'synchronized' + here: we specifically want other threads to be able to cancel a + long-running thread, but this routine requires access to C-side + global state which does not have a mutex. Making this function + synchronized would make it impossible for a long-running job to + be cancelled from another thread. + + The mutexing problem here is not within the core lib or Java, but + within the cached data held by the JNI binding. The cache holds + per-thread state, used by all but a tiny fraction of the JNI + binding layer, and access to that state needs to be + mutex-protected. + */ + public static native void sqlite3_interrupt(@NotNull sqlite3 db); + + //! See sqlite3_interrupt() for threading concerns. + public static native boolean sqlite3_is_interrupted(@NotNull sqlite3 db); + public static synchronized native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); public static synchronized native String sqlite3_libversion(); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index ffe0b83846..e9524e49a7 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -163,6 +163,13 @@ public class Tester1 { /* This function has different mangled names in jdk8 vs jdk19, and this call is here to ensure that the build fails if it cannot find both names. */; + + // These interrupt checks are only to make sure that the JNI binding + // has the proper exported symbol names. They don't actually test + // anything useful. + affirm( !sqlite3_is_interrupted(db) ); + sqlite3_interrupt(db); + affirm( sqlite3_is_interrupted(db) ); sqlite3_close_v2(db); affirm(0 == db.getNativePointer()); } diff --git a/manifest b/manifest index f112606117..0c792ccae0 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sthe\sJava\sNative\sInterface\s(JNI)\sbinding\sinto\strunk. -D 2023-08-12T21:39:18.053 +C Bind\ssqlite3_interrupt()\sand\ssqlite3_is_interrupted()\sto\sJNI\sbut\swith\scaveats\sregarding\smutexing\sof\sthe\sJNIEnv\scache.\sAdd\sa\sloud\swarning\sto\sthe\sJNI\s'dist'\starget\sthat\sit\sshould\sbe\sbuilt\swith\sJDK8\s(a.k.a.\sJava\s1.8)\sfor\scompatibility\sreasons. +D 2023-08-12T23:47:58.408 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,11 +231,11 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 435485ff2005c4bcdea808f5efe6d4ee66a00430c2499dcc4927b20378486bea +F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c bea6b8691a5fa3a8626a771757bb261208d3c5fc6598266d3b0ee23d88e35632 -F ext/jni/src/c/sqlite3-jni.h c5f941b057a24ee62942e6e1bf5a7fd527e5004d20d9638e84a9382813c3cf2a +F ext/jni/src/c/sqlite3-jni.c 1d3bb5113ba4dd7f8645fcc4c669155931e44e234816f528642a738914dd45a4 +F ext/jni/src/c/sqlite3-jni.h 11bf3ab9682f5c393e6ac6a3ddb0fdf7b8dd40f7c77f9ef122d3e5c011a5d329 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/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -254,8 +254,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 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 4b6fd22e04e63eb65d8e4e38fda39ecf15ce244d034607517627ce2e766e7e65 -F ext/jni/src/org/sqlite/jni/Tester1.java 07c14a90427529ceba54b5e8344ca03602f5789dc53c4163ce22f92d8c577a11 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1652af40fc0acb7a140dbe32e3146f980c37c28454b5115a4d0856cbdbc52696 +F ext/jni/src/org/sqlite/jni/Tester1.java fc2ec1f1be58474112b9df8284f0157b64872107f446154c3d0bf1742b924d2b F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2091,9 +2091,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 0a6930a7ff8f8c6ca244d1d654532f3d2a02d77ef67c6cae0c53092743d59ea6 1ba7754045a009d9c94b23ac76b9bb8d9c9cb24d42dcdf1203ee75ac85765d3e -R 71919983f1228c04d42d3555e47fba1b -T +closed 1ba7754045a009d9c94b23ac76b9bb8d9c9cb24d42dcdf1203ee75ac85765d3e Closed\sby\sintegrate-merge. +P 48b13edcec6935bf125b265b41a3e6f7b2407afff89d5b4daa2939e3c5679ca0 +R 122775c5939e6f1540fadefe535658be U stephan -Z 06dd3519c67c3e383d12eeb6b6fbb0d1 +Z a13d97b8681eef4145a4ab842cd3a9d2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 1c747eb83a..97ea0d6766 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -48b13edcec6935bf125b265b41a3e6f7b2407afff89d5b4daa2939e3c5679ca0 \ No newline at end of file +fbf99a2423dd20e4544bdeea85f714e9368ce3b92fefe97efb39a0fb4a557abe \ No newline at end of file From 71e5694cd51bbf66546e58b52553a16fb7e842ff Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 13 Aug 2023 09:53:27 +0000 Subject: [PATCH 02/37] An initial attempt at protecting the JNI global state via mutexes at the C level instead of relying on Java's synchronized keyword. It seems to work but increases the run time of the single-threaded batch tester by roughly 3 times. FossilOrigin-Name: c64e6a52ac79164be37fe643a4a39bd187af198a379410def8b8419f7c2224d4 --- ext/jni/src/c/sqlite3-jni.c | 269 +++++++++++---------- ext/jni/src/c/sqlite3-jni.h | 16 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 246 +++++++++---------- manifest | 16 +- manifest.uuid | 2 +- 5 files changed, 277 insertions(+), 272 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 6c60aeaf3a..4522e05b45 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -498,10 +498,14 @@ static struct { struct { S3JniEnvCache * aHead /* Linked list of in-use instances */; S3JniEnvCache * aFree /* Linked list of free instances */; + sqlite3_mutex * mutex /* mutex for aHead and aFree */; + void const * locker /* env mutex is held on this object's behalf */; } envCache; struct { S3JniDb * aUsed /* Linked list of in-use instances */; S3JniDb * aFree /* Linked list of free instances */; + sqlite3_mutex * mutex /* mutex for aUsed and aFree */; + void const * locker /* perDb mutex is held on this object's behalf */; } perDb; struct { unsigned nphCacheHits; @@ -521,21 +525,50 @@ static struct { #if S3JNI_ENABLE_AUTOEXT struct { S3JniAutoExtension *pHead /* Head of the auto-extension list */; - S3JniDb * psOpening /* handle to the being-opened db. We - need this so that auto extensions - can have a consistent view of the - cross-language db connection and - behave property if they call further - db APIs. */; - int isRunning /* True while auto extensions are - running. This is used to prohibit - manipulation of the auto-extension - list while extensions are - running. */; + S3JniDb * psOpening /* FIXME: move into envCache. Handle to the + being-opened db. We need this so that auto + extensions can have a consistent view of + the cross-language db connection and + behave property if they call further db + APIs. */; + int isRunning /* True while auto extensions are + running. This is used to prohibit + manipulation of the auto-extension + list while extensions are + running. */; } autoExt; #endif } S3JniGlobal; +#define MUTEX_ASSERT_LOCKER_ENV \ + assert( (env) == S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) +#define MUTEX_ASSERT_LOCKER_PDB \ + assert( 0 != S3JniGlobal.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) +#define MUTEX_ASSERT_NOTLOCKER_ENV \ + assert( (env) != S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) +#define MUTEX_ASSERT_NOTLOCKER_PDB \ + assert( 0 == S3JniGlobal.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) +#define MUTEX_ENTER_ENV \ + /*MARKER(("Entering ENV mutex@%p %s.\n", env, __func__));*/ \ + MUTEX_ASSERT_NOTLOCKER_ENV; \ + sqlite3_mutex_enter( S3JniGlobal.envCache.mutex ); \ + S3JniGlobal.envCache.locker = env +#define MUTEX_LEAVE_ENV \ + /*MARKER(("Leaving ENV mutex @%p %s.\n", env, __func__));*/ \ + MUTEX_ASSERT_LOCKER_ENV; \ + S3JniGlobal.envCache.locker = 0; \ + sqlite3_mutex_leave( S3JniGlobal.envCache.mutex ) +#define MUTEX_ENTER_PDB \ + /*MARKER(("Entering PerDb mutex@%p %s.\n", env, __func__));*/ \ + MUTEX_ASSERT_NOTLOCKER_PDB; \ + sqlite3_mutex_enter( S3JniGlobal.perDb.mutex ); \ + S3JniGlobal.perDb.locker = env; +#define MUTEX_LEAVE_PDB \ + /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ + MUTEX_ASSERT_LOCKER_PDB; \ + S3JniGlobal.perDb.locker = 0; \ + sqlite3_mutex_leave( S3JniGlobal.perDb.mutex ) + #define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) static void s3jni_oom(JNIEnv * const env){ (*env)->FatalError(env, "Out of memory.") /* does not return */; @@ -559,12 +592,14 @@ static void * s3jni_malloc(JNIEnv * const env, size_t n){ insofar as possible. Calls (*env)->FatalError() if allocation of an entry fails. That's hypothetically possible but "shouldn't happen." */ -FIXME_THREADING(S3JniEnvCache) static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ - struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; + struct S3JniEnvCache * row; + MUTEX_ENTER_ENV; + row = S3JniGlobal.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ ++S3JniGlobal.metrics.envCacheHits; + MUTEX_LEAVE_ENV; return row; } } @@ -576,11 +611,7 @@ static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ if( row->pNext ) row->pNext->pPrev = 0; }else{ row = sqlite3_malloc(sizeof(S3JniEnvCache)); - if( !row ){ - (*env)->FatalError(env, "Maintenance required: S3JniEnvCache is full.") - /* Does not return, but cc doesn't know that */; - return NULL; - } + OOM_CHECK(row); } memset(row, 0, sizeof(*row)); row->pNext = S3JniGlobal.envCache.aHead; @@ -616,11 +647,12 @@ static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ 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 StndardCharsets.UTF_8 field."); + 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_LEAVE_ENV; return row; } @@ -765,7 +797,6 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, System.out.println(e.getMessage()); // Hi } */ -FIXME_THREADING(S3JniEnvCache) static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); jmethodID mid; @@ -865,10 +896,10 @@ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDest /** Clears s's state and moves it to the free-list. */ -FIXME_THREADING(perDb) static void S3JniDb_set_aside(S3JniDb * const s){ if(s){ JNIEnv * const env = s->env; + MUTEX_ASSERT_LOCKER_PDB; assert(s->pDb && "Else this object is already in the free-list."); //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); assert(s->pPrev != s); @@ -904,39 +935,16 @@ static void S3JniDb_set_aside(S3JniDb * const s){ //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); } } - -/** - Requires that p has been snipped from any linked list it is - in. Clears all Java refs p holds and zeroes out p. -*/ -static void S3JniEnvCache_clear(S3JniEnvCache * const p){ - JNIEnv * const env = p->env; - if(env){ - int i; - UNREF_G(p->g.cObj); - UNREF_G(p->g.cLong); - UNREF_G(p->g.cString); - UNREF_G(p->g.oCharsetUtf8); -#ifdef SQLITE_ENABLE_FTS5 - UNREF_G(p->jFtsExt); - UNREF_G(p->jPhraseIter.klazz); -#endif - for( i = 0; i < NphCache_SIZE; ++i ){ - S3JniNphCache_clear(env, &p->nph[i]); - } - memset(p, 0, sizeof(S3JniEnvCache)); - } -} - /** Cleans up all state in S3JniGlobal.perDb for th given JNIEnv. Results are undefined if a Java-side db uses the API from the given JNIEnv after this call. */ -FIXME_THREADING(perDb) static void S3JniDb_free_for_env(JNIEnv *env){ - S3JniDb * ps = S3JniGlobal.perDb.aUsed; + S3JniDb * ps; S3JniDb * pNext = 0; + MUTEX_ENTER_PDB; + ps = S3JniGlobal.perDb.aUsed; for( ; ps; ps = pNext ){ pNext = ps->pNext; if(ps->env == env){ @@ -946,6 +954,7 @@ static void S3JniDb_free_for_env(JNIEnv *env){ pNext = pPrev; } } + MUTEX_LEAVE_PDB; } /** @@ -957,35 +966,43 @@ static void S3JniDb_free_for_env(JNIEnv *env){ what would otherwise be stale references. */ static int S3JniGlobal_env_uncache(JNIEnv * const env){ - struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; + struct S3JniEnvCache * row; + int i; + assert( 0!=S3JniGlobal.envCache.mutex && "Env mutex misuse."); + row = S3JniGlobal.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ break; } } - if( !row ) return 0; + if( !row ){ + return 0; + } if( row->pNext ) row->pNext->pPrev = row->pPrev; if( row->pPrev ) row->pPrev->pNext = row->pNext; if( S3JniGlobal.envCache.aHead == row ){ assert( !row->pPrev ); S3JniGlobal.envCache.aHead = row->pNext; } - S3JniEnvCache_clear(row); - assert( !row->pNext ); - assert( !row->pPrev ); + 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 ){ + S3JniNphCache_clear(env, &row->nph[i]); + } + memset(row, 0, sizeof(S3JniEnvCache)); row->pNext = S3JniGlobal.envCache.aFree; if( row->pNext ) row->pNext->pPrev = row; S3JniGlobal.envCache.aFree = row; - S3JniDb_free_for_env(env); return 1; } -static void S3JniGlobal_S3JniEnvCache_clear(void){ - while( S3JniGlobal.envCache.aHead ){ - S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env ); - } -} - /** Searches the NativePointerHolder cache for the given combination. If it finds one, it returns it as-is. If it doesn't AND the cache @@ -1128,14 +1145,15 @@ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zC } /** - Extracts the new S3JniDb instance from the free-list, or - allocates one if needed, associats it with pDb, and returns. - Returns NULL on OOM. pDb MUST be associated with jDb via - NativePointerHolder_set(). + Extracts the new S3JniDb instance from the free-list, or allocates + one if needed, associats it with pDb, and returns. Returns NULL on + OOM. pDb MUST, on success of the calling operation, subsequently be + associated with jDb via NativePointerHolder_set(). */ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, jobject jDb){ S3JniDb * rv; + MUTEX_ASSERT_LOCKER_PDB; if(S3JniGlobal.perDb.aFree){ rv = S3JniGlobal.perDb.aFree; //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); @@ -1185,62 +1203,37 @@ static void S3JniDb_dump(S3JniDb *s){ #endif /** - Returns the S3JniDb object for the given db. If allocIfNeeded is - true then a new instance will be allocated if no mapping currently - exists, else NULL is returned if no mapping is found. + Returns the S3JniDb object for the given db. - The 3rd and 4th args should normally only be non-0 for - sqlite3_open(_v2)(). For most other cases, they must be 0, in which - case the db handle will be fished out of the jDb object and NULL is - returned if jDb does not have any associated S3JniDb. + The 3rd argument should normally only be non-0 for routines which + are called from the C library and pass a native db handle instead of + a Java handle. In normal usage, the 2nd argument is a Java-side sqlite3 + object, from which the db is fished out. - If called with a NULL jDb and non-NULL pDb then allocIfNeeded MUST - be false and it will look for a matching db object. That usage is - required for functions, like sqlite3_context_db_handle(), which - return a (sqlite3*) but do not take one as an argument. + Returns NULL if jDb and pDb are both NULL or if there is no + matching S3JniDb entry for pDb or the pointer fished out of jDb. */ FIXME_THREADING(S3JniEnvCache) FIXME_THREADING(perDb) -static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, - sqlite3 *pDb, int allocIfNeeded){ - S3JniDb * s = S3JniGlobal.perDb.aUsed; - if(!jDb){ - if(pDb){ - assert(!allocIfNeeded); - }else{ - return 0; +static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){ + S3JniDb * s = 0; + if(jDb || pDb){ + MUTEX_ENTER_PDB; + s = S3JniGlobal.perDb.aUsed; + if(!pDb){ + assert( jDb ); + pDb = PtrGet_sqlite3(jDb); } - } - assert(allocIfNeeded ? !!pDb : 1); - if(!allocIfNeeded && !pDb){ - pDb = PtrGet_sqlite3(jDb); - } - for( ; pDb && s; s = s->pNext){ - if(s->pDb == pDb) return s; - } - if(allocIfNeeded){ - s = S3JniDb_alloc(env, pDb, jDb); + for( ; pDb && s; s = s->pNext){ + if(s->pDb == pDb){ + break; + } + } + MUTEX_LEAVE_PDB; } return s; } -#if 0 -/** - An alternative form which searches for the S3JniDb instance for - pDb with no JNIEnv-specific info. This can be (but _should_ it be?) - called from the context of a separate JNIEnv than the one mapped - to in the returned object. Returns 0 if no match is found. -*/ -FIXME_THREADING(perDb) -static S3JniDb * S3JniDb_for_db2(sqlite3 *pDb){ - S3JniDb * s = S3JniGlobal.perDb.aUsed; - for( ; pDb && s; s = s->pNext){ - if(s->pDb == pDb) return s; - } - return 0; -} -#endif - #if S3JNI_ENABLE_AUTOEXT /** Unlink ax from S3JniGlobal.autoExt and free it. @@ -2048,7 +2041,7 @@ static int s3jni_busy_handler(void* pState, int n){ FIXME_THREADING(S3JniEnvCache) JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); int rc = 0; if(!ps) return (jint)SQLITE_NOMEM; if(jBusy){ @@ -2079,7 +2072,7 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ FIXME_THREADING(S3JniEnvCache) FIXME_THREADING(perDb) JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); if( ps ){ S3JniHook_unref(env, &ps->busyHandler, 1); return sqlite3_busy_timeout(ps->pDb, (int)ms); @@ -2110,12 +2103,13 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ int rc = 0; S3JniDb * ps = 0; assert(version == 1 || version == 2); - ps = S3JniDb_for_db(env, jDb, 0, 0); + ps = S3JniDb_for_db(env, jDb, 0); if(ps){ - //MARKER(("close()ing db@%p\n", ps->pDb)); rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); + MUTEX_ENTER_PDB; S3JniDb_set_aside(ps) /* MUST come after close() because of ps->trace. */; + MUTEX_LEAVE_PDB; NativePointerHolder_set(env, jDb, 0, S3JniClassNames.sqlite3); } return (jint)rc; @@ -2170,7 +2164,7 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, FIXME_THREADING(S3JniEnvCache) FIXME_THREADING(perDb) JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); jclass klazz; jobject pOld = 0; jmethodID xCallback; @@ -2288,7 +2282,7 @@ static void s3jni_rollback_hook_impl(void *pP){ FIXME_THREADING(perDb) static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); jclass klazz; jobject pOld = 0; jmethodID xCallback; @@ -2357,14 +2351,14 @@ JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ FIXME_THREADING(perDb) JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); - S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb, 0) : 0; + S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb) : 0; return ps ? ps->jDb : 0; } JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, jstring name, jint eTextRep, jobject oCollation){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); jclass klazz; int rc; const char *zName; @@ -2457,7 +2451,7 @@ JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( JENV_CSELF, jobject jDb, jint op, jstring jStr ){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); int rc; char *zStr; @@ -2489,7 +2483,7 @@ FIXME_THREADING(perDb) JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut ){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); int rc; switch( ps ? op : 0 ){ case SQLITE_DBCONFIG_ENABLE_FKEY: @@ -2538,7 +2532,7 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer } JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); char *zDbName; jstring jRv = 0; @@ -2657,7 +2651,9 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, *zDbName = 0; return SQLITE_NOMEM; } + MUTEX_ENTER_PDB; *ps = S3JniDb_alloc(env, 0, *jDb); + MUTEX_LEAVE_PDB; #if S3JNI_ENABLE_AUTOEXT if(*ps){ assert(!S3JniGlobal.autoExt.psOpening); @@ -2688,15 +2684,12 @@ static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, #endif if(*ppDb){ assert(ps->jDb); -#if S3JNI_ENABLE_AUTOEXT - //MARKER(("*autoExt.pHead=%p, ppDb=%p, ps->pDb=%p\n", S3JniGlobal.autoExt.pHead, *ppDb, ps->pDb)); - // invalid when an autoext triggers another open(): - // assert( S3JniGlobal.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); -#endif ps->pDb = *ppDb; NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); }else{ + MUTEX_ENTER_PDB; S3JniDb_set_aside(ps); + MUTEX_LEAVE_PDB; ps = 0; } OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); @@ -2845,7 +2838,7 @@ static int s3jni_progress_handler_impl(void *pP){ } JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){ - S3JniDb * ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * ps = S3JniDb_for_db(env, jDb, 0); jclass klazz; jmethodID xCallback; if( n<1 || !jProgress ){ @@ -3101,7 +3094,7 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, } JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); S3JniHook * const pHook = ps ? &ps->authHook : 0; if( !ps ) return SQLITE_MISUSE; @@ -3195,7 +3188,11 @@ JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){ } JDECL(jint,1shutdown)(JENV_CSELF){ - S3JniGlobal_S3JniEnvCache_clear(); + MUTEX_ENTER_ENV; + while( S3JniGlobal.envCache.aHead ){ + S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env ); + } + MUTEX_LEAVE_ENV; /* Do not clear S3JniGlobal.jvm: it's legal to call sqlite3_initialize() again to restart the lib. */ return sqlite3_shutdown(); @@ -3279,7 +3276,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ } JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); jclass klazz; if( !traceMask || !jTracer ){ if(ps){ @@ -3330,7 +3327,7 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); jclass klazz; jobject pOld = 0; jmethodID xCallback; @@ -3635,7 +3632,7 @@ static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ } JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); jobject rv = 0; if(!ps) return 0; else if(ps->jFtsApi){ @@ -4338,7 +4335,11 @@ Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ */ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ - return S3JniGlobal_env_uncache(env) ? JNI_TRUE : JNI_FALSE; + int rc; + MUTEX_ENTER_ENV; + rc = S3JniGlobal_env_uncache(env); + MUTEX_LEAVE_ENV; + return rc ? JNI_TRUE : JNI_FALSE; } /** @@ -4402,6 +4403,10 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ (*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 ); #if 0 /* Just for sanity checking... */ (void)S3JniGlobal_env_cache(env); diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 6722085fdd..5d741859f1 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1443,6 +1443,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64 (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_shutdown + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown + (JNIEnv *, jclass); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_status @@ -1731,14 +1739,6 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1frombind JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1subtype (JNIEnv *, jclass, jobject); -/* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_shutdown - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown - (JNIEnv *, jclass); - /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_do_something_for_developer diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index adad718145..e7aa10ebae 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -195,7 +195,7 @@ public final class SQLite3Jni { Achtung: it is as yet unknown whether auto extensions registered from one JNIEnv (thread) can be safely called from another. */ - public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback); + public static native int sqlite3_auto_extension(@NotNull AutoExtension callback); public static int sqlite3_bind_blob( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data @@ -205,27 +205,27 @@ public final class SQLite3Jni { : sqlite3_bind_blob(stmt, ndx, data, data.length); } - private static synchronized native int sqlite3_bind_blob( + private static native int sqlite3_bind_blob( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n ); - public static synchronized native int sqlite3_bind_double( + public static native int sqlite3_bind_double( @NotNull sqlite3_stmt stmt, int ndx, double v ); - public static synchronized native int sqlite3_bind_int( + public static native int sqlite3_bind_int( @NotNull sqlite3_stmt stmt, int ndx, int v ); - public static synchronized native int sqlite3_bind_int64( + public static native int sqlite3_bind_int64( @NotNull sqlite3_stmt stmt, int ndx, long v ); - public static synchronized native int sqlite3_bind_null( + public static native int sqlite3_bind_null( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_bind_parameter_count( + public static native int sqlite3_bind_parameter_count( @NotNull sqlite3_stmt stmt ); @@ -233,7 +233,7 @@ 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. */ - private static synchronized native int sqlite3_bind_parameter_index( + private static native int sqlite3_bind_parameter_index( @NotNull sqlite3_stmt stmt, byte[] paramName ); @@ -265,15 +265,15 @@ public final class SQLite3Jni { SQLITE_TRANSIENT for the final parameter and (B) behaves like sqlite3_bind_null() if the data argument is null. */ - private static synchronized native int sqlite3_bind_text( + private static native int sqlite3_bind_text( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes ); - public static synchronized native int sqlite3_bind_zeroblob( + public static native int sqlite3_bind_zeroblob( @NotNull sqlite3_stmt stmt, int ndx, int n ); - public static synchronized native int sqlite3_bind_zeroblob64( + public static native int sqlite3_bind_zeroblob64( @NotNull sqlite3_stmt stmt, int ndx, long n ); @@ -283,11 +283,11 @@ public final class SQLite3Jni { to clear the busy handler. Calling this multiple times with the same object is a no-op on the second and subsequent calls. */ - public static synchronized native int sqlite3_busy_handler( + public static native int sqlite3_busy_handler( @NotNull sqlite3 db, @Nullable BusyHandler handler ); - public static synchronized native int sqlite3_busy_timeout( + public static native int sqlite3_busy_timeout( @NotNull sqlite3 db, int ms ); @@ -296,63 +296,63 @@ public final class SQLite3Jni { effects, if auto extensions are currently running. (The JNI-level list of extensions cannot be manipulated while it is being traversed.) */ - public static synchronized native boolean sqlite3_cancel_auto_extension( + public static native boolean sqlite3_cancel_auto_extension( @NotNull AutoExtension ax ); - public static synchronized native int sqlite3_changes( + public static native int sqlite3_changes( @NotNull sqlite3 db ); - public static synchronized native long sqlite3_changes64( + public static native long sqlite3_changes64( @NotNull sqlite3 db ); - public static synchronized native int sqlite3_clear_bindings( + public static native int sqlite3_clear_bindings( @NotNull sqlite3_stmt stmt ); - public static synchronized native int sqlite3_close( + public static native int sqlite3_close( @NotNull sqlite3 db ); - public static synchronized native int sqlite3_close_v2( + public static native int sqlite3_close_v2( @NotNull sqlite3 db ); - public static synchronized native byte[] sqlite3_column_blob( + public static native byte[] sqlite3_column_blob( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_column_bytes( + public static native int sqlite3_column_bytes( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_column_bytes16( + public static native int sqlite3_column_bytes16( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_column_count( + public static native int sqlite3_column_count( @NotNull sqlite3_stmt stmt ); - public static synchronized native double sqlite3_column_double( + public static native double sqlite3_column_double( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_column_int( + public static native int sqlite3_column_int( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native long sqlite3_column_int64( + public static native long sqlite3_column_int64( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native String sqlite3_column_name( + public static native String sqlite3_column_name( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native String sqlite3_column_database_name( + public static native String sqlite3_column_database_name( @NotNull sqlite3_stmt stmt, int ndx ); @@ -385,11 +385,11 @@ public final class SQLite3Jni { return type.isInstance(o) ? (T)o : null; } - public static synchronized native String sqlite3_column_origin_name( + public static native String sqlite3_column_origin_name( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native String sqlite3_column_table_name( + public static native String sqlite3_column_table_name( @NotNull sqlite3_stmt stmt, int ndx ); @@ -398,7 +398,7 @@ public final class SQLite3Jni { This API includes no functions for working with Java's Modified UTF-8. */ - public static synchronized native String sqlite3_column_text16( + public static native String sqlite3_column_text16( @NotNull sqlite3_stmt stmt, int ndx ); @@ -406,7 +406,7 @@ public final class SQLite3Jni { Returns the given column's contents as UTF-8-encoded (not MUTF-8) text. Use sqlite3_column_text16() to fetch the text */ - public static synchronized native byte[] sqlite3_column_text( + public static native byte[] sqlite3_column_text( @NotNull sqlite3_stmt stmt, int ndx ); @@ -447,11 +447,11 @@ public final class SQLite3Jni { // return rv; // } - public static synchronized native int sqlite3_column_type( + public static native int sqlite3_column_type( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native sqlite3_value sqlite3_column_value( + public static native sqlite3_value sqlite3_column_value( @NotNull sqlite3_stmt stmt, int ndx ); @@ -459,7 +459,7 @@ public final class SQLite3Jni { This functions like C's sqlite3_collation_needed16() because Java's string type is compatible with that interface. */ - public static synchronized native int sqlite3_collation_needed( + public static native int sqlite3_collation_needed( @NotNull sqlite3 db, @Nullable CollationNeeded callback ); @@ -467,11 +467,11 @@ public final class SQLite3Jni { Returns the db handle passed to sqlite3_open() or sqlite3_open_v2(), as opposed to a new wrapper object. */ - public static synchronized native sqlite3 sqlite3_context_db_handle( + public static native sqlite3 sqlite3_context_db_handle( @NotNull sqlite3_context cx ); - public static synchronized native CommitHook sqlite3_commit_hook( + public static native CommitHook sqlite3_commit_hook( @NotNull sqlite3 db, @Nullable CommitHook hook ); @@ -483,7 +483,7 @@ public final class SQLite3Jni { @NotNull String optName ); - public static synchronized native int sqlite3_create_collation( + public static native int sqlite3_create_collation( @NotNull sqlite3 db, @NotNull String name, int eTextRep, @NotNull Collation col ); @@ -496,16 +496,16 @@ public final class SQLite3Jni { SQLFunction's inner classes (Scalar, Aggregate, and Window) for details. */ - public static synchronized native int sqlite3_create_function( + public static native int sqlite3_create_function( @NotNull sqlite3 db, @NotNull String functionName, int nArg, int eTextRep, @NotNull SQLFunction func ); - public static synchronized native int sqlite3_data_count( + public static native int sqlite3_data_count( @NotNull sqlite3_stmt stmt ); - public static synchronized native String sqlite3_db_filename( + public static native String sqlite3_db_filename( @NotNull sqlite3 db, @NotNull String dbName ); @@ -514,7 +514,7 @@ public final class SQLite3Jni { variadic arguments. Returns SQLITE_MISUSE if op is not one of the SQLITE_DBCONFIG_... options which uses this call form. */ - public static synchronized native int sqlite3_db_config( + public static native int sqlite3_db_config( @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out ); @@ -525,37 +525,37 @@ public final class SQLite3Jni { SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be extended in future versions. */ - public static synchronized native int sqlite3_db_config( + public static native int sqlite3_db_config( @NotNull sqlite3 db, int op, @NotNull String val ); - public static synchronized native int sqlite3_db_status( + public static native int sqlite3_db_status( @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent, @NotNull OutputPointer.Int32 pHighwater, boolean reset ); - public static synchronized native int sqlite3_errcode(@NotNull sqlite3 db); + public static native int sqlite3_errcode(@NotNull sqlite3 db); - public static synchronized native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); + public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); - public static synchronized native int sqlite3_extended_errcode(@NotNull sqlite3 db); + public static native int sqlite3_extended_errcode(@NotNull sqlite3 db); - public static synchronized native boolean sqlite3_extended_result_codes( + public static native boolean sqlite3_extended_result_codes( @NotNull sqlite3 db, boolean onoff ); - public static synchronized native String sqlite3_errmsg(@NotNull sqlite3 db); + public static native String sqlite3_errmsg(@NotNull sqlite3 db); - public static synchronized native String sqlite3_errstr(int resultCode); + public static native String sqlite3_errstr(int resultCode); /** Note that the offset values assume UTF-8-encoded SQL. */ - public static synchronized native int sqlite3_error_offset(@NotNull sqlite3 db); + public static native int sqlite3_error_offset(@NotNull sqlite3 db); - public static synchronized native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); + public static native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); - public static synchronized native int sqlite3_initialize(); + public static native int sqlite3_initialize(); /** Design note/FIXME: we have a problem vis-a-vis 'synchronized' @@ -576,11 +576,11 @@ public final class SQLite3Jni { //! See sqlite3_interrupt() for threading concerns. public static native boolean sqlite3_is_interrupted(@NotNull sqlite3 db); - public static synchronized native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); + public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); - public static synchronized native String sqlite3_libversion(); + public static native String sqlite3_libversion(); - public static synchronized native int sqlite3_libversion_number(); + public static native int sqlite3_libversion_number(); /** Works like its C counterpart and makes the native pointer of the @@ -601,11 +601,11 @@ public final class SQLite3Jni { or sqlite3_open_v2() so that they have a predictible object to pass to, e.g., the sqlite3_collation_needed() callback. */ - public static synchronized native int sqlite3_open( + public static native int sqlite3_open( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb ); - public static synchronized native int sqlite3_open_v2( + public static native int sqlite3_open_v2( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, int flags, @Nullable String zVfs ); @@ -629,7 +629,7 @@ public final class SQLite3Jni { necessary, however, and overloads are provided which gloss over that. */ - private static synchronized native int sqlite3_prepare( + private static native int sqlite3_prepare( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset @@ -658,7 +658,7 @@ public final class SQLite3Jni { return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); } - private static synchronized native int sqlite3_prepare_v2( + private static native int sqlite3_prepare_v2( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset @@ -687,7 +687,7 @@ public final class SQLite3Jni { return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); } - private static synchronized native int sqlite3_prepare_v3( + private static native int sqlite3_prepare_v3( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset @@ -716,22 +716,22 @@ public final class SQLite3Jni { return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); } - public static synchronized native void sqlite3_progress_handler( + public static native void sqlite3_progress_handler( @NotNull sqlite3 db, int n, @Nullable ProgressHandler h ); //TODO??? void *sqlite3_preupdate_hook(...) and friends - public static synchronized native int sqlite3_reset(@NotNull sqlite3_stmt stmt); + public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt); /** Works like the C API except that it has no side effects if auto extensions are currently running. (The JNI-level list of extensions cannot be manipulated while it is being traversed.) */ - public static synchronized native void sqlite3_reset_auto_extension(); + public static native void sqlite3_reset_auto_extension(); - public static synchronized native void sqlite3_result_double( + public static native void sqlite3_result_double( @NotNull sqlite3_context cx, double v ); @@ -742,7 +742,7 @@ public final class SQLite3Jni { results in the C-level sqlite3_result_error() being called with a complaint about the invalid argument. */ - private static synchronized native void sqlite3_result_error( + private static native void sqlite3_result_error( @NotNull sqlite3_context cx, @Nullable byte[] msg, int eTextRep ); @@ -785,27 +785,27 @@ public final class SQLite3Jni { sqlite3_result_error16(cx, e.getMessage()); } - public static synchronized native void sqlite3_result_error_toobig( + public static native void sqlite3_result_error_toobig( @NotNull sqlite3_context cx ); - public static synchronized native void sqlite3_result_error_nomem( + public static native void sqlite3_result_error_nomem( @NotNull sqlite3_context cx ); - public static synchronized native void sqlite3_result_error_code( + public static native void sqlite3_result_error_code( @NotNull sqlite3_context cx, int c ); - public static synchronized native void sqlite3_result_null( + public static native void sqlite3_result_null( @NotNull sqlite3_context cx ); - public static synchronized native void sqlite3_result_int( + public static native void sqlite3_result_int( @NotNull sqlite3_context cx, int v ); - public static synchronized native void sqlite3_result_int64( + public static native void sqlite3_result_int64( @NotNull sqlite3_context cx, long v ); @@ -825,7 +825,7 @@ public final class SQLite3Jni { Note that there is no sqlite3_bind_java_object() counterpart. */ - public static synchronized native void sqlite3_result_java_object( + public static native void sqlite3_result_java_object( @NotNull sqlite3_context cx, @NotNull Object o ); @@ -883,19 +883,19 @@ public final class SQLite3Jni { sqlite3_result_text(cx, v); } - public static synchronized native void sqlite3_result_value( + public static native void sqlite3_result_value( @NotNull sqlite3_context cx, @NotNull sqlite3_value v ); - public static synchronized native void sqlite3_result_zeroblob( + public static native void sqlite3_result_zeroblob( @NotNull sqlite3_context cx, int n ); - public static synchronized native int sqlite3_result_zeroblob64( + public static native int sqlite3_result_zeroblob64( @NotNull sqlite3_context cx, long n ); - private static synchronized native void sqlite3_result_blob( + private static native void sqlite3_result_blob( @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen ); @@ -915,7 +915,7 @@ public final class SQLite3Jni { If maxLen is larger than blob.length, it is truncated to that value. If it is negative, results are undefined. */ - private static synchronized native void sqlite3_result_blob64( + private static native void sqlite3_result_blob64( @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen ); @@ -925,7 +925,7 @@ public final class SQLite3Jni { sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length)); } - private static synchronized native void sqlite3_result_text( + private static native void sqlite3_result_text( @NotNull sqlite3_context cx, @Nullable byte[] text, int maxLen ); @@ -959,17 +959,23 @@ public final class SQLite3Jni { text.length, it is silently truncated to text.length. If it is negative, results are undefined. */ - private static synchronized native void sqlite3_result_text64( + private static native void sqlite3_result_text64( @NotNull sqlite3_context cx, @Nullable byte[] text, long maxLength, int encoding ); - public static synchronized native int sqlite3_status( + /** + Cleans up all per-JNIEnv and per-db state managed by the library + then calls the C-native sqlite3_shutdown(). + */ + public static synchronized native int sqlite3_shutdown(); + + public static native int sqlite3_status( int op, @NotNull OutputPointer.Int32 pCurrent, @NotNull OutputPointer.Int32 pHighwater, boolean reset ); - public static synchronized native int sqlite3_status64( + public static native int sqlite3_status64( int op, @NotNull OutputPointer.Int64 pCurrent, @NotNull OutputPointer.Int64 pHighwater, boolean reset ); @@ -1025,32 +1031,32 @@ public final class SQLite3Jni { sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); } - public static synchronized native RollbackHook sqlite3_rollback_hook( + public static native RollbackHook sqlite3_rollback_hook( @NotNull sqlite3 db, @Nullable RollbackHook hook ); //! Sets or unsets (if auth is null) the current authorizer. - public static synchronized native int sqlite3_set_authorizer( + public static native int sqlite3_set_authorizer( @NotNull sqlite3 db, @Nullable Authorizer auth ); - public static synchronized native void sqlite3_set_last_insert_rowid( + public static native void sqlite3_set_last_insert_rowid( @NotNull sqlite3 db, long rowid ); - public static synchronized native int sqlite3_sleep(int ms); + public static native int sqlite3_sleep(int ms); - public static synchronized native String sqlite3_sourceid(); + public static native String sqlite3_sourceid(); - public static synchronized native String sqlite3_sql(@NotNull sqlite3_stmt stmt); + public static native String sqlite3_sql(@NotNull sqlite3_stmt stmt); - public static synchronized native int sqlite3_step(@NotNull sqlite3_stmt stmt); + public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); /** Internal impl of the public sqlite3_strglob() method. Neither argument may be NULL and both _MUST_ be NUL-terminated. */ - private static synchronized native int sqlite3_strglob( + private static native int sqlite3_strglob( @NotNull byte[] glob, @NotNull byte[] txt ); @@ -1067,7 +1073,7 @@ public final class SQLite3Jni { Internal impl of the public sqlite3_strlike() method. Neither argument may be NULL and both _MUST_ be NUL-terminated. */ - private static synchronized native int sqlite3_strlike( + private static native int sqlite3_strlike( @NotNull byte[] glob, @NotNull byte[] txt, int escChar ); @@ -1081,11 +1087,11 @@ public final class SQLite3Jni { ); } - public static synchronized native int sqlite3_threadsafe(); + public static native int sqlite3_threadsafe(); - public static synchronized native int sqlite3_total_changes(@NotNull sqlite3 db); + public static native int sqlite3_total_changes(@NotNull sqlite3 db); - public static synchronized native long sqlite3_total_changes64(@NotNull sqlite3 db); + public static native long sqlite3_total_changes64(@NotNull sqlite3 db); /** Works like C's sqlite3_trace_v2() except that the 3rd argument to that @@ -1097,33 +1103,33 @@ public final class SQLite3Jni { mapping state fails and SQLITE_ERROR if the given callback object cannot be processed propertly (i.e. an internal error). */ - public static synchronized native int sqlite3_trace_v2( + public static native int sqlite3_trace_v2( @NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer ); - public static synchronized native UpdateHook sqlite3_update_hook( + public static native UpdateHook sqlite3_update_hook( sqlite3 db, UpdateHook hook ); - public static synchronized native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); + public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_bytes(@NotNull sqlite3_value v); + public static native int sqlite3_value_bytes(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_bytes16(@NotNull sqlite3_value v); + public static native int sqlite3_value_bytes16(@NotNull sqlite3_value v); - public static synchronized native double sqlite3_value_double(@NotNull sqlite3_value v); + public static native double sqlite3_value_double(@NotNull sqlite3_value v); - public static synchronized native sqlite3_value sqlite3_value_dupe( + public static native sqlite3_value sqlite3_value_dupe( @NotNull sqlite3_value v ); - public static synchronized native int sqlite3_value_encoding(@NotNull sqlite3_value v); + public static native int sqlite3_value_encoding(@NotNull sqlite3_value v); - public static synchronized native void sqlite3_value_free(@Nullable sqlite3_value v); + public static native void sqlite3_value_free(@Nullable sqlite3_value v); - public static synchronized native int sqlite3_value_int(@NotNull sqlite3_value v); + public static native int sqlite3_value_int(@NotNull sqlite3_value v); - public static synchronized native long sqlite3_value_int64(@NotNull sqlite3_value v); + public static native long sqlite3_value_int64(@NotNull sqlite3_value v); /** If the given value was set using sqlite3_result_java_value() then @@ -1132,7 +1138,7 @@ public final class SQLite3Jni { It is up to the caller to inspect the object to determine its type, and cast it if necessary. */ - public static synchronized native Object sqlite3_value_java_object( + public static native Object sqlite3_value_java_object( @NotNull sqlite3_value v ); @@ -1153,41 +1159,35 @@ public final class SQLite3Jni { See sqlite3_value_text_utf8() for how to extract text in standard UTF-8. */ - public static synchronized native String sqlite3_value_text(@NotNull sqlite3_value v); + public static native String sqlite3_value_text(@NotNull sqlite3_value v); /** The sqlite3_value counterpart of sqlite3_column_text_utf8(). */ - public static synchronized native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); + public static native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); - public static synchronized native byte[] sqlite3_value_text16(@NotNull sqlite3_value v); + public static native byte[] sqlite3_value_text16(@NotNull sqlite3_value v); - public static synchronized native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v); + public static native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v); - public static synchronized native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); + public static native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_type(@NotNull sqlite3_value v); + public static native int sqlite3_value_type(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); + public static native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_nochange(@NotNull sqlite3_value v); + public static native int sqlite3_value_nochange(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_frombind(@NotNull sqlite3_value v); + public static native int sqlite3_value_frombind(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_subtype(@NotNull sqlite3_value v); - - /** - Cleans up all per-JNIEnv and per-db state managed by the library - then calls the C-native sqlite3_shutdown(). - */ - public static synchronized native int sqlite3_shutdown(); + public static native int sqlite3_value_subtype(@NotNull sqlite3_value v); /** This is NOT part of the public API. It exists solely as a place to hook in arbitrary C-side code during development and testing of this library. */ - public static synchronized native void sqlite3_do_something_for_developer(); + public static native void sqlite3_do_something_for_developer(); ////////////////////////////////////////////////////////////////////// // SQLITE_... constants follow... diff --git a/manifest b/manifest index 0c792ccae0..d3b949dbf9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_interrupt()\sand\ssqlite3_is_interrupted()\sto\sJNI\sbut\swith\scaveats\sregarding\smutexing\sof\sthe\sJNIEnv\scache.\sAdd\sa\sloud\swarning\sto\sthe\sJNI\s'dist'\starget\sthat\sit\sshould\sbe\sbuilt\swith\sJDK8\s(a.k.a.\sJava\s1.8)\sfor\scompatibility\sreasons. -D 2023-08-12T23:47:58.408 +C An\sinitial\sattempt\sat\sprotecting\sthe\sJNI\sglobal\sstate\svia\smutexes\sat\sthe\sC\slevel\sinstead\sof\srelying\son\sJava's\ssynchronized\skeyword.\sIt\sseems\sto\swork\sbut\sincreases\sthe\srun\stime\sof\sthe\ssingle-threaded\sbatch\stester\sby\sroughly\s3\stimes. +D 2023-08-13T09:53:27.824 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,8 +234,8 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 1d3bb5113ba4dd7f8645fcc4c669155931e44e234816f528642a738914dd45a4 -F ext/jni/src/c/sqlite3-jni.h 11bf3ab9682f5c393e6ac6a3ddb0fdf7b8dd40f7c77f9ef122d3e5c011a5d329 +F ext/jni/src/c/sqlite3-jni.c 93fbeed06441352df68f6c0e4bc2cd895e81d8550dc9972cafb30621958b4784 +F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 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/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -254,7 +254,7 @@ 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 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1652af40fc0acb7a140dbe32e3146f980c37c28454b5115a4d0856cbdbc52696 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 7933fabc267dbfdccb784563d8c404f90275fe9418e8cd6c43c584bc9c57f70f F ext/jni/src/org/sqlite/jni/Tester1.java fc2ec1f1be58474112b9df8284f0157b64872107f446154c3d0bf1742b924d2b F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2091,8 +2091,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 48b13edcec6935bf125b265b41a3e6f7b2407afff89d5b4daa2939e3c5679ca0 -R 122775c5939e6f1540fadefe535658be +P fbf99a2423dd20e4544bdeea85f714e9368ce3b92fefe97efb39a0fb4a557abe +R 42a3d0d4c63d13c26ffa7bd8f71380ed U stephan -Z a13d97b8681eef4145a4ab842cd3a9d2 +Z 5906c49c68c9155fc27b7d9430c5551e # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 97ea0d6766..684bcd68e8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fbf99a2423dd20e4544bdeea85f714e9368ce3b92fefe97efb39a0fb4a557abe \ No newline at end of file +c64e6a52ac79164be37fe643a4a39bd187af198a379410def8b8419f7c2224d4 \ No newline at end of file From 4b4a911c5f69a9a5ba5ff83f2f0f9a377e7e7119 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 13 Aug 2023 10:28:35 +0000 Subject: [PATCH 03/37] Add some docs and metrics for the new mutex internals. FossilOrigin-Name: 33d1780b43182d2574adbc1928707af825c485c99762738e58bc6d7c6c52ac6a --- ext/jni/src/c/sqlite3-jni.c | 33 ++++++++++++++++++++++----------- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 4522e05b45..eebe124f89 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -499,19 +499,25 @@ static struct { S3JniEnvCache * aHead /* Linked list of in-use instances */; S3JniEnvCache * aFree /* Linked list of free instances */; sqlite3_mutex * mutex /* mutex for aHead and aFree */; - void const * locker /* env mutex is held on this object's behalf */; + void const * locker /* env mutex is held on this object's behalf + (used only for sanity checking). */; } envCache; struct { S3JniDb * aUsed /* Linked list of in-use instances */; S3JniDb * aFree /* Linked list of free instances */; - sqlite3_mutex * mutex /* mutex for aUsed and aFree */; - void const * locker /* perDb mutex is held on this object's behalf */; + sqlite3_mutex * mutex /* mutex for aUsed and aFree */; + void const * locker /* perDb mutex is held on this object's + behalf. Unlike envCache.locker, we + cannot always have this set to the + current JNIEnv object. */; } perDb; struct { unsigned nphCacheHits; unsigned nphCacheMisses; unsigned envCacheHits; unsigned envCacheMisses; + unsigned nMutexEnv /* number of times envCache.mutex was entered */; + unsigned nMutexPerDb /* number of times perDb.mutex was entered */; unsigned nDestroy /* xDestroy() calls across all types */; struct { /* Number of calls for each type of UDF callback. */ @@ -542,30 +548,28 @@ static struct { #define MUTEX_ASSERT_LOCKER_ENV \ assert( (env) == S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) -#define MUTEX_ASSERT_LOCKER_PDB \ - assert( 0 != S3JniGlobal.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) #define MUTEX_ASSERT_NOTLOCKER_ENV \ assert( (env) != S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) -#define MUTEX_ASSERT_NOTLOCKER_PDB \ - assert( 0 == S3JniGlobal.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) #define MUTEX_ENTER_ENV \ /*MARKER(("Entering ENV mutex@%p %s.\n", env, __func__));*/ \ MUTEX_ASSERT_NOTLOCKER_ENV; \ sqlite3_mutex_enter( S3JniGlobal.envCache.mutex ); \ + ++S3JniGlobal.metrics.nMutexEnv; \ S3JniGlobal.envCache.locker = env #define MUTEX_LEAVE_ENV \ /*MARKER(("Leaving ENV mutex @%p %s.\n", env, __func__));*/ \ MUTEX_ASSERT_LOCKER_ENV; \ S3JniGlobal.envCache.locker = 0; \ sqlite3_mutex_leave( S3JniGlobal.envCache.mutex ) +#define MUTEX_ASSERT_LOCKED_PDB \ + assert( 0 != S3JniGlobal.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) #define MUTEX_ENTER_PDB \ /*MARKER(("Entering PerDb mutex@%p %s.\n", env, __func__));*/ \ - MUTEX_ASSERT_NOTLOCKER_PDB; \ sqlite3_mutex_enter( S3JniGlobal.perDb.mutex ); \ + ++S3JniGlobal.metrics.nMutexPerDb; \ S3JniGlobal.perDb.locker = env; #define MUTEX_LEAVE_PDB \ /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ - MUTEX_ASSERT_LOCKER_PDB; \ S3JniGlobal.perDb.locker = 0; \ sqlite3_mutex_leave( S3JniGlobal.perDb.mutex ) @@ -899,7 +903,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; - MUTEX_ASSERT_LOCKER_PDB; + MUTEX_ASSERT_LOCKED_PDB; assert(s->pDb && "Else this object is already in the free-list."); //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); assert(s->pPrev != s); @@ -1035,6 +1039,10 @@ static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zCl you should look them up once and then reuse them. Similarly, looking up class objects can be expensive, so they should be cached as well. + + Reminder: we do not need a mutex for the envRow->nph cache + because all nph entries are per-thread and envCache.mutex + already guards the fetching of envRow. */ struct S3JniEnvCache * const envRow = S3JniGlobal_env_cache(env); S3JniNphCache * freeSlot = 0; @@ -1153,7 +1161,7 @@ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zC static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, jobject jDb){ S3JniDb * rv; - MUTEX_ASSERT_LOCKER_PDB; + MUTEX_ASSERT_LOCKED_PDB; if(S3JniGlobal.perDb.aFree){ rv = S3JniGlobal.perDb.aFree; //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); @@ -3496,6 +3504,9 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ printf("\tJNIEnv cache %u misses, %u hits\n", S3JniGlobal.metrics.envCacheMisses, S3JniGlobal.metrics.envCacheHits); + printf("\tMutex entry: %u env, %u perDb\n", + S3JniGlobal.metrics.nMutexEnv, + S3JniGlobal.metrics.nMutexPerDb); puts("Java-side UDF calls:"); #define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3JniGlobal.metrics.udf.n##T) UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse); diff --git a/manifest b/manifest index d3b949dbf9..48db0129da 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C An\sinitial\sattempt\sat\sprotecting\sthe\sJNI\sglobal\sstate\svia\smutexes\sat\sthe\sC\slevel\sinstead\sof\srelying\son\sJava's\ssynchronized\skeyword.\sIt\sseems\sto\swork\sbut\sincreases\sthe\srun\stime\sof\sthe\ssingle-threaded\sbatch\stester\sby\sroughly\s3\stimes. -D 2023-08-13T09:53:27.824 +C Add\ssome\sdocs\sand\smetrics\sfor\sthe\snew\smutex\sinternals. +D 2023-08-13T10:28:35.456 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 93fbeed06441352df68f6c0e4bc2cd895e81d8550dc9972cafb30621958b4784 +F ext/jni/src/c/sqlite3-jni.c 44c1da6d1ae9d8ea2af16dd5c1d17224a655c9c94135892f81571b2481896431 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -2091,8 +2091,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 fbf99a2423dd20e4544bdeea85f714e9368ce3b92fefe97efb39a0fb4a557abe -R 42a3d0d4c63d13c26ffa7bd8f71380ed +P c64e6a52ac79164be37fe643a4a39bd187af198a379410def8b8419f7c2224d4 +R cd02d0fb58628c824a2d7de7efc47b02 U stephan -Z 5906c49c68c9155fc27b7d9430c5551e +Z 6ab46f9c79559c9df9412a6d1bef3261 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 684bcd68e8..0fe79c981a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c64e6a52ac79164be37fe643a4a39bd187af198a379410def8b8419f7c2224d4 \ No newline at end of file +33d1780b43182d2574adbc1928707af825c485c99762738e58bc6d7c6c52ac6a \ No newline at end of file From 88381e53fc9a689b88bb8a60b687653b5c7f820b Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 13 Aug 2023 12:40:27 +0000 Subject: [PATCH 04/37] Add a mutex for auto-extensions, tied in to the open() process since that's the route into auto-extensions. FossilOrigin-Name: 8da97e0db4eeacf91aa6fd909fd7cb73b050d194dfc7739a502b55f7eca6d7b1 --- ext/jni/src/c/sqlite3-jni.c | 197 ++++++++++++--------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 42 +++-- manifest | 14 +- manifest.uuid | 2 +- 4 files changed, 140 insertions(+), 115 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index eebe124f89..149feef554 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -400,8 +400,6 @@ static void S3JniNphCache_clear(JNIEnv * const env, S3JniNphCache * const p){ memset(p, 0, sizeof(S3JniNphCache)); } -#define S3JNI_ENABLE_AUTOEXT 1 -#if S3JNI_ENABLE_AUTOEXT /* Whether auto extensions are feasible here is currently unknown due to... @@ -430,7 +428,6 @@ struct S3JniAutoExtension { S3JniAutoExtension *pNext /* next linked-list entry */; S3JniAutoExtension *pPrev /* previous linked-list entry */; }; -#endif /** State for various hook callbacks. */ typedef struct S3JniHook S3JniHook; @@ -518,6 +515,7 @@ static struct { unsigned envCacheMisses; unsigned nMutexEnv /* number of times envCache.mutex was entered */; unsigned nMutexPerDb /* number of times perDb.mutex was entered */; + unsigned nMutexAutoExt /* number of times autoExt.mutex was entered */; unsigned nDestroy /* xDestroy() calls across all types */; struct { /* Number of calls for each type of UDF callback. */ @@ -528,10 +526,9 @@ static struct { unsigned nInverse; } udf; } metrics; -#if S3JNI_ENABLE_AUTOEXT struct { S3JniAutoExtension *pHead /* Head of the auto-extension list */; - S3JniDb * psOpening /* FIXME: move into envCache. Handle to the + S3JniDb * pdbOpening /* FIXME: move into envCache. Handle to the being-opened db. We need this so that auto extensions can have a consistent view of the cross-language db connection and @@ -542,36 +539,50 @@ static struct { manipulation of the auto-extension list while extensions are running. */; + sqlite3_mutex * mutex /* mutex for aUsed and aFree */; + void const * locker /* Mutex is locked on this object's behalf */; } autoExt; -#endif } S3JniGlobal; #define MUTEX_ASSERT_LOCKER_ENV \ assert( (env) == S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) #define MUTEX_ASSERT_NOTLOCKER_ENV \ assert( (env) != S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) -#define MUTEX_ENTER_ENV \ +#define MUTEX_ENTER_ENV \ /*MARKER(("Entering ENV mutex@%p %s.\n", env, __func__));*/ \ - MUTEX_ASSERT_NOTLOCKER_ENV; \ - sqlite3_mutex_enter( S3JniGlobal.envCache.mutex ); \ - ++S3JniGlobal.metrics.nMutexEnv; \ + MUTEX_ASSERT_NOTLOCKER_ENV; \ + sqlite3_mutex_enter( S3JniGlobal.envCache.mutex ); \ + ++S3JniGlobal.metrics.nMutexEnv; \ S3JniGlobal.envCache.locker = env -#define MUTEX_LEAVE_ENV \ +#define MUTEX_LEAVE_ENV \ /*MARKER(("Leaving ENV mutex @%p %s.\n", env, __func__));*/ \ - MUTEX_ASSERT_LOCKER_ENV; \ - S3JniGlobal.envCache.locker = 0; \ + MUTEX_ASSERT_LOCKER_ENV; \ + S3JniGlobal.envCache.locker = 0; \ sqlite3_mutex_leave( S3JniGlobal.envCache.mutex ) -#define MUTEX_ASSERT_LOCKED_PDB \ +#define MUTEX_ASSERT_LOCKED_PDB \ assert( 0 != S3JniGlobal.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) -#define MUTEX_ENTER_PDB \ +#define MUTEX_ENTER_PDB \ /*MARKER(("Entering PerDb mutex@%p %s.\n", env, __func__));*/ \ - sqlite3_mutex_enter( S3JniGlobal.perDb.mutex ); \ - ++S3JniGlobal.metrics.nMutexPerDb; \ + sqlite3_mutex_enter( S3JniGlobal.perDb.mutex ); \ + ++S3JniGlobal.metrics.nMutexPerDb; \ S3JniGlobal.perDb.locker = env; -#define MUTEX_LEAVE_PDB \ +#define MUTEX_LEAVE_PDB \ /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ - S3JniGlobal.perDb.locker = 0; \ + S3JniGlobal.perDb.locker = 0; \ sqlite3_mutex_leave( S3JniGlobal.perDb.mutex ) +#define MUTEX_ENTER_EXT \ + /*MARKER(("Entering autoExt mutex@%p %s.\n", env, __func__));*/ \ + sqlite3_mutex_enter( S3JniGlobal.autoExt.mutex ); \ + ++S3JniGlobal.metrics.nMutexAutoExt +#define MUTEX_TRY_EXT(FAIL_EXPR) \ + /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ + if( sqlite3_mutex_try( S3JniGlobal.autoExt.mutex ) ){ FAIL_EXPR; } \ + S3JniGlobal.autoExt.locker = env; \ + ++S3JniGlobal.metrics.nMutexAutoExt +#define MUTEX_LEAVE_EXT \ + /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ + S3JniGlobal.autoExt.locker = 0; \ + sqlite3_mutex_leave( S3JniGlobal.autoExt.mutex ) #define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) static void s3jni_oom(JNIEnv * const env){ @@ -1023,7 +1034,6 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ This simple cache catches >99% of searches in the current (2023-07-31) tests. */ -FIXME_THREADING(S3JniEnvCache) static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zClassName){ /** According to: @@ -1041,8 +1051,8 @@ static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zCl cached as well. Reminder: we do not need a mutex for the envRow->nph cache - because all nph entries are per-thread and envCache.mutex - already guards the fetching of envRow. + because all nph entries are per-thread and envCache.mutex already + guards the fetching of envRow. */ struct S3JniEnvCache * const envRow = S3JniGlobal_env_cache(env); S3JniNphCache * freeSlot = 0; @@ -1221,8 +1231,6 @@ static void S3JniDb_dump(S3JniDb *s){ Returns NULL if jDb and pDb are both NULL or if there is no matching S3JniDb entry for pDb or the pointer fished out of jDb. */ -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){ S3JniDb * s = 0; if(jDb || pDb){ @@ -1242,7 +1250,6 @@ static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){ return s; } -#if S3JNI_ENABLE_AUTOEXT /** Unlink ax from S3JniGlobal.autoExt and free it. */ @@ -1279,7 +1286,7 @@ static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, return 0; } ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", - "(Lorg/sqlite/jni/sqlite3;)I"); + "(Lorg/sqlite/jni/sqlite3;)I"); if(!ax->midFunc){ MARKER(("Error getting xEntryPoint(sqlite3) from object.")); S3JniAutoExtension_free(env, ax); @@ -1292,7 +1299,6 @@ static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, } return ax; } -#endif /* S3JNI_ENABLE_AUTOEXT */ /** Requires that jCx be a Java-side sqlite3_context wrapper for pCx. @@ -1881,35 +1887,37 @@ 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) -#if S3JNI_ENABLE_AUTOEXT +static JNIEnv * s3jni_get_env(void){ + JNIEnv * env = 0; + if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env, + JNI_VERSION_1_8) ){ + fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n"); + abort(); + } + return env; +} + /* Central auto-extension handler. */ -FIXME_THREADING(autoExt) -static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, - const struct sqlite3_api_routines *ignored){ +static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *ignored){ S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead; int rc; JNIEnv * env = 0; - S3JniDb * const ps = S3JniGlobal.autoExt.psOpening; - //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); - S3JniGlobal.autoExt.psOpening = 0; + S3JniDb * const ps = S3JniGlobal.autoExt.pdbOpening; + + assert( S3JniGlobal.autoExt.locker ); + assert( S3JniGlobal.autoExt.locker == ps ); + S3JniGlobal.autoExt.pdbOpening = 0; if( !pAX ){ assert( 0==S3JniGlobal.autoExt.isRunning ); return 0; - } - else if( S3JniGlobal.autoExt.isRunning ){ - /* Necessary to avoid certain endless loop/stack overflow cases. */ - *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while " - "auto-extensions are running."); - return SQLITE_MISUSE; - } - else if(!ps){ - MARKER(("Internal error: cannot find S3JniDb for auto-extension\n")); - return SQLITE_ERROR; - }else if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env, JNI_VERSION_1_8) ){ - assert(!"Cannot get JNIEnv"); - *pzErr = sqlite3_mprintf("Could not get current JNIEnv."); + }else if( S3JniGlobal.autoExt.locker != ps ) { + *pzErr = sqlite3_mprintf("Internal error: unexpected path lead to " + "running an auto-extension."); return SQLITE_ERROR; } + env = s3jni_get_env(); + //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); assert( !ps->pDb /* it's still being opened */ ); ps->pDb = pDb; assert( ps->jDb ); @@ -1941,8 +1949,9 @@ JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ S3JniAutoExtension * ax; if( !jAutoExt ) return SQLITE_MISUSE; - else if( 0==once && ++once ){ - sqlite3_auto_extension( (void(*)(void))s3jni_auto_extension ); + MUTEX_ENTER_EXT; + if( 0==once && ++once ){ + sqlite3_auto_extension( (void(*)(void))s3jni_run_java_auto_extensions ); } ax = S3JniGlobal.autoExt.pHead; for( ; ax; ax = ax->pNext ){ @@ -1950,9 +1959,10 @@ JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ return 0 /* C API treats this as a no-op. */; } } - return S3JniAutoExtension_alloc(env, jAutoExt) ? 0 : SQLITE_NOMEM; + ax = S3JniAutoExtension_alloc(env, jAutoExt); + MUTEX_LEAVE_EXT; + return ax ? 0 : SQLITE_NOMEM; } -#endif /* S3JNI_ENABLE_AUTOEXT */ FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, @@ -2088,20 +2098,23 @@ JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ return SQLITE_MISUSE; } -#if S3JNI_ENABLE_AUTOEXT FIXME_THREADING(autoExt) JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ - S3JniAutoExtension * ax;; - if( S3JniGlobal.autoExt.isRunning ) return JNI_FALSE; - for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ - if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - S3JniAutoExtension_free(env, ax); - return JNI_TRUE; + S3JniAutoExtension * ax; + jboolean rc = JNI_FALSE; + MUTEX_ENTER_EXT; + if( !S3JniGlobal.autoExt.isRunning ) { + for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ + if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + S3JniAutoExtension_free(env, ax); + rc = JNI_TRUE; + break; + } } } - return JNI_FALSE; + MUTEX_LEAVE_EXT; + return rc; } -#endif /* S3JNI_ENABLE_AUTOEXT */ /** @@ -2649,27 +2662,41 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, jstring jDbName, char **zDbName, S3JniDb ** ps, jobject *jDb){ + int rc = 0; + MUTEX_TRY_EXT(return SQLITE_BUSY); *jc = S3JniGlobal_env_cache(env); - if(!*jc) return SQLITE_NOMEM; + if(!*jc){ + rc = SQLITE_NOMEM; + goto end; + } *zDbName = jDbName ? s3jni_jstring_to_utf8(*jc, jDbName, 0) : 0; - if(jDbName && !*zDbName) return SQLITE_NOMEM; + if(jDbName && !*zDbName){ + rc = SQLITE_NOMEM; + goto end; + } *jDb = new_sqlite3_wrapper(env, 0); if( !*jDb ){ sqlite3_free(*zDbName); *zDbName = 0; - return SQLITE_NOMEM; + rc = SQLITE_NOMEM; + goto end; } MUTEX_ENTER_PDB; *ps = S3JniDb_alloc(env, 0, *jDb); MUTEX_LEAVE_PDB; -#if S3JNI_ENABLE_AUTOEXT if(*ps){ - assert(!S3JniGlobal.autoExt.psOpening); - S3JniGlobal.autoExt.psOpening = *ps; + S3JniGlobal.autoExt.pdbOpening = *ps; + S3JniGlobal.autoExt.locker = *ps; + }else{ + rc = SQLITE_NOMEM; } -#endif //MARKER(("pre-open ps@%p\n", *ps)); - return *ps ? 0 : SQLITE_NOMEM; +end: + /* Remain in autoExt.mutex until s3jni_open_post(). */ + if(rc){ + MUTEX_LEAVE_EXT; + } + return rc; } /** @@ -2686,10 +2713,8 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, sqlite3 **ppDb, jobject jOut, int theRc){ //MARKER(("post-open() ps@%p db@%p\n", ps, *ppDb)); -#if S3JNI_ENABLE_AUTOEXT - assert( S3JniGlobal.autoExt.pHead ? ps!=S3JniGlobal.autoExt.psOpening : 1 ); - S3JniGlobal.autoExt.psOpening = 0; -#endif + assert( S3JniGlobal.autoExt.locker == ps ); + S3JniGlobal.autoExt.pdbOpening = 0; if(*ppDb){ assert(ps->jDb); ps->pDb = *ppDb; @@ -2701,6 +2726,7 @@ static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, ps = 0; } OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); + MUTEX_LEAVE_EXT; return theRc; } @@ -2710,8 +2736,8 @@ JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ jobject jDb = 0; S3JniDb * ps = 0; S3JniEnvCache * jc = 0; - S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; - int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + S3JniDb * const prevOpening = S3JniGlobal.autoExt.pdbOpening; + int rc= s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); if( 0==rc ){ rc = sqlite3_open(zName, &pOut); //MARKER(("env=%p, *env=%p\n", env, *env)); @@ -2720,7 +2746,7 @@ JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); } - S3JniGlobal.autoExt.psOpening = prevOpening; + S3JniGlobal.autoExt.pdbOpening = prevOpening; return (jint)rc; } @@ -2732,7 +2758,7 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, S3JniDb * ps = 0; S3JniEnvCache * jc = 0; char *zVfs = 0; - S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; + S3JniDb * const prevOpening = S3JniGlobal.autoExt.pdbOpening; int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); if( 0==rc && strVfs ){ zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); @@ -2747,10 +2773,10 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", zName, zVfs, pOut, (int)flags, nrc));*/ rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + S3JniGlobal.autoExt.pdbOpening = prevOpening; assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); sqlite3_free(zVfs); - S3JniGlobal.autoExt.psOpening = prevOpening; return (jint)rc; } @@ -2886,15 +2912,13 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ return rc; } -#if S3JNI_ENABLE_AUTOEXT JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ - if(!S3JniGlobal.autoExt.isRunning){ - while( S3JniGlobal.autoExt.pHead ){ - S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead); - } + MUTEX_ENTER_EXT; + while( S3JniGlobal.autoExt.pHead ){ + S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead); } + MUTEX_LEAVE_EXT; } -#endif /* S3JNI_ENABLE_AUTOEXT */ /* sqlite3_result_text/blob() and friends. */ static void result_blob_text(int asBlob, int as64, @@ -3504,9 +3528,10 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ printf("\tJNIEnv cache %u misses, %u hits\n", S3JniGlobal.metrics.envCacheMisses, S3JniGlobal.metrics.envCacheHits); - printf("\tMutex entry: %u env, %u perDb\n", + printf("Mutex entry:\n\t%u env\n\t%u perDb\n\t%u autoExt (mostly via open[_v2]())\n", S3JniGlobal.metrics.nMutexEnv, - S3JniGlobal.metrics.nMutexPerDb); + S3JniGlobal.metrics.nMutexPerDb, + S3JniGlobal.metrics.nMutexAutoExt); puts("Java-side UDF calls:"); #define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3JniGlobal.metrics.udf.n##T) UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse); @@ -4418,6 +4443,8 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ 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 ); #if 0 /* Just for sanity checking... */ (void)S3JniGlobal_env_cache(env); diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index e7aa10ebae..dbd2e4bae7 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -178,24 +178,25 @@ public final class SQLite3Jni { not have access to the sqlite3_api object which native auto-extensions do. - - If an auto-extension opens a db, thereby triggering recursion - in the auto-extension handler, it will fail with a message - explaining that recursion is not permitted. + - If an auto-extension opens a db, opening will fail with SQLITE_BUSY. + The alternative would be endless recursion into the auto-extension. - - All of the other auto extension routines will fail without side - effects if invoked from within the execution of an - auto-extension. i.e. auto extensions can neither be added, + - The list of auto-extensions must not be manipulated from within + an auto-extension. Auto extensions can neither be added, removed, nor cleared while one registered with this function is - running. Auto-extensions registered directly with the library - via C code, as opposed to indirectly via Java, do not have that - limitation. + running. Attempting to do so may lead to a deadlock. 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. + + Design note: this family of methods is synchronized in order to + help avoid a small race condition where an in-progress + sqlite3_reset_auto_extension() or sqlite3_cancel_auto_extension() + could cause sqlite3_open() to fail with SQLITE_BUSY. */ - public static native int sqlite3_auto_extension(@NotNull AutoExtension callback); + public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback); public static int sqlite3_bind_blob( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data @@ -296,7 +297,7 @@ public final class SQLite3Jni { effects, if auto extensions are currently running. (The JNI-level list of extensions cannot be manipulated while it is being traversed.) */ - public static native boolean sqlite3_cancel_auto_extension( + public static synchronized native boolean sqlite3_cancel_auto_extension( @NotNull AutoExtension ax ); @@ -313,11 +314,11 @@ public final class SQLite3Jni { ); public static native int sqlite3_close( - @NotNull sqlite3 db + @Nullable sqlite3 db ); public static native int sqlite3_close_v2( - @NotNull sqlite3 db + @Nullable sqlite3 db ); public static native byte[] sqlite3_column_blob( @@ -593,19 +594,16 @@ public final class SQLite3Jni { Recall that even if opening fails, the output pointer might be non-null. Any error message about the failure will be in that object and it is up to the caller to sqlite3_close() that - db handle. + db handle. Passing a null to sqlite3_close() is legal. - Pedantic note: though any number of Java-level sqlite3 objects - may refer to/wrap a single C-level (sqlite3*), the JNI internals - take a reference to the object which is passed to sqlite3_open() - or sqlite3_open_v2() so that they have a predictible object to - pass to, e.g., the sqlite3_collation_needed() callback. + Design note: this method is synchronized in order to help + alleviate a race condition involving auto-extensions. */ - public static native int sqlite3_open( + public static synchronized native int sqlite3_open( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb ); - public static native int sqlite3_open_v2( + public static synchronized native int sqlite3_open_v2( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, int flags, @Nullable String zVfs ); @@ -729,7 +727,7 @@ public final class SQLite3Jni { extensions are currently running. (The JNI-level list of extensions cannot be manipulated while it is being traversed.) */ - public static native void sqlite3_reset_auto_extension(); + public static synchronized native void sqlite3_reset_auto_extension(); public static native void sqlite3_result_double( @NotNull sqlite3_context cx, double v diff --git a/manifest b/manifest index 48db0129da..dbbd37eaaa 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssome\sdocs\sand\smetrics\sfor\sthe\snew\smutex\sinternals. -D 2023-08-13T10:28:35.456 +C Add\sa\smutex\sfor\sauto-extensions,\stied\sin\sto\sthe\sopen()\sprocess\ssince\sthat's\sthe\sroute\sinto\sauto-extensions. +D 2023-08-13T12:40:27.217 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 44c1da6d1ae9d8ea2af16dd5c1d17224a655c9c94135892f81571b2481896431 +F ext/jni/src/c/sqlite3-jni.c 4f6f8f2dec309a6b117a6e8f460078b5f6f6b65a17c530ae2f5907b6425d714c F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -254,7 +254,7 @@ 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 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 7933fabc267dbfdccb784563d8c404f90275fe9418e8cd6c43c584bc9c57f70f +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5eeba0b1a00fb34bc93fe60186f6032fcf4d568fc5868d70029883d3d07cc306 F ext/jni/src/org/sqlite/jni/Tester1.java fc2ec1f1be58474112b9df8284f0157b64872107f446154c3d0bf1742b924d2b F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2091,8 +2091,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 c64e6a52ac79164be37fe643a4a39bd187af198a379410def8b8419f7c2224d4 -R cd02d0fb58628c824a2d7de7efc47b02 +P 33d1780b43182d2574adbc1928707af825c485c99762738e58bc6d7c6c52ac6a +R 6a1ce79c2368c04d74cde070b44b6572 U stephan -Z 6ab46f9c79559c9df9412a6d1bef3261 +Z d637a436f204decfbfbf40fa44769c67 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0fe79c981a..4f1f622ee9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -33d1780b43182d2574adbc1928707af825c485c99762738e58bc6d7c6c52ac6a \ No newline at end of file +8da97e0db4eeacf91aa6fd909fd7cb73b050d194dfc7739a502b55f7eca6d7b1 \ No newline at end of file From 6a1ed4c811d3a62df9ff7a9f9662fe7060b6c271 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 13 Aug 2023 20:58:12 +0000 Subject: [PATCH 05/37] Internal API renaming for clarity's sake. FossilOrigin-Name: 911e4fc5aaf9478214095a65f74af3ebca883922c36cf7a8d911116c42cf9de8 --- ext/jni/src/c/sqlite3-jni.c | 133 ++++++++++++++++++------------------ manifest | 12 ++-- manifest.uuid | 2 +- 3 files changed, 72 insertions(+), 75 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 149feef554..55ecd4b2dc 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -329,7 +329,8 @@ enum { }; /** - Cache entry for NativePointerHolder lookups. + Cache entry for NativePointerHolder subclasses and OutputPointer + types. */ typedef struct S3JniNphCache S3JniNphCache; struct S3JniNphCache { @@ -358,8 +359,8 @@ struct S3JniNphCache { Whereas we cache new refs for each thread. */ -typedef struct S3JniEnvCache S3JniEnvCache; -struct S3JniEnvCache { +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 @@ -377,20 +378,15 @@ struct S3JniEnvCache { jobject jFtsExt /* Global ref to Java singleton for the Fts5ExtensionApi instance. */; struct { - jclass klazz; - jfieldID fidA; - jfieldID fidB; + jclass klazz /* Global ref to the Fts5Phrase iter class */; + jfieldID fidA /* Fts5Phrase::a member */; + jfieldID fidB /* Fts5Phrase::b member */; } jPhraseIter; #endif - S3JniEnvCache * pPrev /* Previous entry in the linked list */; - S3JniEnvCache * pNext /* Next entry in the linked list */; - /** TODO?: S3JniNphCache *pNphHit; - - and always set it to the most recent cache search result. - - The intent would be to help fast-track cache lookups and would - speed up, e.g., the sqlite3_value-to-Java-array loop in a - multi-threaded app. + S3JniEnv * pPrev /* Previous entry in the linked list */; + S3JniEnv * pNext /* Next entry in the linked list */; + /** + Cache of Java refs/IDs for NativePointerHolder subclasses. */ S3JniNphCache nph[NphCache_SIZE]; }; @@ -493,8 +489,8 @@ static struct { */ JavaVM * jvm; struct { - S3JniEnvCache * aHead /* Linked list of in-use instances */; - S3JniEnvCache * aFree /* Linked list of free instances */; + S3JniEnv * aHead /* Linked list of in-use instances */; + S3JniEnv * aFree /* Linked list of free instances */; sqlite3_mutex * mutex /* mutex for aHead and aFree */; void const * locker /* env mutex is held on this object's behalf (used only for sanity checking). */; @@ -607,8 +603,8 @@ static void * s3jni_malloc(JNIEnv * const env, size_t n){ insofar as possible. Calls (*env)->FatalError() if allocation of an entry fails. That's hypothetically possible but "shouldn't happen." */ -static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ - struct S3JniEnvCache * row; +static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){ + struct S3JniEnv * row; MUTEX_ENTER_ENV; row = S3JniGlobal.envCache.aHead; for( ; row; row = row->pNext ){ @@ -625,7 +621,7 @@ static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ S3JniGlobal.envCache.aFree = row->pNext; if( row->pNext ) row->pNext->pPrev = 0; }else{ - row = sqlite3_malloc(sizeof(S3JniEnvCache)); + row = sqlite3_malloc(sizeof(S3JniEnv)); OOM_CHECK(row); } memset(row, 0, sizeof(*row)); @@ -721,7 +717,7 @@ static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * standard UTF-8 to a Java string, but JNI offers only algorithms for working with MUTF-8, not UTF-8. */ -static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc, +static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc, const char * const z, int n){ jstring rv = NULL; JNIEnv * const env = jc->env; @@ -758,7 +754,7 @@ static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc, The returned memory is allocated from sqlite3_malloc() and ownership is transferred to the caller. */ -static char * s3jni_jstring_to_utf8(S3JniEnvCache * const jc, +static char * s3jni_jstring_to_utf8(S3JniEnv * const jc, jstring jstr, int *nLen){ JNIEnv * const env = jc->env; jbyteArray jba; @@ -813,7 +809,7 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, } */ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); jmethodID mid; jstring msg; char * zMsg; @@ -981,7 +977,7 @@ static void S3JniDb_free_for_env(JNIEnv *env){ what would otherwise be stale references. */ static int S3JniGlobal_env_uncache(JNIEnv * const env){ - struct S3JniEnvCache * row; + struct S3JniEnv * row; int i; assert( 0!=S3JniGlobal.envCache.mutex && "Env mutex misuse."); row = S3JniGlobal.envCache.aHead; @@ -1011,7 +1007,7 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ for( i = 0; i < NphCache_SIZE; ++i ){ S3JniNphCache_clear(env, &row->nph[i]); } - memset(row, 0, sizeof(S3JniEnvCache)); + memset(row, 0, sizeof(S3JniEnv)); row->pNext = S3JniGlobal.envCache.aFree; if( row->pNext ) row->pNext->pPrev = row; S3JniGlobal.envCache.aFree = row; @@ -1054,7 +1050,7 @@ static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zCl because all nph entries are per-thread and envCache.mutex already guards the fetching of envRow. */ - struct S3JniEnvCache * const envRow = S3JniGlobal_env_cache(env); + struct S3JniEnv * const envRow = S3JniGlobal_env_cache(env); S3JniNphCache * freeSlot = 0; S3JniNphCache * pCache = 0; int i; @@ -1438,6 +1434,7 @@ static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); } #endif + /* Sets the value property of the OutputPointer.String jOut object to v. */ static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, @@ -1964,7 +1961,7 @@ JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ return ax ? 0 : SQLITE_NOMEM; } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ int rc; @@ -1979,31 +1976,31 @@ JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, return (jint)rc; } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt, jint ndx, jdouble val){ return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt, jint ndx, jint val){ return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt, jint ndx, jlong val){ return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){ int rc = 0; jbyte * const pBuf = JBA_TOC(jName); @@ -2015,7 +2012,7 @@ JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName return rc; } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ if(baData){ @@ -2029,13 +2026,13 @@ JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, } } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt, jint ndx, jint n){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, jint ndx, jlong n){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); @@ -2057,7 +2054,7 @@ static int s3jni_busy_handler(void* pState, int n){ return rc; } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); int rc = 0; @@ -2087,7 +2084,7 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ : sqlite3_busy_handler(ps->pDb, 0, 0); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) FIXME_THREADING(perDb) JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); @@ -2136,13 +2133,13 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ return (jint)rc; } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) FIXME_THREADING(perDb) JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){ return s3jni_close_db(env, pDb, 2); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) FIXME_THREADING(perDb) JDECL(jint,1close)(JENV_CSELF, jobject pDb){ return s3jni_close_db(env, pDb, 1); @@ -2182,7 +2179,7 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, UNREF_L(jName); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) FIXME_THREADING(perDb) JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); @@ -2220,7 +2217,7 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ return rc; } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); @@ -2234,25 +2231,25 @@ JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, } } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); @@ -2261,7 +2258,7 @@ JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, return s3jni_new_jbyteArray(env, p, n); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); @@ -2270,7 +2267,7 @@ JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, return s3jni_text16_to_jstring(env, p, n); } -FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(S3JniEnv) JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); @@ -2554,7 +2551,7 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); char *zDbName; jstring jRv = 0; int nStr = 0; @@ -2594,7 +2591,7 @@ JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){ JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); - S3JniEnvCache * const jc = pDb ? S3JniGlobal_env_cache(env) : 0; + S3JniEnv * const jc = pDb ? S3JniGlobal_env_cache(env) : 0; return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0; } @@ -2608,7 +2605,7 @@ JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); jstring rv = 0; if( pStmt ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); char * zSql = sqlite3_expanded_sql(pStmt); OOM_CHECK(zSql); if( zSql ){ @@ -2659,7 +2656,7 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ } //! Pre-open() code common to sqlite3_open(_v2)(). -static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, +static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, jstring jDbName, char **zDbName, S3JniDb ** ps, jobject *jDb){ int rc = 0; @@ -2735,7 +2732,7 @@ JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ char *zName = 0; jobject jDb = 0; S3JniDb * ps = 0; - S3JniEnvCache * jc = 0; + S3JniEnv * jc = 0; S3JniDb * const prevOpening = S3JniGlobal.autoExt.pdbOpening; int rc= s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); if( 0==rc ){ @@ -2756,7 +2753,7 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, char *zName = 0; jobject jDb = 0; S3JniDb * ps = 0; - S3JniEnvCache * jc = 0; + S3JniEnv * jc = 0; char *zVfs = 0; S3JniDb * const prevOpening = S3JniGlobal.autoExt.pdbOpening; int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); @@ -3235,7 +3232,7 @@ JDECL(jstring,1sql)(JENV_CSELF, jobject jpStmt){ jstring rv = 0; if( pStmt ){ const char * zSql = 0; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); zSql = sqlite3_sql(pStmt); rv = s3jni_utf8_to_jstring(jc, zSql, -1); OOM_CHECK(rv); @@ -3258,7 +3255,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ 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 */; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); int rc; int createStmt = 0; switch(traceflag){ @@ -3512,7 +3509,7 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ puts("sizeofs:"); #define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) SO(void*); - SO(S3JniEnvCache); + SO(S3JniEnv); SO(S3JniHook); SO(S3JniDb); SO(S3JniClassNames); @@ -3642,7 +3639,7 @@ 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){ - S3JniEnvCache * const row = S3JniGlobal_env_cache(env); + S3JniEnv * const row = S3JniGlobal_env_cache(env); if( !row->jFtsExt ){ row->jFtsExt = new_NativePointerHolder_object(env, S3JniClassNames.Fts5ExtensionApi, s3jni_ftsext()); @@ -3709,7 +3706,7 @@ JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, &pz, &pn); if( 0==rc ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0; if( pz ){ if( jstr ){ @@ -3866,7 +3863,7 @@ JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){ /** Initializes jc->jPhraseIter if it needed it. */ -static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnvCache * const jc, +static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnv * const jc, jobject jIter){ if(!jc->jPhraseIter.klazz){ jclass klazz = (*env)->GetObjectClass(env, jIter); @@ -3879,7 +3876,7 @@ static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnvCache * const jc, } /* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */ -static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnvCache const * const jc, +static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnv const * const jc, Fts5PhraseIter const * const pSrc, jobject jIter){ assert(jc->jPhraseIter.klazz); @@ -3890,7 +3887,7 @@ static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnvCache const * const } /* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */ -static void s3jni_phraseIter_JToN(JNIEnv *const env, S3JniEnvCache const * const jc, +static void s3jni_phraseIter_JToN(JNIEnv *const env, S3JniEnv const * const jc, jobject jIter, Fts5PhraseIter * const pDest){ assert(jc->jPhraseIter.klazz); pDest->a = @@ -3905,7 +3902,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0, iOff = 0; s3jni_phraseIter_init(env, jc, jIter); @@ -3922,7 +3919,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0; s3jni_phraseIter_init(env, jc, jIter); @@ -3938,7 +3935,7 @@ JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int iCol = 0, iOff = 0; if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; @@ -3953,7 +3950,7 @@ JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int iCol = 0; if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; @@ -3975,7 +3972,7 @@ JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){ struct s3jni_xQueryPhraseState { JNIEnv *env; Fts5ExtensionApi const * fext; - S3JniEnvCache const * jc; + S3JniEnv const * jc; jmethodID midCallback; jobject jCallback; jobject jFcx; @@ -4007,7 +4004,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, jobject jCallback){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); struct s3jni_xQueryPhraseState s; jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; if( !klazz ) return SQLITE_MISUSE; @@ -4093,7 +4090,7 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, jint tokFlags, jobject jFcx, jbyteArray jbaText, jobject jCallback){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniGlobal_env_cache(env); struct s3jni_xQueryPhraseState s; int rc = 0; jbyte * const pText = jCallback ? JBA_TOC(jbaText) : 0; diff --git a/manifest b/manifest index dbbd37eaaa..ad2950dc49 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\smutex\sfor\sauto-extensions,\stied\sin\sto\sthe\sopen()\sprocess\ssince\sthat's\sthe\sroute\sinto\sauto-extensions. -D 2023-08-13T12:40:27.217 +C Internal\sAPI\srenaming\sfor\sclarity's\ssake. +D 2023-08-13T20:58:12.729 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 4f6f8f2dec309a6b117a6e8f460078b5f6f6b65a17c530ae2f5907b6425d714c +F ext/jni/src/c/sqlite3-jni.c 1b1f3fa286476933171e75465214deab44c55714aaaa80b5ce145e89f10b0df8 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -2091,8 +2091,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 33d1780b43182d2574adbc1928707af825c485c99762738e58bc6d7c6c52ac6a -R 6a1ce79c2368c04d74cde070b44b6572 +P 8da97e0db4eeacf91aa6fd909fd7cb73b050d194dfc7739a502b55f7eca6d7b1 +R 582810911695a14eb1f701cb46e67a37 U stephan -Z d637a436f204decfbfbf40fa44769c67 +Z 414dea18ee7e2187dd201f9308cbe98b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4f1f622ee9..6ade59ea97 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8da97e0db4eeacf91aa6fd909fd7cb73b050d194dfc7739a502b55f7eca6d7b1 \ No newline at end of file +911e4fc5aaf9478214095a65f74af3ebca883922c36cf7a8d911116c42cf9de8 \ No newline at end of file From d518e94adbf2555b603bee5f2aa628f82cf1fdd9 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 14 Aug 2023 08:28:46 +0000 Subject: [PATCH 06/37] JNI-internal docs and removal of obsolete code. FossilOrigin-Name: b62d93258b6a661f3a9b61468b3b641c14faf2d2196f78aca95fe14de43c9444 --- ext/jni/src/c/sqlite3-jni.c | 81 ++++++++++++++++--------- ext/jni/src/org/sqlite/jni/Tester1.java | 9 ++- manifest | 14 ++--- manifest.uuid | 2 +- 4 files changed, 70 insertions(+), 36 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 55ecd4b2dc..129a601210 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -432,9 +432,11 @@ struct S3JniHook{ jmethodID midCallback /* callback method. Signature depends on jObj's type */; jclass klazz /* global ref to jObj's class. Only needed - by hooks which have an xDestroy() method, - as lookup of that method is deferred - until the object requires cleanup. */; + 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. */; }; /** @@ -522,19 +524,38 @@ static struct { unsigned nInverse; } udf; } metrics; + /** + The list of bound auto-extensions (Java-side: + org.sqlite.jni.AutoExtension objects). Because this data + structure cannot be manipulated during traversal (without adding + more code to deal with it), and to avoid a small window where a + call to sqlite3_reset/clear_auto_extension() could lock the + structure during an open() call, we lock this mutex before + sqlite3_open() is called and unlock it once sqlite3_open() + returns. + */ struct { S3JniAutoExtension *pHead /* Head of the auto-extension list */; - S3JniDb * pdbOpening /* FIXME: move into envCache. Handle to the - being-opened db. We need this so that auto - extensions can have a consistent view of - the cross-language db connection and - behave property if they call further db - APIs. */; - int isRunning /* True while auto extensions are - running. This is used to prohibit - manipulation of the auto-extension - list while extensions are - running. */; + /** + pdbOpening is used to coordinate the Java/DB connection of a + being-open()'d db. "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: + + - At the start of open(), we lock on this->mutex. + - Allocate the Java side of that connection and set pdbOpening + to point to that object. + - Call open(), which triggers the auto-extension handler. + That handler uses pdbOpening to connect the native db handle + which it recieves with pdbOpening. + - Return from open(). + - Clean up and unlock the mutex. + + If open() did not block on a mutex, there would be a race + condition in which two open() calls could set pdbOpening. + */ + S3JniDb * pdbOpening; sqlite3_mutex * mutex /* mutex for aUsed and aFree */; void const * locker /* Mutex is locked on this object's behalf */; } autoExt; @@ -1906,7 +1927,6 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, assert( S3JniGlobal.autoExt.locker == ps ); S3JniGlobal.autoExt.pdbOpening = 0; if( !pAX ){ - assert( 0==S3JniGlobal.autoExt.isRunning ); return 0; }else if( S3JniGlobal.autoExt.locker != ps ) { *pzErr = sqlite3_mprintf("Internal error: unexpected path lead to " @@ -1919,7 +1939,6 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, ps->pDb = pDb; assert( ps->jDb ); NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3); - ++S3JniGlobal.autoExt.isRunning; for( ; pAX; pAX = pAX->pNext ){ rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); IFTHREW { @@ -1936,7 +1955,6 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, break; } } - --S3JniGlobal.autoExt.isRunning; return rc; } @@ -2100,13 +2118,11 @@ JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ S3JniAutoExtension * ax; jboolean rc = JNI_FALSE; MUTEX_ENTER_EXT; - if( !S3JniGlobal.autoExt.isRunning ) { - for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ - if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - S3JniAutoExtension_free(env, ax); - rc = JNI_TRUE; - break; - } + for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ + if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + S3JniAutoExtension_free(env, ax); + rc = JNI_TRUE; + break; } } MUTEX_LEAVE_EXT; @@ -2660,7 +2676,14 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, jstring jDbName, char **zDbName, S3JniDb ** ps, jobject *jDb){ int rc = 0; - MUTEX_TRY_EXT(return SQLITE_BUSY); + MUTEX_TRY_EXT(return SQLITE_BUSY) + /* we don't wait forever here because it could lead to a deadlock + if an auto-extension opens a database. Without a mutex, that + situation leads to infinite recursion and stack overflow, which + is infinitely easier to track down from client code. Note that + we rely on the Java methods for open() and auto-extension + handling to be synchronized so that this BUSY cannot be + triggered by a race condition with those functions. */; *jc = S3JniGlobal_env_cache(env); if(!*jc){ rc = SQLITE_NOMEM; @@ -2714,8 +2737,12 @@ static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, S3JniGlobal.autoExt.pdbOpening = 0; if(*ppDb){ assert(ps->jDb); - ps->pDb = *ppDb; - NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); + if( 0==ps->pDb ){ + ps->pDb = *ppDb; + NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); + }else{ + assert( ps->pDb == *ppDb /* set up via s3jni_run_java_auto_extensions() */); + } }else{ MUTEX_ENTER_PDB; S3JniDb_set_aside(ps); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index e9524e49a7..055e070bfd 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -1079,10 +1079,17 @@ public class Tester1 { affirm( 0==rc ); sqlite3_close( createNewDb() ); affirm( 3==val.value ); + + sqlite3 db = createNewDb(); + affirm( 4==val.value ); + execSql(db, "ATTACH ':memory' as foo"); + affirm( 4==val.value /* ATTACH uses the same connection, not sub-connections. */ ); + sqlite3_close(db); + affirm( sqlite3_cancel_auto_extension(ax) ); affirm( !sqlite3_cancel_auto_extension(ax) ); sqlite3_close(createNewDb()); - affirm( 3==val.value ); + affirm( 4==val.value ); rc = sqlite3_auto_extension( ax ); affirm( 0==rc ); Exception err = null; diff --git a/manifest b/manifest index ad2950dc49..8673db6eeb 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Internal\sAPI\srenaming\sfor\sclarity's\ssake. -D 2023-08-13T20:58:12.729 +C JNI-internal\sdocs\sand\sremoval\sof\sobsolete\scode. +D 2023-08-14T08:28:46.677 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 1b1f3fa286476933171e75465214deab44c55714aaaa80b5ce145e89f10b0df8 +F ext/jni/src/c/sqlite3-jni.c 1bc1cbaf075065793f83cf5bfba631216ced48d7beae0768ebd838e923e7e590 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -255,7 +255,7 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5eeba0b1a00fb34bc93fe60186f6032fcf4d568fc5868d70029883d3d07cc306 -F ext/jni/src/org/sqlite/jni/Tester1.java fc2ec1f1be58474112b9df8284f0157b64872107f446154c3d0bf1742b924d2b +F ext/jni/src/org/sqlite/jni/Tester1.java 368e836d943d9e882d2a217d0f582ed4141d164f174bebc50715acd57549a09b F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2091,8 +2091,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 8da97e0db4eeacf91aa6fd909fd7cb73b050d194dfc7739a502b55f7eca6d7b1 -R 582810911695a14eb1f701cb46e67a37 +P 911e4fc5aaf9478214095a65f74af3ebca883922c36cf7a8d911116c42cf9de8 +R c6d49900ed87c14449d9c08a36536c71 U stephan -Z 414dea18ee7e2187dd201f9308cbe98b +Z 09908deaf1ed6a1fae2d9bef97861a7c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 6ade59ea97..7a0d99b2d3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -911e4fc5aaf9478214095a65f74af3ebca883922c36cf7a8d911116c42cf9de8 \ No newline at end of file +b62d93258b6a661f3a9b61468b3b641c14faf2d2196f78aca95fe14de43c9444 \ No newline at end of file From 7f2dea75ad0b5ada4de690e84ed07430e64059f8 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 14 Aug 2023 13:27:40 +0000 Subject: [PATCH 07/37] More work on the JNI-specific mutexes. Rework the NativePointerHolder cache lookup to be slightly simpler and O(1) instead of O(N). FossilOrigin-Name: c84ded0e59aea4861d72b53b4b40cf580747c0f6ca58c334a996f1a825276cb5 --- ext/jni/src/c/sqlite3-jni.c | 331 +++++++++------------ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 5 +- manifest | 14 +- manifest.uuid | 2 +- 4 files changed, 159 insertions(+), 193 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 129a601210..d33329575d 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -105,7 +105,7 @@ # define SQLITE_TEMP_STORE 2 #endif #ifndef SQLITE_THREADSAFE -# define SQLITE_THREADSAFE 0 +# define SQLITE_THREADSAFE 1 #endif /**********************************************************************/ @@ -189,10 +189,10 @@ /** Helpers for extracting pointers from jobjects, noting that the corresponding Java interfaces have already done the type-checking. */ -#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3) -#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_stmt) -#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_value) -#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_context) +#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3) +#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3_stmt) +#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3_value) +#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3_context) /* Helpers for Java value reference management. */ static inline jobject new_global_ref(JNIEnv * const env, jobject const v){ return v ? (*env)->NewGlobalRef(env, v) : NULL; @@ -209,47 +209,55 @@ static inline void delete_local_ref(JNIEnv * const env, jobject const v){ #define UNREF_L(VAR) delete_local_ref(env,(VAR)) /** - Constant string class names used as keys for S3JniGlobal_nph_cache(), -S3Jni - and - friends. + Keys for use with S3JniGlobal_nph_cache(). +*/ +typedef struct S3NphRef S3NphRef; +struct S3NphRef { + const int index /* index into S3JniEnv->nph[] */; + const char * const zName /* Full Java name of the class */; +}; + +/** + Keys for each concrete NativePointerHolder subclass. */ static const struct { - const char * const sqlite3; - const char * const sqlite3_stmt; - const char * const sqlite3_context; - const char * const sqlite3_value; - const char * const OutputPointer_Int32; - const char * const OutputPointer_Int64; - const char * const OutputPointer_String; - const char * const OutputPointer_ByteArray; - const char * const OutputPointer_sqlite3; - const char * const OutputPointer_sqlite3_stmt; + const S3NphRef sqlite3; + const S3NphRef sqlite3_stmt; + const S3NphRef sqlite3_context; + const S3NphRef sqlite3_value; + const S3NphRef OutputPointer_Int32; + const S3NphRef OutputPointer_Int64; + const S3NphRef OutputPointer_String; + const S3NphRef OutputPointer_ByteArray; + const S3NphRef OutputPointer_sqlite3; + const S3NphRef OutputPointer_sqlite3_stmt; #ifdef SQLITE_ENABLE_FTS5 - const char * const Fts5Context; - const char * const Fts5ExtensionApi; - const char * const fts5_api; - const char * const fts5_tokenizer; - const char * const Fts5Tokenizer; + const S3NphRef Fts5Context; + const S3NphRef Fts5ExtensionApi; + const S3NphRef fts5_api; + const S3NphRef fts5_tokenizer; + const S3NphRef Fts5Tokenizer; #endif -} S3JniClassNames = { - "org/sqlite/jni/sqlite3", - "org/sqlite/jni/sqlite3_stmt", - "org/sqlite/jni/sqlite3_context", - "org/sqlite/jni/sqlite3_value", - "org/sqlite/jni/OutputPointer$Int32", - "org/sqlite/jni/OutputPointer$Int64", - "org/sqlite/jni/OutputPointer$String", - "org/sqlite/jni/OutputPointer$ByteArray", - "org/sqlite/jni/OutputPointer$sqlite3", - "org/sqlite/jni/OutputPointer$sqlite3_stmt", +} S3NphRefs = { +#define NREF(INDEX, NAME) { INDEX, "org/sqlite/jni/" NAME } + NREF(0, "sqlite3"), + NREF(1, "sqlite3_stmt"), + NREF(2, "sqlite3_context"), + NREF(3, "sqlite3_value"), + NREF(4, "OutputPointer$Int32"), + NREF(5, "OutputPointer$Int64"), + NREF(6, "OutputPointer$String"), + NREF(7, "OutputPointer$ByteArray"), + NREF(8, "OutputPointer$sqlite3"), + NREF(9, "OutputPointer$sqlite3_stmt"), #ifdef SQLITE_ENABLE_FTS5 - "org/sqlite/jni/Fts5Context", - "org/sqlite/jni/Fts5ExtensionApi", - "org/sqlite/jni/fts5_api", - "org/sqlite/jni/fts5_tokenizer", - "org/sqlite/jni/Fts5Tokenizer" + NREF(10, "Fts5Context"), + NREF(11, "Fts5ExtensionApi"), + NREF(12, "fts5_api"), + NREF(13, "fts5_tokenizer"), + NREF(14, "Fts5Tokenizer") #endif +#undef NREF }; /** Create a trivial JNI wrapper for (int CName(void)). */ @@ -321,22 +329,20 @@ enum { (only) the library's NativePointerHolder types, a fixed count known at build-time. If we add more than this a fatal error will be triggered with a reminder to increase this. This value needs - to be exactly the number of entries in the S3JniClassNames - object. The S3JniClassNames entries are the keys for this particular + to be exactly the number of entries in the S3NphRefs object. The + index field of those entries are the keys for this particular cache. */ - NphCache_SIZE = sizeof(S3JniClassNames) / sizeof(char const *) + NphCache_SIZE = sizeof(S3NphRefs) / sizeof(S3NphRef) }; /** Cache entry for NativePointerHolder subclasses and OutputPointer types. */ -typedef struct S3JniNphCache S3JniNphCache; -struct S3JniNphCache { - const char * zClassName /* "full/class/Name". Must be a static - string pointer from the S3JniClassNames - struct. */; +typedef struct S3JniNphClass S3JniNphClass; +struct S3JniNphClass { + const S3NphRef * pRef /* Entry from S3NphRefs. */; jclass klazz /* global ref to the concrete NativePointerHolder subclass represented by zClassName */; @@ -388,12 +394,12 @@ struct S3JniEnv { /** Cache of Java refs/IDs for NativePointerHolder subclasses. */ - S3JniNphCache nph[NphCache_SIZE]; + S3JniNphClass nph[NphCache_SIZE]; }; -static void S3JniNphCache_clear(JNIEnv * const env, S3JniNphCache * const p){ +static void S3JniNphClass_clear(JNIEnv * const env, S3JniNphClass * const p){ UNREF_G(p->klazz); - memset(p, 0, sizeof(S3JniNphCache)); + memset(p, 0, sizeof(S3JniNphClass)); } /* @@ -507,8 +513,6 @@ static struct { current JNIEnv object. */; } perDb; struct { - unsigned nphCacheHits; - unsigned nphCacheMisses; unsigned envCacheHits; unsigned envCacheMisses; unsigned nMutexEnv /* number of times envCache.mutex was entered */; @@ -556,7 +560,7 @@ static struct { condition in which two open() calls could set pdbOpening. */ S3JniDb * pdbOpening; - sqlite3_mutex * mutex /* mutex for aUsed and aFree */; + sqlite3_mutex * mutex /* mutex for manipulation/traversal of pHead */; void const * locker /* Mutex is locked on this object's behalf */; } autoExt; } S3JniGlobal; @@ -565,29 +569,29 @@ static struct { assert( (env) == S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) #define MUTEX_ASSERT_NOTLOCKER_ENV \ assert( (env) != S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) -#define MUTEX_ENTER_ENV \ +#define MUTEX_ENV_ENTER \ /*MARKER(("Entering ENV mutex@%p %s.\n", env, __func__));*/ \ MUTEX_ASSERT_NOTLOCKER_ENV; \ sqlite3_mutex_enter( S3JniGlobal.envCache.mutex ); \ ++S3JniGlobal.metrics.nMutexEnv; \ S3JniGlobal.envCache.locker = env -#define MUTEX_LEAVE_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 ) #define MUTEX_ASSERT_LOCKED_PDB \ assert( 0 != S3JniGlobal.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) -#define MUTEX_ENTER_PDB \ +#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; -#define MUTEX_LEAVE_PDB \ +#define MUTEX_PDB_LEAVE \ /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ S3JniGlobal.perDb.locker = 0; \ sqlite3_mutex_leave( S3JniGlobal.perDb.mutex ) -#define MUTEX_ENTER_EXT \ +#define MUTEX_ENV_EXT \ /*MARKER(("Entering autoExt mutex@%p %s.\n", env, __func__));*/ \ sqlite3_mutex_enter( S3JniGlobal.autoExt.mutex ); \ ++S3JniGlobal.metrics.nMutexAutoExt @@ -596,7 +600,7 @@ static struct { if( sqlite3_mutex_try( S3JniGlobal.autoExt.mutex ) ){ FAIL_EXPR; } \ S3JniGlobal.autoExt.locker = env; \ ++S3JniGlobal.metrics.nMutexAutoExt -#define MUTEX_LEAVE_EXT \ +#define MUTEX_EXT_LEAVE \ /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ S3JniGlobal.autoExt.locker = 0; \ sqlite3_mutex_leave( S3JniGlobal.autoExt.mutex ) @@ -626,12 +630,12 @@ static void * s3jni_malloc(JNIEnv * const env, size_t n){ */ static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){ struct S3JniEnv * row; - MUTEX_ENTER_ENV; + MUTEX_ENV_ENTER; row = S3JniGlobal.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ ++S3JniGlobal.metrics.envCacheHits; - MUTEX_LEAVE_ENV; + MUTEX_ENV_LEAVE; return row; } } @@ -684,7 +688,7 @@ static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){ REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); } - MUTEX_LEAVE_ENV; + MUTEX_ENV_LEAVE; return row; } @@ -975,18 +979,18 @@ static void S3JniDb_set_aside(S3JniDb * const s){ static void S3JniDb_free_for_env(JNIEnv *env){ S3JniDb * ps; S3JniDb * pNext = 0; - MUTEX_ENTER_PDB; + MUTEX_PDB_ENTER; ps = S3JniGlobal.perDb.aUsed; for( ; ps; ps = pNext ){ pNext = ps->pNext; if(ps->env == env){ S3JniDb * const pPrev = ps->pPrev; S3JniDb_set_aside(ps); - assert(pPrev ? pPrev->pNext!=ps : 1); - pNext = pPrev; + assert(pPrev ? pPrev->pNext==pNext : 1); + assert( ps == S3JniGlobal.perDb.aFree ); } } - MUTEX_LEAVE_PDB; + MUTEX_PDB_LEAVE; } /** @@ -1026,7 +1030,7 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ UNREF_G(row->jPhraseIter.klazz); #endif for( i = 0; i < NphCache_SIZE; ++i ){ - S3JniNphCache_clear(env, &row->nph[i]); + S3JniNphClass_clear(env, &row->nph[i]); } memset(row, 0, sizeof(S3JniEnv)); row->pNext = S3JniGlobal.envCache.aFree; @@ -1037,10 +1041,8 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ /** Searches the NativePointerHolder cache for the given combination. - If it finds one, it returns it as-is. If it doesn't AND the cache - has a free slot, it populates that slot with (env, zClassName, - klazz) and returns it. If the cache is full with no match it - returns NULL. + If it finds one, it returns it as-is. If it doesn't, it populates a + cache slot's klazz member and returns the cache slot. It is up to the caller to populate the other members of the returned object if needed. @@ -1051,7 +1053,7 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ This simple cache catches >99% of searches in the current (2023-07-31) tests. */ -static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zClassName){ +static S3JniNphClass * S3JniGlobal_nph_cache(JNIEnv * const env, S3NphRef const* pRef){ /** According to: @@ -1072,43 +1074,16 @@ static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zCl guards the fetching of envRow. */ struct S3JniEnv * const envRow = S3JniGlobal_env_cache(env); - S3JniNphCache * freeSlot = 0; - S3JniNphCache * pCache = 0; - int i; + S3JniNphClass * pCache; assert(envRow); - for( i = 0; i < NphCache_SIZE; ++i ){ - pCache = &envRow->nph[i]; - if(zClassName == pCache->zClassName){ - ++S3JniGlobal.metrics.nphCacheHits; -#define DUMP_NPH_CACHES 0 -#if DUMP_NPH_CACHES - MARKER(("Cache hit #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - S3JniGlobal.metrics.nphCacheHits, zClassName, pCache->klazz, pCache->fidValue, - pCache->midCtor)); -#endif - assert(pCache->klazz); - return pCache; - }else if(!freeSlot && !pCache->zClassName){ - freeSlot = pCache; - } - } - if(freeSlot){ - freeSlot->zClassName = zClassName; - freeSlot->klazz = (*env)->FindClass(env, zClassName); + pCache = &envRow->nph[pRef->index]; + if( !pCache->pRef ){ + pCache->pRef = pRef; + pCache->klazz = (*env)->FindClass(env, pRef->zName); EXCEPTION_IS_FATAL("FindClass() unexpectedly threw"); - freeSlot->klazz = REF_G(freeSlot->klazz); - ++S3JniGlobal.metrics.nphCacheMisses; -#if DUMP_NPH_CACHES - static unsigned int cacheMisses = 0; - MARKER(("Cache miss #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - S3JniGlobal.metrics.nphCacheMisses, zClassName, freeSlot->klazz, - freeSlot->fidValue, freeSlot->midCtor)); -#endif -#undef DUMP_NPH_CACHES - }else{ - (*env)->FatalError(env, "MAINTENANCE REQUIRED: NphCache_SIZE is too low."); + pCache->klazz = REF_G(pCache->klazz); } - return freeSlot; + return pCache; } /** @@ -1127,11 +1102,10 @@ static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ as a cache key. */ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, - const char *zClassName){ + S3NphRef const* pRef){ jfieldID setter = 0; - S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); + S3JniNphClass * const pCache = S3JniGlobal_nph_cache(env, pRef); if(pCache && pCache->klazz && pCache->fidValue){ - assert(zClassName == pCache->zClassName); setter = pCache->fidValue; assert(setter); }else{ @@ -1141,7 +1115,6 @@ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, if(pCache){ assert(pCache->klazz); assert(!pCache->fidValue); - assert(zClassName == pCache->zClassName); pCache->fidValue = setter; } } @@ -1154,11 +1127,11 @@ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, zClassName must be a static string so we can use its address as a cache key. */ -static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zClassName){ +static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, S3NphRef const* pRef){ if( pObj ){ jfieldID getter = 0; void * rv = 0; - S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); + S3JniNphClass * const pCache = S3JniGlobal_nph_cache(env, pRef); if(pCache && pCache->fidValue){ getter = pCache->fidValue; }else{ @@ -1167,7 +1140,6 @@ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zC getter = NativePointerHolder_getField(env, klazz); if(pCache){ assert(pCache->klazz); - assert(zClassName == pCache->zClassName); pCache->fidValue = getter; } } @@ -1188,7 +1160,7 @@ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zC static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, jobject jDb){ S3JniDb * rv; - MUTEX_ASSERT_LOCKED_PDB; + MUTEX_PDB_ENTER; if(S3JniGlobal.perDb.aFree){ rv = S3JniGlobal.perDb.aFree; //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); @@ -1221,6 +1193,7 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, rv->pDb = pDb; rv->env = env; } + MUTEX_PDB_LEAVE; return rv; } @@ -1251,7 +1224,7 @@ static void S3JniDb_dump(S3JniDb *s){ static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){ S3JniDb * s = 0; if(jDb || pDb){ - MUTEX_ENTER_PDB; + MUTEX_PDB_ENTER; s = S3JniGlobal.perDb.aUsed; if(!pDb){ assert( jDb ); @@ -1262,7 +1235,7 @@ static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){ break; } } - MUTEX_LEAVE_PDB; + MUTEX_PDB_LEAVE; } return s; } @@ -1341,8 +1314,8 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, jfieldID member; void * pAgg; int rc = 0; - S3JniNphCache * const pCache = - S3JniGlobal_nph_cache(env, S3JniClassNames.sqlite3_context); + S3JniNphClass * const pCache = + S3JniGlobal_nph_cache(env, &S3NphRefs.sqlite3_context); if(pCache && pCache->klazz && pCache->fidSetAgg){ member = pCache->fidSetAgg; assert(member); @@ -1375,7 +1348,7 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, /** Common init for OutputPointer_set_Int32() and friends. zClassName must be a - pointer from S3JniClassNames. jOut must be an instance of that + pointer from S3NphRefs. jOut must be an instance of that class. Fetches the jfieldID for jOut's [value] property, which must be of the type represented by the JNI type signature zTypeSig, and stores it in pFieldId. Fails fatally if the property is not found, @@ -1385,12 +1358,11 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, this routine with the same zClassName but different zTypeSig: it will misbehave. */ -static void setupOutputPointer(JNIEnv * const env, const char *zClassName, +static void setupOutputPointer(JNIEnv * const env, S3NphRef const * pRef, const char * const zTypeSig, jobject const jOut, jfieldID * const pFieldId){ jfieldID setter = 0; - S3JniNphCache * const pCache = - S3JniGlobal_nph_cache(env, zClassName); + S3JniNphClass * const pCache = S3JniGlobal_nph_cache(env, pRef); if(pCache && pCache->klazz && pCache->fidValue){ setter = pCache->fidValue; }else{ @@ -1410,7 +1382,7 @@ static void setupOutputPointer(JNIEnv * const env, const char *zClassName, to v. */ static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){ jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_Int32, "I", jOut, &setter); + setupOutputPointer(env, &S3NphRefs.OutputPointer_Int32, "I", jOut, &setter); (*env)->SetIntField(env, jOut, setter, (jint)v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } @@ -1419,7 +1391,7 @@ static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int to v. */ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_Int64, "J", jOut, &setter); + setupOutputPointer(env, &S3NphRefs.OutputPointer_Int64, "J", jOut, &setter); (*env)->SetLongField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); } @@ -1427,7 +1399,7 @@ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlon static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, jobject jDb){ jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3, + setupOutputPointer(env, &S3NphRefs.OutputPointer_sqlite3, "Lorg/sqlite/jni/sqlite3;", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, jDb); EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value"); @@ -1436,7 +1408,7 @@ static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, jobject jStmt){ jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3_stmt, + setupOutputPointer(env, &S3NphRefs.OutputPointer_sqlite3_stmt, "Lorg/sqlite/jni/sqlite3_stmt;", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, jStmt); EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value"); @@ -1449,7 +1421,7 @@ static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOu static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, jbyteArray const v){ jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_ByteArray, "[B", + setupOutputPointer(env, &S3NphRefs.OutputPointer_ByteArray, "[B", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); @@ -1461,7 +1433,7 @@ static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, jstring const v){ jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_String, + setupOutputPointer(env, &S3NphRefs.OutputPointer_String, "Ljava/lang/String;", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); @@ -1587,28 +1559,26 @@ static void ResultJavaVal_finalizer(void *v){ if Java fails to allocate, but the JNI docs are not entirely clear on that detail. - Always use a static string pointer from S3JniClassNames for the 2nd + Always use an static pointer from the S3NphRefs struct for the 2nd argument so that we can use its address as a cache key. */ -static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zClassName, +static jobject new_NativePointerHolder_object(JNIEnv * const env, S3NphRef const * pRef, const void * pNative){ jobject rv = 0; jclass klazz = 0; jmethodID ctor = 0; - S3JniNphCache * const pCache = - S3JniGlobal_nph_cache(env, zClassName); - if(pCache && pCache->midCtor){ + S3JniNphClass * const pCache = S3JniGlobal_nph_cache(env, pRef); + if(pCache->midCtor){ assert( pCache->klazz ); klazz = pCache->klazz; ctor = pCache->midCtor; }else{ klazz = pCache ? pCache->klazz - : (*env)->FindClass(env, zClassName); + : (*env)->FindClass(env, pRef->zName); ctor = klazz ? (*env)->GetMethodID(env, klazz, "", "()V") : 0; EXCEPTION_IS_FATAL("Cannot find constructor for class."); if(pCache){ - assert(zClassName == pCache->zClassName); assert(pCache->klazz); assert(!pCache->midCtor); pCache->midCtor = ctor; @@ -1618,21 +1588,21 @@ static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zC assert(ctor); rv = (*env)->NewObject(env, klazz, ctor); EXCEPTION_IS_FATAL("No-arg constructor threw."); - if(rv) NativePointerHolder_set(env, rv, pNative, zClassName); + if(rv) NativePointerHolder_set(env, rv, pNative, pRef); return rv; } static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3, sv); + return new_NativePointerHolder_object(env, &S3NphRefs.sqlite3, sv); } static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_context, sv); + return new_NativePointerHolder_object(env, &S3NphRefs.sqlite3_context, sv); } static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_stmt, sv); + return new_NativePointerHolder_object(env, &S3NphRefs.sqlite3_stmt, sv); } static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_value, sv); + return new_NativePointerHolder_object(env, &S3NphRefs.sqlite3_value, sv); } enum UDFType { @@ -1938,7 +1908,7 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, assert( !ps->pDb /* it's still being opened */ ); ps->pDb = pDb; assert( ps->jDb ); - NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3); + NativePointerHolder_set(env, ps->jDb, pDb, &S3NphRefs.sqlite3); for( ; pAX; pAX = pAX->pNext ){ rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); IFTHREW { @@ -1964,7 +1934,7 @@ JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ S3JniAutoExtension * ax; if( !jAutoExt ) return SQLITE_MISUSE; - MUTEX_ENTER_EXT; + MUTEX_ENV_EXT; if( 0==once && ++once ){ sqlite3_auto_extension( (void(*)(void))s3jni_run_java_auto_extensions ); } @@ -1975,7 +1945,7 @@ JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ } } ax = S3JniAutoExtension_alloc(env, jAutoExt); - MUTEX_LEAVE_EXT; + MUTEX_EXT_LEAVE; return ax ? 0 : SQLITE_NOMEM; } @@ -2117,7 +2087,7 @@ FIXME_THREADING(autoExt) JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ S3JniAutoExtension * ax; jboolean rc = JNI_FALSE; - MUTEX_ENTER_EXT; + MUTEX_ENV_EXT; for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ S3JniAutoExtension_free(env, ax); @@ -2125,7 +2095,7 @@ JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ break; } } - MUTEX_LEAVE_EXT; + MUTEX_EXT_LEAVE; return rc; } @@ -2140,11 +2110,11 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ ps = S3JniDb_for_db(env, jDb, 0); if(ps){ rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); - MUTEX_ENTER_PDB; + MUTEX_PDB_ENTER; S3JniDb_set_aside(ps) /* MUST come after close() because of ps->trace. */; - MUTEX_LEAVE_PDB; - NativePointerHolder_set(env, jDb, 0, S3JniClassNames.sqlite3); + MUTEX_PDB_LEAVE; + NativePointerHolder_set(env, jDb, 0, &S3NphRefs.sqlite3); } return (jint)rc; } @@ -2647,7 +2617,7 @@ JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ rc = sqlite3_finalize(pStmt); - NativePointerHolder_set(env, jpStmt, 0, S3JniClassNames.sqlite3_stmt); + NativePointerHolder_set(env, jpStmt, 0, &S3NphRefs.sqlite3_stmt); } return rc; } @@ -2683,7 +2653,7 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, is infinitely easier to track down from client code. Note that we rely on the Java methods for open() and auto-extension handling to be synchronized so that this BUSY cannot be - triggered by a race condition with those functions. */; + triggered by a race condition with the auto-ext functions. */; *jc = S3JniGlobal_env_cache(env); if(!*jc){ rc = SQLITE_NOMEM; @@ -2701,9 +2671,7 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, rc = SQLITE_NOMEM; goto end; } - MUTEX_ENTER_PDB; *ps = S3JniDb_alloc(env, 0, *jDb); - MUTEX_LEAVE_PDB; if(*ps){ S3JniGlobal.autoExt.pdbOpening = *ps; S3JniGlobal.autoExt.locker = *ps; @@ -2712,9 +2680,9 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, } //MARKER(("pre-open ps@%p\n", *ps)); end: - /* Remain in autoExt.mutex until s3jni_open_post(). */ if(rc){ - MUTEX_LEAVE_EXT; + MUTEX_EXT_LEAVE; + /* Else remain in autoExt.mutex until s3jni_open_post(). */ } return rc; } @@ -2739,18 +2707,18 @@ static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, assert(ps->jDb); if( 0==ps->pDb ){ ps->pDb = *ppDb; - NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); + NativePointerHolder_set(env, ps->jDb, *ppDb, &S3NphRefs.sqlite3); }else{ assert( ps->pDb == *ppDb /* set up via s3jni_run_java_auto_extensions() */); } }else{ - MUTEX_ENTER_PDB; + MUTEX_PDB_ENTER; S3JniDb_set_aside(ps); - MUTEX_LEAVE_PDB; + MUTEX_PDB_LEAVE; ps = 0; } OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); - MUTEX_LEAVE_EXT; + MUTEX_EXT_LEAVE /* locked in s3jni_open_pre() */; return theRc; } @@ -2848,7 +2816,7 @@ end: OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); } if( pStmt ){ - NativePointerHolder_set(env, jStmt, pStmt, S3JniClassNames.sqlite3_stmt); + NativePointerHolder_set(env, jStmt, pStmt, &S3NphRefs.sqlite3_stmt); }else{ /* Happens for comments and whitespace */ UNREF_L(jStmt); @@ -2937,11 +2905,11 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ } JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ - MUTEX_ENTER_EXT; + MUTEX_ENV_EXT; while( S3JniGlobal.autoExt.pHead ){ S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead); } - MUTEX_LEAVE_EXT; + MUTEX_EXT_LEAVE; } /* sqlite3_result_text/blob() and friends. */ @@ -3244,11 +3212,11 @@ JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){ } JDECL(jint,1shutdown)(JENV_CSELF){ - MUTEX_ENTER_ENV; + MUTEX_ENV_ENTER; while( S3JniGlobal.envCache.aHead ){ S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env ); } - MUTEX_LEAVE_ENV; + MUTEX_ENV_LEAVE; /* Do not clear S3JniGlobal.jvm: it's legal to call sqlite3_initialize() again to restart the lib. */ return sqlite3_shutdown(); @@ -3539,16 +3507,13 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ SO(S3JniEnv); SO(S3JniHook); SO(S3JniDb); - SO(S3JniClassNames); + SO(S3NphRefs); printf("\t(^^^ %u NativePointerHolder subclasses)\n", - (unsigned)(sizeof(S3JniClassNames) / sizeof(const char *))); + (unsigned)NphCache_SIZE); SO(S3JniGlobal); SO(S3JniAutoExtension); SO(S3JniUdf); printf("Cache info:\n"); - printf("\tNativePointerHolder cache: %u misses, %u hits\n", - S3JniGlobal.metrics.nphCacheMisses, - S3JniGlobal.metrics.nphCacheHits); printf("\tJNIEnv cache %u misses, %u hits\n", S3JniGlobal.metrics.envCacheMisses, S3JniGlobal.metrics.envCacheHits); @@ -3588,10 +3553,10 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ JNIEXPORT ReturnType JNICALL \ JFuncNameFtsTok(Suffix) -#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_api) -#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_tokenizer) -#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Context) -#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Tokenizer) +#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.fts5_api) +#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.fts5_tokenizer) +#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.Fts5Context) +#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.Fts5Tokenizer) #define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() /** @@ -3655,10 +3620,10 @@ static inline Fts5ExtensionApi const * s3jni_ftsext(void){ } static inline jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.Fts5Context, sv); + return new_NativePointerHolder_object(env, &S3NphRefs.Fts5Context, sv); } static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.fts5_api, sv); + return new_NativePointerHolder_object(env, &S3NphRefs.fts5_api, sv); } /** @@ -3668,7 +3633,7 @@ static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ S3JniEnv * const row = S3JniGlobal_env_cache(env); if( !row->jFtsExt ){ - row->jFtsExt = new_NativePointerHolder_object(env, S3JniClassNames.Fts5ExtensionApi, + row->jFtsExt = new_NativePointerHolder_object(env, &S3NphRefs.Fts5ExtensionApi, s3jni_ftsext()); if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt); } @@ -4113,7 +4078,7 @@ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, /** Proxy for Fts5ExtensionApi.xTokenize() and fts5_tokenizer.xTokenize() */ -static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, +static jint s3jni_fts5_xTokenize(JENV_OSELF, S3NphRef const *pRef, jint tokFlags, jobject jFcx, jbyteArray jbaText, jobject jCallback){ Fts5ExtDecl; @@ -4140,11 +4105,11 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, s.tok.jba = REF_L(jbaText); s.tok.zPrev = (const char *)pText; s.tok.nPrev = (int)nText; - if( zClassName == S3JniClassNames.Fts5ExtensionApi ){ + if( pRef == &S3NphRefs.Fts5ExtensionApi ){ rc = fext->xTokenize(PtrGet_Fts5Context(jFcx), (const char *)pText, (int)nText, &s, s3jni_xTokenize_xToken); - }else if( zClassName == S3JniClassNames.fts5_tokenizer ){ + }else if( pRef == &S3NphRefs.fts5_tokenizer ){ fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf); rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags, (const char *)pText, (int)nText, @@ -4162,13 +4127,13 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, JDECLFtsXA(jint,xTokenize)(JENV_OSELF,jobject jFcx, jbyteArray jbaText, jobject jCallback){ - return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5ExtensionApi, + return s3jni_fts5_xTokenize(env, jSelf, &S3NphRefs.Fts5ExtensionApi, 0, jFcx, jbaText, jCallback); } JDECLFtsTok(jint,xTokenize)(JENV_OSELF,jobject jFcx, jint tokFlags, jbyteArray jbaText, jobject jCallback){ - return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5Tokenizer, + return s3jni_fts5_xTokenize(env, jSelf, &S3NphRefs.Fts5Tokenizer, tokFlags, jFcx, jbaText, jCallback); } @@ -4396,9 +4361,9 @@ Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ int rc; - MUTEX_ENTER_ENV; + MUTEX_ENV_ENTER; rc = S3JniGlobal_env_uncache(env); - MUTEX_LEAVE_ENV; + MUTEX_ENV_LEAVE; return rc ? JNI_TRUE : JNI_FALSE; } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index dbd2e4bae7..073a79835f 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -178,8 +178,9 @@ public final class SQLite3Jni { not have access to the sqlite3_api object which native auto-extensions do. - - If an auto-extension opens a db, opening will fail with SQLITE_BUSY. - The alternative would be endless recursion into the auto-extension. + - If an auto-extension opens a db from the same thread, opening + will fail with SQLITE_BUSY. The alternative would be endless + recursion into the auto-extension. - The list of auto-extensions must not be manipulated from within an auto-extension. Auto extensions can neither be added, diff --git a/manifest b/manifest index 8673db6eeb..8cb273b6f5 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C JNI-internal\sdocs\sand\sremoval\sof\sobsolete\scode. -D 2023-08-14T08:28:46.677 +C More\swork\son\sthe\sJNI-specific\smutexes.\sRework\sthe\sNativePointerHolder\scache\slookup\sto\sbe\sslightly\ssimpler\sand\sO(1)\sinstead\sof\sO(N). +D 2023-08-14T13:27:40.885 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 1bc1cbaf075065793f83cf5bfba631216ced48d7beae0768ebd838e923e7e590 +F ext/jni/src/c/sqlite3-jni.c 0ca77c27d05b677191f105bc6f8570c916b78991a809d44566c60cfc16b50612 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -254,7 +254,7 @@ 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 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5eeba0b1a00fb34bc93fe60186f6032fcf4d568fc5868d70029883d3d07cc306 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java cd0627b5317435f9a6c72247915f9e32d6e8c225fd6f0db2c66b4a7f0b4e5601 F ext/jni/src/org/sqlite/jni/Tester1.java 368e836d943d9e882d2a217d0f582ed4141d164f174bebc50715acd57549a09b F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2091,8 +2091,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 911e4fc5aaf9478214095a65f74af3ebca883922c36cf7a8d911116c42cf9de8 -R c6d49900ed87c14449d9c08a36536c71 +P b62d93258b6a661f3a9b61468b3b641c14faf2d2196f78aca95fe14de43c9444 +R 640b629cd539e67d64418aa6a08729eb U stephan -Z 09908deaf1ed6a1fae2d9bef97861a7c +Z db03beab64a32e82d4161306677f6282 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7a0d99b2d3..0f159ac8d9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b62d93258b6a661f3a9b61468b3b641c14faf2d2196f78aca95fe14de43c9444 \ No newline at end of file +c84ded0e59aea4861d72b53b4b40cf580747c0f6ca58c334a996f1a825276cb5 \ No newline at end of file From 9019e2e667b297a4c9548ffeb8651d914f70d509 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 14 Aug 2023 17:12:55 +0000 Subject: [PATCH 08/37] Bring handling of the Java auto-ext handler more in line with the core in terms of locking and mutability during traversal. This removes the explicit synchronous requirement from the Java open() and auto-ext bindings. FossilOrigin-Name: 42994b952e092ae4fa319395208622e887387ca3ff8ac57961c824a6c272bf0e --- ext/jni/src/c/sqlite3-jni.c | 459 +++++++++++---------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 37 +- manifest | 14 +- manifest.uuid | 2 +- 4 files changed, 254 insertions(+), 258 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index d33329575d..37aa06460e 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -354,83 +354,11 @@ struct S3JniNphClass { by the sqlite3_context binding. */; }; -/** - 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. -*/ -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 */; -#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. - */ - S3JniNphClass nph[NphCache_SIZE]; -}; - static void S3JniNphClass_clear(JNIEnv * const env, S3JniNphClass * const p){ UNREF_G(p->klazz); memset(p, 0, sizeof(S3JniNphClass)); } -/* - Whether auto extensions are feasible here is currently unknown due - to... - - 1) 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? - - 2) a chicken/egg problem involving the Java/C mapping of the db: - when auto extensions are run, the db has not yet been connected to - Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave - properly because they have a different jobject and the API - guarantees the user that _that_ object is the one the API will bind - the native to. - - If we change the open(_v2()) interfaces to use OutputPointer.sqlite3 - instead of the client passing in an instance, we could work around - (2). -*/ -typedef struct S3JniAutoExtension S3JniAutoExtension; -typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*); -struct S3JniAutoExtension { - jobject jObj; - jmethodID midFunc; - S3JniAutoExtension_xEntryPoint xEntryPoint; - S3JniAutoExtension *pNext /* next linked-list entry */; - S3JniAutoExtension *pPrev /* previous linked-list entry */; -}; - /** State for various hook callbacks. */ typedef struct S3JniHook S3JniHook; struct S3JniHook{ @@ -480,6 +408,95 @@ struct S3JniDb { S3JniDb * pPrev /* Previous entry in the available/free list */; }; +/** + 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. +*/ +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 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 open(), which triggers the auto-extension handler. + That handler uses pdbOpening to connect the native db handle + which it receives with pdbOpening. + + - When open() returns, check whether it invoked the auto-ext + handler. If not, complete the Java/C binding unless 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. + */ + S3JniNphClass nph[NphCache_SIZE]; +}; + +/* + Whether auto extensions are feasible here is currently unknown due + to... + + 1) 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? + + 2) a chicken/egg problem involving the Java/C mapping of the db: + when auto extensions are run, the db has not yet been connected to + Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave + properly because they have a different jobject and the API + guarantees the user that _that_ object is the one the API will bind + the native to. + + If we change the open(_v2()) interfaces to use OutputPointer.sqlite3 + instead of the client passing in an instance, we could work around + (2). +*/ +typedef struct S3JniAutoExtension S3JniAutoExtension; +struct S3JniAutoExtension { + jobject jObj /* Java object */; + jmethodID midFunc /* xEntryPoint() callback */; +}; + /** Global state, e.g. caches and metrics. */ @@ -530,38 +547,15 @@ static struct { } metrics; /** The list of bound auto-extensions (Java-side: - org.sqlite.jni.AutoExtension objects). Because this data - structure cannot be manipulated during traversal (without adding - more code to deal with it), and to avoid a small window where a - call to sqlite3_reset/clear_auto_extension() could lock the - structure during an open() call, we lock this mutex before - sqlite3_open() is called and unlock it once sqlite3_open() - returns. + org.sqlite.jni.AutoExtension objects). */ struct { - S3JniAutoExtension *pHead /* Head of the auto-extension list */; - /** - pdbOpening is used to coordinate the Java/DB connection of a - being-open()'d db. "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: - - - At the start of open(), we lock on this->mutex. - - Allocate the Java side of that connection and set pdbOpening - to point to that object. - - Call open(), which triggers the auto-extension handler. - That handler uses pdbOpening to connect the native db handle - which it recieves with pdbOpening. - - Return from open(). - - Clean up and unlock the mutex. - - If open() did not block on a mutex, there would be a race - condition in which two open() calls could set pdbOpening. - */ - S3JniDb * pdbOpening; - sqlite3_mutex * mutex /* mutex for manipulation/traversal of pHead */; - void const * locker /* Mutex is locked on this object's behalf */; + S3JniAutoExtension *pExt /* Head of the auto-extension list */; + int nAlloc /* number of entries allocated for pExt, + as distinct from the number of active + entries. */; + int nExt /* number of active entries in pExt. */; + sqlite3_mutex * mutex /* mutex for manipulation/traversal of pExt */; } autoExt; } S3JniGlobal; @@ -591,18 +585,12 @@ static struct { /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ S3JniGlobal.perDb.locker = 0; \ sqlite3_mutex_leave( S3JniGlobal.perDb.mutex ) -#define MUTEX_ENV_EXT \ +#define MUTEX_EXT_ENTER \ /*MARKER(("Entering autoExt mutex@%p %s.\n", env, __func__));*/ \ sqlite3_mutex_enter( S3JniGlobal.autoExt.mutex ); \ ++S3JniGlobal.metrics.nMutexAutoExt -#define MUTEX_TRY_EXT(FAIL_EXPR) \ - /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ - if( sqlite3_mutex_try( S3JniGlobal.autoExt.mutex ) ){ FAIL_EXPR; } \ - S3JniGlobal.autoExt.locker = env; \ - ++S3JniGlobal.metrics.nMutexAutoExt #define MUTEX_EXT_LEAVE \ - /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ - S3JniGlobal.autoExt.locker = 0; \ + /*MARKER(("Leaving autoExt mutex@%p %s.\n", env, __func__));*/ \ sqlite3_mutex_leave( S3JniGlobal.autoExt.mutex ) #define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) @@ -1241,53 +1229,40 @@ static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){ } /** - Unlink ax from S3JniGlobal.autoExt and free it. + Unref any Java-side state in ax. */ -static void S3JniAutoExtension_free(JNIEnv * const env, - S3JniAutoExtension * const ax){ - if( ax ){ - if( ax->pNext ) ax->pNext->pPrev = ax->pPrev; - if( ax == S3JniGlobal.autoExt.pHead ){ - assert( !ax->pNext ); - S3JniGlobal.autoExt.pHead = ax->pNext; - }else if( ax->pPrev ){ - ax->pPrev->pNext = ax->pNext; - } - ax->pNext = ax->pPrev = 0; +static void S3JniAutoExtension_clear(JNIEnv * const env, + S3JniAutoExtension * const ax){ + if( ax->jObj ){ UNREF_G(ax->jObj); - sqlite3_free(ax); + memset(ax, 0, sizeof(*ax)); } } /** - Allocates a new auto extension and plugs it in to S3JniGlobal.autoExt. - Returns 0 on OOM or if there is an error collecting the required - state from jAutoExt (which must be an AutoExtension object). + Initializes a pre-allocated S3JniAutoExtension object. Returns + non-0 if there is an error collecting the required state from + jAutoExt (which must be an AutoExtension object). */ -static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, - jobject const jAutoExt){ - S3JniAutoExtension * const ax = sqlite3_malloc(sizeof(*ax)); - if( ax ){ - jclass klazz; - memset(ax, 0, sizeof(*ax)); - klazz = (*env)->GetObjectClass(env, jAutoExt); - if(!klazz){ - S3JniAutoExtension_free(env, ax); - return 0; - } - ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", - "(Lorg/sqlite/jni/sqlite3;)I"); - if(!ax->midFunc){ - MARKER(("Error getting xEntryPoint(sqlite3) from object.")); - S3JniAutoExtension_free(env, ax); - return 0; - } - ax->jObj = REF_G(jAutoExt); - ax->pNext = S3JniGlobal.autoExt.pHead; - if( ax->pNext ) ax->pNext->pPrev = ax; - S3JniGlobal.autoExt.pHead = ax; +static int S3JniAutoExtension_init(JNIEnv *const env, + S3JniAutoExtension * const ax, + jobject const jAutoExt){ + jclass klazz; + klazz = (*env)->GetObjectClass(env, jAutoExt); + if(!klazz){ + S3JniAutoExtension_clear(env, ax); + return SQLITE_ERROR; } - return ax; + ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", + "(Lorg/sqlite/jni/sqlite3;)I"); + if(!ax->midFunc){ + MARKER(("Error getting xEntryPoint(sqlite3) from object.")); + S3JniAutoExtension_clear(env, ax); + return SQLITE_ERROR; + } + UNREF_L(klazz); + ax->jObj = REF_G(jAutoExt); + return 0; } /** @@ -1888,65 +1863,96 @@ static JNIEnv * s3jni_get_env(void){ /* Central auto-extension handler. */ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, const struct sqlite3_api_routines *ignored){ - S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead; - int rc; + int rc = 0; + unsigned i, go = 1; JNIEnv * env = 0; - S3JniDb * const ps = S3JniGlobal.autoExt.pdbOpening; - - assert( S3JniGlobal.autoExt.locker ); - assert( S3JniGlobal.autoExt.locker == ps ); - S3JniGlobal.autoExt.pdbOpening = 0; - if( !pAX ){ - return 0; - }else if( S3JniGlobal.autoExt.locker != ps ) { - *pzErr = sqlite3_mprintf("Internal error: unexpected path lead to " - "running an auto-extension."); - return SQLITE_ERROR; - } + S3JniDb * ps; + S3JniEnv * jc; + if( 0==S3JniGlobal.autoExt.nExt ) return 0; env = s3jni_get_env(); + jc = S3JniGlobal_env_cache(env); + ps = jc->pdbOpening; + jc->pdbOpening = 0; + assert( ps && "Unexpected arrival of null S3JniDb in auto-extension runner." ); //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); - assert( !ps->pDb /* it's still being opened */ ); + assert( !ps->pDb && "it's still being opened" ); ps->pDb = pDb; assert( ps->jDb ); NativePointerHolder_set(env, ps->jDb, pDb, &S3NphRefs.sqlite3); - for( ; pAX; pAX = pAX->pNext ){ - rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); - IFTHREW { - jthrowable const ex = (*env)->ExceptionOccurred(env); - char * zMsg; - EXCEPTION_CLEAR; - zMsg = s3jni_exception_error_msg(env, ex); - UNREF_L(ex); - *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); - sqlite3_free(zMsg); - rc = rc ? rc : SQLITE_ERROR; - break; - }else if( rc ){ - break; + for( i = 0; go && 0==rc; ++i ){ + S3JniAutoExtension const * ax; + MUTEX_EXT_ENTER; + if( i >= S3JniGlobal.autoExt.nAlloc ){ + ax = 0; + go = 0; + }else{ + ax = &S3JniGlobal.autoExt.pExt[i]; + } + MUTEX_EXT_LEAVE; + if( ax && ax->jObj ){ + rc = (*env)->CallIntMethod(env, ax->jObj, ax->midFunc, ps->jDb); + IFTHREW { + jthrowable const ex = (*env)->ExceptionOccurred(env); + char * zMsg; + EXCEPTION_CLEAR; + zMsg = s3jni_exception_error_msg(env, ex); + UNREF_L(ex); + *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); + sqlite3_free(zMsg); + rc = rc ? rc : SQLITE_ERROR; + } } } return rc; } FIXME_THREADING(autoExt) -JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ +JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){ static int once = 0; + int i; S3JniAutoExtension * ax; + int rc = 0; + int firstEmptySlot = -1; if( !jAutoExt ) return SQLITE_MISUSE; - MUTEX_ENV_EXT; - if( 0==once && ++once ){ - sqlite3_auto_extension( (void(*)(void))s3jni_run_java_auto_extensions ); - } - ax = S3JniGlobal.autoExt.pHead; - for( ; ax; ax = ax->pNext ){ - if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - return 0 /* C API treats this as a no-op. */; + MUTEX_EXT_ENTER; + for( i = 0; i < S3JniGlobal.autoExt.nAlloc; ++i ){ + /* Look for match or first empty slot. */ + ax = &S3JniGlobal.autoExt.pExt[i]; + if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + break /* this as a no-op. */; + }else if( !ax->jObj && firstEmptySlot<0 ){ + firstEmptySlot = (int)i; + } + } + if(i == S3JniGlobal.autoExt.nAlloc ){ + if( firstEmptySlot >= 0 ){ + ax = &S3JniGlobal.autoExt.pExt[firstEmptySlot]; + rc = S3JniAutoExtension_init(env, ax, jAutoExt); + }else{ + unsigned n = 1 + S3JniGlobal.autoExt.nAlloc; + S3JniAutoExtension * const aNew = + sqlite3_realloc( S3JniGlobal.autoExt.pExt, + n * sizeof(S3JniAutoExtension) ); + if( !aNew ){ + rc = SQLITE_NOMEM; + }else{ + S3JniGlobal.autoExt.pExt = aNew; + ax = &S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc]; + ++S3JniGlobal.autoExt.nAlloc; + rc = S3JniAutoExtension_init(env, ax, jAutoExt); + assert( rc ? 0==ax->jObj : 0!=ax->jObj ); + } + } + } + if( 0==rc ){ + ++S3JniGlobal.autoExt.nExt; + if( 0==once && ++once ){ + sqlite3_auto_extension( (void(*)(void))s3jni_run_java_auto_extensions ); } } - ax = S3JniAutoExtension_alloc(env, jAutoExt); MUTEX_EXT_LEAVE; - return ax ? 0 : SQLITE_NOMEM; + return rc; } FIXME_THREADING(S3JniEnv) @@ -2087,14 +2093,37 @@ FIXME_THREADING(autoExt) JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ S3JniAutoExtension * ax; jboolean rc = JNI_FALSE; - MUTEX_ENV_EXT; - for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ - if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - S3JniAutoExtension_free(env, ax); + int i; + MUTEX_EXT_ENTER; +#if 1 + for( i = 0; i < S3JniGlobal.autoExt.nAlloc; ++i ){ + ax = &S3JniGlobal.autoExt.pExt[i]; + if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + S3JniAutoExtension_clear(env, ax); + /* Move final entry into this slot. */ + *ax = S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc - 1]; + memset(&S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc - 1], 0, + sizeof(S3JniAutoExtension)); + --S3JniGlobal.autoExt.nExt; rc = JNI_TRUE; break; } } +#else + /* Why does this impl lead to an invalid unref error? */ + for( i = S3JniGlobal.autoExt.nAlloc-1; i <= 0; --i ){ + ax = &S3JniGlobal.autoExt.pExt[i]; + if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + S3JniAutoExtension_clear(env, ax); + /* Move final entry into this slot. */ + *ax = S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc - 1]; + memset(&S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc - 1], 0, + sizeof(S3JniAutoExtension)); + rc = JNI_TRUE; + break; + } + } +#endif MUTEX_EXT_LEAVE; return rc; } @@ -2646,14 +2675,6 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, jstring jDbName, char **zDbName, S3JniDb ** ps, jobject *jDb){ int rc = 0; - MUTEX_TRY_EXT(return SQLITE_BUSY) - /* we don't wait forever here because it could lead to a deadlock - if an auto-extension opens a database. Without a mutex, that - situation leads to infinite recursion and stack overflow, which - is infinitely easier to track down from client code. Note that - we rely on the Java methods for open() and auto-extension - handling to be synchronized so that this BUSY cannot be - triggered by a race condition with the auto-ext functions. */; *jc = S3JniGlobal_env_cache(env); if(!*jc){ rc = SQLITE_NOMEM; @@ -2673,17 +2694,12 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, } *ps = S3JniDb_alloc(env, 0, *jDb); if(*ps){ - S3JniGlobal.autoExt.pdbOpening = *ps; - S3JniGlobal.autoExt.locker = *ps; + (*jc)->pdbOpening = *ps; }else{ rc = SQLITE_NOMEM; } //MARKER(("pre-open ps@%p\n", *ps)); end: - if(rc){ - MUTEX_EXT_LEAVE; - /* Else remain in autoExt.mutex until s3jni_open_post(). */ - } return rc; } @@ -2698,11 +2714,11 @@ end: Returns theRc. */ -static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, - sqlite3 **ppDb, jobject jOut, int theRc){ +static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc, + S3JniDb * ps, sqlite3 **ppDb, + jobject jOut, int theRc){ //MARKER(("post-open() ps@%p db@%p\n", ps, *ppDb)); - assert( S3JniGlobal.autoExt.locker == ps ); - S3JniGlobal.autoExt.pdbOpening = 0; + jc->pdbOpening = 0; if(*ppDb){ assert(ps->jDb); if( 0==ps->pDb ){ @@ -2718,7 +2734,6 @@ static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, ps = 0; } OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); - MUTEX_EXT_LEAVE /* locked in s3jni_open_pre() */; return theRc; } @@ -2728,17 +2743,15 @@ JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ jobject jDb = 0; S3JniDb * ps = 0; S3JniEnv * jc = 0; - S3JniDb * const prevOpening = S3JniGlobal.autoExt.pdbOpening; - int rc= s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); if( 0==rc ){ rc = sqlite3_open(zName, &pOut); //MARKER(("env=%p, *env=%p\n", env, *env)); //MARKER(("open() ps@%p db@%p\n", ps, pOut)); - rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc); assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); } - S3JniGlobal.autoExt.pdbOpening = prevOpening; return (jint)rc; } @@ -2750,7 +2763,6 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, S3JniDb * ps = 0; S3JniEnv * jc = 0; char *zVfs = 0; - S3JniDb * const prevOpening = S3JniGlobal.autoExt.pdbOpening; int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); if( 0==rc && strVfs ){ zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); @@ -2764,8 +2776,7 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, //MARKER(("open_v2() ps@%p db@%p\n", ps, pOut)); /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", zName, zVfs, pOut, (int)flags, nrc));*/ - rc = s3jni_open_post(env, ps, &pOut, jOut, rc); - S3JniGlobal.autoExt.pdbOpening = prevOpening; + rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc); assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); sqlite3_free(zVfs); @@ -2905,10 +2916,12 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ } JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ - MUTEX_ENV_EXT; - while( S3JniGlobal.autoExt.pHead ){ - S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead); + int i; + MUTEX_EXT_ENTER; + for( i = 0; i < S3JniGlobal.autoExt.nAlloc; ++i ){ + S3JniAutoExtension_clear( env, &S3JniGlobal.autoExt.pExt[i] ); } + S3JniGlobal.autoExt.nExt = 0; MUTEX_EXT_LEAVE; } @@ -3517,7 +3530,7 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ printf("\tJNIEnv cache %u misses, %u hits\n", S3JniGlobal.metrics.envCacheMisses, S3JniGlobal.metrics.envCacheHits); - printf("Mutex entry:\n\t%u env\n\t%u perDb\n\t%u autoExt (mostly via open[_v2]())\n", + 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); diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 073a79835f..0833c1d4a3 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -178,26 +178,17 @@ public final class SQLite3Jni { not have access to the sqlite3_api object which native auto-extensions do. - - If an auto-extension opens a db from the same thread, opening - will fail with SQLITE_BUSY. The alternative would be endless - recursion into the auto-extension. - - - The list of auto-extensions must not be manipulated from within - an auto-extension. Auto extensions can neither be added, - removed, nor cleared while one registered with this function is - running. Attempting to do so may lead to a deadlock. + - If the list of auto-extensions is manipulated from an + auto-extension, it is undefined which, if any, auto-extensions + will subsequently execute for the current database (it depends + 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. - - Design note: this family of methods is synchronized in order to - help avoid a small race condition where an in-progress - sqlite3_reset_auto_extension() or sqlite3_cancel_auto_extension() - could cause sqlite3_open() to fail with SQLITE_BUSY. */ - public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback); + public static native int sqlite3_auto_extension(@NotNull AutoExtension callback); public static int sqlite3_bind_blob( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data @@ -293,12 +284,7 @@ public final class SQLite3Jni { @NotNull sqlite3 db, int ms ); - /** - Works like the C API except that it returns false, without side - effects, if auto extensions are currently running. (The JNI-level - list of extensions cannot be manipulated while it is being traversed.) - */ - public static synchronized native boolean sqlite3_cancel_auto_extension( + public static native boolean sqlite3_cancel_auto_extension( @NotNull AutoExtension ax ); @@ -595,16 +581,13 @@ public final class SQLite3Jni { Recall that even if opening fails, the output pointer might be non-null. Any error message about the failure will be in that object and it is up to the caller to sqlite3_close() that - db handle. Passing a null to sqlite3_close() is legal. - - Design note: this method is synchronized in order to help - alleviate a race condition involving auto-extensions. + db handle. */ - public static synchronized native int sqlite3_open( + public static native int sqlite3_open( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb ); - public static synchronized native int sqlite3_open_v2( + public static native int sqlite3_open_v2( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, int flags, @Nullable String zVfs ); @@ -728,7 +711,7 @@ public final class SQLite3Jni { extensions are currently running. (The JNI-level list of extensions cannot be manipulated while it is being traversed.) */ - public static synchronized native void sqlite3_reset_auto_extension(); + public static native void sqlite3_reset_auto_extension(); public static native void sqlite3_result_double( @NotNull sqlite3_context cx, double v diff --git a/manifest b/manifest index 8cb273b6f5..611838b934 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\swork\son\sthe\sJNI-specific\smutexes.\sRework\sthe\sNativePointerHolder\scache\slookup\sto\sbe\sslightly\ssimpler\sand\sO(1)\sinstead\sof\sO(N). -D 2023-08-14T13:27:40.885 +C Bring\shandling\sof\sthe\sJava\sauto-ext\shandler\smore\sin\sline\swith\sthe\score\sin\sterms\sof\slocking\sand\smutability\sduring\straversal.\sThis\sremoves\sthe\sexplicit\ssynchronous\srequirement\sfrom\sthe\sJava\sopen()\sand\sauto-ext\sbindings. +D 2023-08-14T17:12:55.531 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 0ca77c27d05b677191f105bc6f8570c916b78991a809d44566c60cfc16b50612 +F ext/jni/src/c/sqlite3-jni.c 4b93d970b142e62712f2cbdde01e1c5ed78af5f306238efad0e53276f26f1211 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -254,7 +254,7 @@ 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 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java cd0627b5317435f9a6c72247915f9e32d6e8c225fd6f0db2c66b4a7f0b4e5601 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5897c1d11f6c780825c7ac739270365e6312990195fc135fc6b02d5536dbae18 F ext/jni/src/org/sqlite/jni/Tester1.java 368e836d943d9e882d2a217d0f582ed4141d164f174bebc50715acd57549a09b F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2091,8 +2091,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 b62d93258b6a661f3a9b61468b3b641c14faf2d2196f78aca95fe14de43c9444 -R 640b629cd539e67d64418aa6a08729eb +P c84ded0e59aea4861d72b53b4b40cf580747c0f6ca58c334a996f1a825276cb5 +R 49ea94318acd6a04e4782997e6036b52 U stephan -Z db03beab64a32e82d4161306677f6282 +Z f760a4c17dc36361fbad3526fa5aa4c4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0f159ac8d9..a0b706b5cf 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c84ded0e59aea4861d72b53b4b40cf580747c0f6ca58c334a996f1a825276cb5 \ No newline at end of file +42994b952e092ae4fa319395208622e887387ca3ff8ac57961c824a6c272bf0e \ No newline at end of file From 6b51e35a9b76f0896c4d2a986093d659c8ac8255 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 15 Aug 2023 09:26:47 +0000 Subject: [PATCH 09/37] Minor reshaping of Tester1 moving towards making a multi-threaded run mode. FossilOrigin-Name: f104c14c26c123ee78c09fc1bc59efb8668dc624da05c1d8dbeaf3c9dd02a393 --- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 2 +- ext/jni/src/org/sqlite/jni/Tester1.java | 49 +++++++++++++++------- manifest | 14 +++---- manifest.uuid | 2 +- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 0833c1d4a3..74676b48b6 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -141,7 +141,7 @@ public final class SQLite3Jni { undefined if any database objects are (A) still active at the time it is called _and_ (B) calls are subsequently made into the library with such a database. Doing so will, at best, lead to a - crash. Azt worst, it will lead to the db possibly misbehaving + crash. At worst, it will lead to the db possibly misbehaving because some of its Java-bound state has been cleared. There is no immediate harm in (A) so long as condition (B) is not met. This process does _not_ actually close any databases or finalize diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 055e070bfd..2629c394c9 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -21,6 +21,12 @@ public class Tester1 { int dbOpen; } + private String name; + + Tester1(String name){ + this.name = name; + } + static final Metrics metrics = new Metrics(); private static final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); @@ -45,11 +51,17 @@ public class Tester1 { } static int affirmCount = 0; - public static void affirm(Boolean v){ + public static void affirm(Boolean v, String comment){ ++affirmCount; assert( v /* prefer assert over exception if it's enabled because - the JNI layer sometimes has to suppress exceptions. */); - if( !v ) throw new RuntimeException("Assertion failed."); + the JNI layer sometimes has to suppress exceptions, + so they might be squelched on their way back to the + top. */); + if( !v ) throw new RuntimeException(comment); + } + + public static void affirm(Boolean v){ + affirm(v, "Affirmation failed."); } private static void test1(){ @@ -65,7 +77,7 @@ public class Tester1 { affirm(SQLITE_MAX_TRIGGER_DEPTH>0); } - public static sqlite3 createNewDb(){ + static sqlite3 createNewDb(){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open(":memory:", out); ++metrics.dbOpen; @@ -83,11 +95,11 @@ public class Tester1 { return db; } - public static void execSql(sqlite3 db, String[] sql){ + static void execSql(sqlite3 db, String[] sql){ execSql(db, String.join("", sql)); } - public static int execSql(sqlite3 db, boolean throwOnError, String sql){ + static int execSql(sqlite3 db, boolean throwOnError, String sql){ OutputPointer.Int32 oTail = new OutputPointer.Int32(); final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); int pos = 0, n = 1; @@ -127,11 +139,11 @@ public class Tester1 { return rc; } - public static void execSql(sqlite3 db, String sql){ + static void execSql(sqlite3 db, String sql){ execSql(db, true, sql); } - public static sqlite3_stmt prepare(sqlite3 db, String sql){ + static sqlite3_stmt prepare(sqlite3 db, String sql){ outStmt.clear(); int rc = sqlite3_prepare(db, sql, outStmt); affirm( 0 == rc ); @@ -519,7 +531,6 @@ public class Tester1 { rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false); affirm( 0 == rc ); affirm( cur32.value > 0 ); - outln(cur32.value," ",high32.value); affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ ); sqlite3_close_v2(db); @@ -1110,12 +1121,9 @@ public class Tester1 { outln("Woke up."); } - public static void main(String[] args) throws Exception { - final long timeStart = System.nanoTime(); - test1(); + private void runTests() throws Exception { if(false) testCompileOption(); - final java.util.List liArgs = - java.util.Arrays.asList(args); + test1(); testOpenDb1(); testOpenDb2(); testPrepare123(); @@ -1141,12 +1149,21 @@ public class Tester1 { testAuthorizer(); testFts5(); testAutoExtension(); + } + + public static void main(String[] args) throws Exception { + + final long timeStart = System.nanoTime(); + new Tester1("main thread").runTests(); + final long timeEnd = System.nanoTime(); + + final java.util.List liArgs = + java.util.Arrays.asList(args); //testSleep(); if(liArgs.indexOf("-v")>0){ sqlite3_do_something_for_developer(); //listBoundMethods(); } - final long timeEnd = System.nanoTime(); affirm( SQLite3Jni.uncacheJniEnv() ); affirm( !SQLite3Jni.uncacheJniEnv() ); outln("Tests done. Metrics:"); @@ -1172,7 +1189,7 @@ public class Tester1 { outln("\tSQLite3Jni sqlite3_*() methods: "+ nNatives+" native methods and "+ (nMethods - nNatives)+" Java impls"); - outln("\tTotal time = " + outln("\tTotal test time = " +((timeEnd - timeStart)/1000000.0)+"ms"); } } diff --git a/manifest b/manifest index 611838b934..c5fa07875d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bring\shandling\sof\sthe\sJava\sauto-ext\shandler\smore\sin\sline\swith\sthe\score\sin\sterms\sof\slocking\sand\smutability\sduring\straversal.\sThis\sremoves\sthe\sexplicit\ssynchronous\srequirement\sfrom\sthe\sJava\sopen()\sand\sauto-ext\sbindings. -D 2023-08-14T17:12:55.531 +C Minor\sreshaping\sof\sTester1\smoving\stowards\smaking\sa\smulti-threaded\srun\smode. +D 2023-08-15T09:26:47.524 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -254,8 +254,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 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5897c1d11f6c780825c7ac739270365e6312990195fc135fc6b02d5536dbae18 -F ext/jni/src/org/sqlite/jni/Tester1.java 368e836d943d9e882d2a217d0f582ed4141d164f174bebc50715acd57549a09b +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 99334f54f5f41feb4c14dc988b93219e37799e032f2bc07bda6323b1dfb99e75 +F ext/jni/src/org/sqlite/jni/Tester1.java 63f02d45ad073ac9d98eb7d681a024b38f6abf978dd1454be9346cbf347b1b57 F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2091,8 +2091,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 c84ded0e59aea4861d72b53b4b40cf580747c0f6ca58c334a996f1a825276cb5 -R 49ea94318acd6a04e4782997e6036b52 +P 42994b952e092ae4fa319395208622e887387ca3ff8ac57961c824a6c272bf0e +R efccc6a7a6eed17207843d66c0fb4c2d U stephan -Z f760a4c17dc36361fbad3526fa5aa4c4 +Z 6864924ac8d4acb7bd32dec052e883fa # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a0b706b5cf..0857f50b77 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -42994b952e092ae4fa319395208622e887387ca3ff8ac57961c824a6c272bf0e \ No newline at end of file +f104c14c26c123ee78c09fc1bc59efb8668dc624da05c1d8dbeaf3c9dd02a393 \ No newline at end of file From 0fa2545e7f3aa39689eb1ca57e1bc886c1812fd3 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 17 Aug 2023 10:49:06 +0000 Subject: [PATCH 10/37] Remove the FIXME markers related to threading. Code style cleanups. FossilOrigin-Name: 154ab26dc6ba2d1fd976e8fe6dc1b1a06c734f7e9a276a3edc5c2f30b0d6d36a --- ext/jni/src/c/sqlite3-jni.c | 268 ++++++++++++++++-------------------- manifest | 12 +- manifest.uuid | 2 +- 3 files changed, 126 insertions(+), 156 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 37aa06460e..59c591f074 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -13,15 +13,15 @@ ** org.sqlite.jni.SQLiteJni (from which sqlite3-jni.h is generated). */ -/** - If you found this comment by searching the code for - CallStaticObjectMethod then you're the victim of an OpenJDK bug: - - https://bugs.openjdk.org/browse/JDK-8130659 - - It's known to happen with OpenJDK v8 but not with v19. - - This code does not use JNI's CallStaticObjectMethod(). +/* +** If you found this comment by searching the code for +** CallStaticObjectMethod then you're the victim of an OpenJDK bug: +** +** https://bugs.openjdk.org/browse/JDK-8130659 +** +** It's known to happen with OpenJDK v8 but not with v19. +** +** This code does not use JNI's CallStaticObjectMethod(). */ /* @@ -133,9 +133,13 @@ #undef INC__STRINGIFY #undef SQLITE_C +/* +** End of the sqlite3 lib setup. What follows is JNI-specific. +*/ + #include "sqlite3-jni.h" -#include /* only for testing/debugging */ #include +#include /* only for testing/debugging */ /* Only for debugging */ #define MARKER(pfexp) \ @@ -151,24 +155,24 @@ #define JDECL(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ JFuncName(Suffix) -/** - Shortcuts for the first 2 parameters to all JNI bindings. - - The type of the jSelf arg differs, but no docs seem to mention - this: for static methods it's of type jclass and for non-static - it's jobject. jobject actually works for all funcs, in the sense - that it compiles and runs so long as we don't use jSelf (which is - only rarely needed in this code), but to be pedantically correct we - need the proper type in the signature. - - Not even the official docs mention this discrepancy: - - https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers +/* +** Shortcuts for the first 2 parameters to all JNI bindings. +** +** The type of the jSelf arg differs, but no docs seem to mention +** this: for static methods it's of type jclass and for non-static +** it's jobject. jobject actually works for all funcs, in the sense +** that it compiles and runs so long as we don't use jSelf (which is +** only rarely needed in this code), but to be pedantically correct we +** need the proper type in the signature. +** +** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers */ #define JENV_OSELF JNIEnv * const env, jobject jSelf #define JENV_CSELF JNIEnv * const env, jclass jKlazz -/* Helpers to account for -Xcheck:jni warnings about not having - checked for exceptions. */ +/* +** Helpers to account for -Xcheck:jni warnings about not having +** checked for exceptions. +*/ #define IFTHREW if((*env)->ExceptionCheck(env)) #define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) #define EXCEPTION_CLEAR (*env)->ExceptionClear(env) @@ -272,9 +276,11 @@ static const struct { return (jint)CName((int)arg); \ } -/** Create a trivial JNI wrapper for (const mutf8_string * - CName(void)). This is only valid for functions which are known to - return ASCII or text which is equivalent in UTF-8 and MUTF-8. */ +/* +** Create a trivial JNI wrapper for (const mutf8_string * +** CName(void)). This is only valid for functions which are known to +** return ASCII or text which is equivalent in UTF-8 and MUTF-8. + */ #define WRAP_MUTF8_VOID(JniNameSuffix,CName) \ JDECL(jstring,JniNameSuffix)(JENV_CSELF){ \ return (*env)->NewStringUTF( env, CName() ); \ @@ -318,40 +324,35 @@ static const struct { #define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) #define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) -/* Marker for code which needs(?) to be made thread-safe. REASON is a - terse reminder about why that function requires a mutex. -*/ -#define FIXME_THREADING(REASON) - enum { - /** - Size of the NativePointerHolder cache. Need enough space for - (only) the library's NativePointerHolder types, a fixed count - known at build-time. If we add more than this a fatal error will - be triggered with a reminder to increase this. This value needs - to be exactly the number of entries in the S3NphRefs object. The - index field of those entries are the keys for this particular - cache. + /* + ** Size of the NativePointerHolder cache. Need enough space for + ** (only) the library's NativePointerHolder types, a fixed count + ** known at build-time. If we add more than this a fatal error will + ** be triggered with a reminder to increase this. This value needs + ** to be exactly the number of entries in the S3NphRefs object. The + ** index field of those entries are the keys for this particular + ** cache. */ NphCache_SIZE = sizeof(S3NphRefs) / sizeof(S3NphRef) }; -/** - Cache entry for NativePointerHolder subclasses and OutputPointer - types. +/* +** Cache entry for NativePointerHolder subclasses and OutputPointer +** types. */ typedef struct S3JniNphClass S3JniNphClass; struct S3JniNphClass { const S3NphRef * pRef /* Entry from S3NphRefs. */; - jclass klazz /* global ref to the concrete - NativePointerHolder subclass represented by - zClassName */; - jmethodID midCtor /* klazz's no-arg constructor. Used by - new_NativePointerHolder_object(). */; - jfieldID fidValue /* NativePointerHolder.nativePointer and - OutputPointer.X.value */; - jfieldID fidSetAgg /* sqlite3_context::aggregateContext. Used only - by the sqlite3_context binding. */; + jclass klazz /* global ref to the concrete + NativePointerHolder subclass represented by + zClassName */; + jmethodID midCtor /* klazz's no-arg constructor. Used by + new_NativePointerHolder_object(). */; + jfieldID fidValue /* NativePointerHolder.nativePointer and + OutputPointer.X.value */; + jfieldID fidSetAgg /* sqlite3_context::aggregateContext. Used only + by the sqlite3_context binding. */; }; static void S3JniNphClass_clear(JNIEnv * const env, S3JniNphClass * const p){ @@ -373,10 +374,10 @@ struct S3JniHook{ lookup. */; }; -/** - Per-(sqlite3*) state for various JNI bindings. This state is - allocated as needed, cleaned up in sqlite3_close(_v2)(), and - recycled when possible. It is freed during sqlite3_shutdown(). +/* +** Per-(sqlite3*) state for various JNI bindings. This state is +** allocated as needed, cleaned up in sqlite3_close(_v2)(), and +** recycled when possible. It is freed during sqlite3_shutdown(). */ typedef struct S3JniDb S3JniDb; struct S3JniDb { @@ -408,16 +409,16 @@ struct S3JniDb { S3JniDb * pPrev /* Previous entry in the available/free list */; }; -/** - 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 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. */ typedef struct S3JniEnv S3JniEnv; struct S3JniEnv { @@ -434,25 +435,25 @@ struct S3JniEnv { 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 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 open(), which triggers the auto-extension handler. - That handler uses pdbOpening to connect the native db handle - which it receives with pdbOpening. - - - When open() returns, check whether it invoked the auto-ext - handler. If not, complete the Java/C binding unless 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 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 open(), which triggers the auto-extension handler. That + ** handler uses pdbOpening to connect the native db handle which + ** it receives with pdbOpening. + ** + ** - When open() returns, check whether it invoked the auto-ext + ** handler. If not, complete the Java/C binding unless open() + ** returns a NULL db, in which case free pdbOpening. */ S3JniDb * pdbOpening; #ifdef SQLITE_ENABLE_FTS5 @@ -466,30 +467,30 @@ struct S3JniEnv { #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. */ S3JniNphClass nph[NphCache_SIZE]; }; /* - Whether auto extensions are feasible here is currently unknown due - to... - - 1) 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? - - 2) a chicken/egg problem involving the Java/C mapping of the db: - when auto extensions are run, the db has not yet been connected to - Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave - properly because they have a different jobject and the API - guarantees the user that _that_ object is the one the API will bind - the native to. - - If we change the open(_v2()) interfaces to use OutputPointer.sqlite3 - instead of the client passing in an instance, we could work around - (2). +** Whether auto extensions are feasible here is currently unknown due +** to... +** +** 1) 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? +** +** 2) a chicken/egg problem involving the Java/C mapping of the db: +** when auto extensions are run, the db has not yet been connected to +** Java. If we do that during the auto-ext, sqlite3_open(_v2)() will +** not behave properly because they have a different jobject and the +** API guarantees the user that _that_ object is the one the API will +** bind the native to. +** +** If we change the open(_v2()) interfaces to use +** OutputPointer.sqlite3 instead of the client passing in an instance, +** we could work around (2). */ typedef struct S3JniAutoExtension S3JniAutoExtension; struct S3JniAutoExtension { @@ -497,20 +498,20 @@ struct S3JniAutoExtension { jmethodID midFunc /* xEntryPoint() callback */; }; -/** - Global state, e.g. caches and metrics. +/* +** Global state, e.g. caches and metrics. */ static struct { - /** - According to: https://developer.ibm.com/articles/j-jni/ - - > A thread can get a JNIEnv by calling GetEnv() using the JNI - invocation interface through a JavaVM object. The JavaVM object - itself can be obtained by calling the JNI GetJavaVM() method - using a JNIEnv object and can be cached and shared across - threads. Caching a copy of the JavaVM object enables any thread - with access to the cached object to get access to its own - JNIEnv when necessary. + /* + ** According to: https://developer.ibm.com/articles/j-jni/ + ** + ** > A thread can get a JNIEnv by calling GetEnv() using the JNI + ** invocation interface through a JavaVM object. The JavaVM object + ** itself can be obtained by calling the JNI GetJavaVM() method + ** using a JNIEnv object and can be cached and shared across + ** threads. Caching a copy of the JavaVM object enables any thread + ** with access to the cached object to get access to its own + ** JNIEnv when necessary. */ JavaVM * jvm; struct { @@ -529,6 +530,7 @@ static struct { cannot always have this set to the current JNIEnv object. */; } perDb; + /* Internal metrics. */ struct { unsigned envCacheHits; unsigned envCacheMisses; @@ -1906,7 +1908,6 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, return rc; } -FIXME_THREADING(autoExt) JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){ static int once = 0; int i; @@ -1955,7 +1956,6 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){ return rc; } -FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ int rc; @@ -1970,31 +1970,26 @@ JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, return (jint)rc; } -FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt, jint ndx, jdouble val){ return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); } -FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt, jint ndx, jint val){ return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); } -FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt, jint ndx, jlong val){ return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); } -FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){ int rc = 0; jbyte * const pBuf = JBA_TOC(jName); @@ -2006,7 +2001,6 @@ JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName return rc; } -FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ if(baData){ @@ -2020,13 +2014,11 @@ JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, } } -FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt, jint ndx, jint n){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); } -FIXME_THREADING(S3JniEnv) JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, jint ndx, jlong n){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); @@ -2048,7 +2040,6 @@ static int s3jni_busy_handler(void* pState, int n){ return rc; } -FIXME_THREADING(S3JniEnv) JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); int rc = 0; @@ -2078,8 +2069,6 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ : sqlite3_busy_handler(ps->pDb, 0, 0); } -FIXME_THREADING(S3JniEnv) -FIXME_THREADING(perDb) JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); if( ps ){ @@ -2089,7 +2078,6 @@ JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ return SQLITE_MISUSE; } -FIXME_THREADING(autoExt) JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ S3JniAutoExtension * ax; jboolean rc = JNI_FALSE; @@ -2148,14 +2136,10 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ return (jint)rc; } -FIXME_THREADING(S3JniEnv) -FIXME_THREADING(perDb) JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){ return s3jni_close_db(env, pDb, 2); } -FIXME_THREADING(S3JniEnv) -FIXME_THREADING(perDb) JDECL(jint,1close)(JENV_CSELF, jobject pDb){ return s3jni_close_db(env, pDb, 1); } @@ -2194,8 +2178,6 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, UNREF_L(jName); } -FIXME_THREADING(S3JniEnv) -FIXME_THREADING(perDb) JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); jclass klazz; @@ -2232,7 +2214,6 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ return rc; } -FIXME_THREADING(S3JniEnv) JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); @@ -2246,25 +2227,21 @@ JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, } } -FIXME_THREADING(S3JniEnv) JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnv) JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnv) JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnv) JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); @@ -2273,7 +2250,6 @@ JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, return s3jni_new_jbyteArray(env, p, n); } -FIXME_THREADING(S3JniEnv) JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); @@ -2282,7 +2258,6 @@ JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, return s3jni_text16_to_jstring(env, p, n); } -FIXME_THREADING(S3JniEnv) JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); @@ -2312,7 +2287,6 @@ static void s3jni_rollback_hook_impl(void *pP){ (void)s3jni_commit_rollback_hook_impl(0, pP); } -FIXME_THREADING(perDb) static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, jobject jHook){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); @@ -2381,7 +2355,6 @@ JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ return rc; } -FIXME_THREADING(perDb) JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb) : 0; @@ -2509,7 +2482,6 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( return rc; } -FIXME_THREADING(perDb) /* sqlite3_db_config() for (int,int*) */ /* ACHTUNG: openjdk v19 creates a different mangled name for this function than openjdk v8 does. */ @@ -3174,7 +3146,6 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ (sqlite3_int64)rowId); } -FIXME_THREADING(nphCache) JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, jboolean reset ){ int iCur = 0, iHigh = 0; @@ -3186,7 +3157,6 @@ JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, return (jint)rc; } -FIXME_THREADING(nphCache) JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, jboolean reset ){ sqlite3_int64 iCur = 0, iHigh = 0; diff --git a/manifest b/manifest index c5fa07875d..baff99aba0 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\sreshaping\sof\sTester1\smoving\stowards\smaking\sa\smulti-threaded\srun\smode. -D 2023-08-15T09:26:47.524 +C Remove\sthe\sFIXME\smarkers\srelated\sto\sthreading.\sCode\sstyle\scleanups. +D 2023-08-17T10:49:06.982 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 4b93d970b142e62712f2cbdde01e1c5ed78af5f306238efad0e53276f26f1211 +F ext/jni/src/c/sqlite3-jni.c f3c4512da82b1e4b735bd350912f7434006536daba42034e67ca1c9f55a3311c F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -2091,8 +2091,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 42994b952e092ae4fa319395208622e887387ca3ff8ac57961c824a6c272bf0e -R efccc6a7a6eed17207843d66c0fb4c2d +P f104c14c26c123ee78c09fc1bc59efb8668dc624da05c1d8dbeaf3c9dd02a393 +R 752949cb94cc31a882224e4bb6c2f9c9 U stephan -Z 6864924ac8d4acb7bd32dec052e883fa +Z 3fb553739319fd15e325c90ff8ac1d2c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0857f50b77..f9ca946375 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f104c14c26c123ee78c09fc1bc59efb8668dc624da05c1d8dbeaf3c9dd02a393 \ No newline at end of file +154ab26dc6ba2d1fd976e8fe6dc1b1a06c734f7e9a276a3edc5c2f30b0d6d36a \ No newline at end of file From 1dcb24698875a44919cf843daa86b8f08de0ccc7 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 17 Aug 2023 12:44:52 +0000 Subject: [PATCH 11/37] Minor internal JNI cleanups and fixes. FossilOrigin-Name: 0e9437de026cbfb333b90bb3400f1c015f85d49d73a25ad1000623216b88bfa0 --- ext/jni/src/c/sqlite3-jni.c | 157 +++++++++++------------- ext/jni/src/org/sqlite/jni/Tester1.java | 12 +- manifest | 14 +-- manifest.uuid | 2 +- 4 files changed, 90 insertions(+), 95 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 59c591f074..bfc885eb44 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -222,7 +222,8 @@ struct S3NphRef { }; /** - Keys for each concrete NativePointerHolder subclass. + Keys for each concrete NativePointerHolder subclass. These are to + be used with S3JniGlobal_nph_cache() and friends. */ static const struct { const S3NphRef sqlite3; @@ -243,7 +244,7 @@ static const struct { const S3NphRef Fts5Tokenizer; #endif } S3NphRefs = { -#define NREF(INDEX, NAME) { INDEX, "org/sqlite/jni/" NAME } +#define NREF(INDEX, JAVANAME) { INDEX, "org/sqlite/jni/" JAVANAME } NREF(0, "sqlite3"), NREF(1, "sqlite3_stmt"), NREF(2, "sqlite3_context"), @@ -298,9 +299,11 @@ static const struct { return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \ } /** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ -#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ - JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \ - return (*env)->NewStringUTF(env, CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx)); \ +#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ + JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \ + return s3jni_utf8_to_jstring(S3JniGlobal_env_cache(env), \ + CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx), \ + -1); \ } /** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ #define WRAP_INT_DB(JniNameSuffix,CName) \ @@ -442,18 +445,19 @@ struct S3JniEnv { ** Java representation, but auto-extensions require that binding. We ** handle this as follows: ** - ** - In 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. + ** - 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 open(), which triggers the auto-extension handler. That - ** handler uses pdbOpening to connect the native db handle which - ** it receives with pdbOpening. + ** - 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 open() returns, check whether it invoked the auto-ext - ** handler. If not, complete the Java/C binding unless open() - ** returns a NULL db, in which case free 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 @@ -636,8 +640,7 @@ static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){ S3JniGlobal.envCache.aFree = row->pNext; if( row->pNext ) row->pNext->pPrev = 0; }else{ - row = sqlite3_malloc(sizeof(S3JniEnv)); - OOM_CHECK(row); + row = s3jni_malloc(env, sizeof(S3JniEnv)); } memset(row, 0, sizeof(*row)); row->pNext = S3JniGlobal.envCache.aHead; @@ -976,7 +979,7 @@ static void S3JniDb_free_for_env(JNIEnv *env){ if(ps->env == env){ S3JniDb * const pPrev = ps->pPrev; S3JniDb_set_aside(ps); - assert(pPrev ? pPrev->pNext==pNext : 1); + assert( pPrev ? pPrev->pNext==pNext : 1 ); assert( ps == S3JniGlobal.perDb.aFree ); } } @@ -994,7 +997,7 @@ static void S3JniDb_free_for_env(JNIEnv *env){ static int S3JniGlobal_env_uncache(JNIEnv * const env){ struct S3JniEnv * row; int i; - assert( 0!=S3JniGlobal.envCache.mutex && "Env mutex misuse."); + MUTEX_ASSERT_LOCKER_ENV; row = S3JniGlobal.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ @@ -1029,19 +1032,15 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ return 1; } -/** - Searches the NativePointerHolder cache for the given combination. - If it finds one, it returns it as-is. If it doesn't, it populates a - cache slot's klazz member and returns the cache slot. - - It is up to the caller to populate the other members of the returned - object if needed. - - zClassName must be a static string so we can use its address as a - cache key. - - This simple cache catches >99% of searches in the current - (2023-07-31) tests. +/* +** Searches the NativePointerHolder cache for the given combination of +** args. It returns a cache entry with its klazz member set. +** +** It is up to the caller to populate the other members of the returned +** object if needed. +** +** This simple cache catches >99% of searches in the current +** (2023-07-31) tests. */ static S3JniNphClass * S3JniGlobal_nph_cache(JNIEnv * const env, S3NphRef const* pRef){ /** @@ -1094,19 +1093,15 @@ static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, S3NphRef const* pRef){ jfieldID setter = 0; - S3JniNphClass * const pCache = S3JniGlobal_nph_cache(env, pRef); - if(pCache && pCache->klazz && pCache->fidValue){ - setter = pCache->fidValue; + S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef); + if(pNC->fidValue){ + setter = pNC->fidValue; assert(setter); }else{ - jclass const klazz = - pCache ? pCache->klazz : (*env)->GetObjectClass(env, ppOut); - setter = NativePointerHolder_getField(env, klazz); - if(pCache){ - assert(pCache->klazz); - assert(!pCache->fidValue); - pCache->fidValue = setter; - } + jclass const klazz = pNC->klazz + ? pNC->klazz + : (pNC->klazz = (*env)->GetObjectClass(env, ppOut)); + setter = pNC->fidValue = NativePointerHolder_getField(env, klazz); } (*env)->SetLongField(env, ppOut, setter, (jlong)p); EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer."); @@ -1115,23 +1110,20 @@ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, /** Fetches a native ptr value from NativePointerHolder object ppOut. zClassName must be a static string so we can use its address as a - cache key. + cache key. This is a no-op if pObj is NULL. */ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, S3NphRef const* pRef){ if( pObj ){ jfieldID getter = 0; void * rv = 0; - S3JniNphClass * const pCache = S3JniGlobal_nph_cache(env, pRef); - if(pCache && pCache->fidValue){ - getter = pCache->fidValue; + S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef); + if(pNC->fidValue){ + getter = pNC->fidValue; }else{ - jclass const klazz = - pCache ? pCache->klazz : (*env)->GetObjectClass(env, pObj); - getter = NativePointerHolder_getField(env, klazz); - if(pCache){ - assert(pCache->klazz); - pCache->fidValue = getter; - } + jclass const klazz = pNC->klazz + ? pNC->klazz + : (pNC->klazz = (*env)->GetObjectClass(env, pObj)); + getter = pNC->fidValue = NativePointerHolder_getField(env, klazz); } rv = (void*)(*env)->GetLongField(env, pObj, getter); IFTHREW_REPORT; @@ -1507,7 +1499,7 @@ typedef struct { } ResultJavaVal; /* For use with sqlite3_result/value_pointer() */ -#define RESULT_JAVA_VAL_STRING "ResultJavaVal" +#define ResultJavaValuePtrStr "ResultJavaVal" static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal)); @@ -1921,7 +1913,8 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){ /* Look for match or first empty slot. */ ax = &S3JniGlobal.autoExt.pExt[i]; if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - break /* this as a no-op. */; + MUTEX_EXT_LEAVE; + return 0 /* this as a no-op. */; }else if( !ax->jObj && firstEmptySlot<0 ){ firstEmptySlot = (int)i; } @@ -2083,8 +2076,8 @@ JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ jboolean rc = JNI_FALSE; int i; MUTEX_EXT_ENTER; -#if 1 - for( i = 0; i < S3JniGlobal.autoExt.nAlloc; ++i ){ + /* This algo mirrors the one in the core. */ + for( i = S3JniGlobal.autoExt.nAlloc-1; i >= 0; --i ){ ax = &S3JniGlobal.autoExt.pExt[i]; if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ S3JniAutoExtension_clear(env, ax); @@ -2097,21 +2090,6 @@ JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ break; } } -#else - /* Why does this impl lead to an invalid unref error? */ - for( i = S3JniGlobal.autoExt.nAlloc-1; i <= 0; --i ){ - ax = &S3JniGlobal.autoExt.pExt[i]; - if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - S3JniAutoExtension_clear(env, ax); - /* Move final entry into this slot. */ - *ax = S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc - 1]; - memset(&S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc - 1], 0, - sizeof(S3JniAutoExtension)); - rc = JNI_TRUE; - break; - } - } -#endif MUTEX_EXT_LEAVE; return rc; } @@ -2887,7 +2865,7 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ return rc; } -JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ +static void s3jni_reset_auto_extension(JNIEnv *env){ int i; MUTEX_EXT_ENTER; for( i = 0; i < S3JniGlobal.autoExt.nAlloc; ++i ){ @@ -2897,6 +2875,10 @@ JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ MUTEX_EXT_LEAVE; } +JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ + s3jni_reset_auto_extension(env); +} + /* sqlite3_result_text/blob() and friends. */ static void result_blob_text(int asBlob, int as64, int eTextRep/*only for (asBlob=0)*/, @@ -3037,8 +3019,8 @@ JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){ if(v){ ResultJavaVal * const rjv = ResultJavaVal_alloc(env, v); if(rjv){ - sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv, RESULT_JAVA_VAL_STRING, - ResultJavaVal_finalizer); + sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv, + ResultJavaValuePtrStr, ResultJavaVal_finalizer); }else{ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); } @@ -3052,16 +3034,19 @@ JDECL(void,1result_1null)(JENV_CSELF, jobject jpCx){ } JDECL(void,1result_1text)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ - return result_blob_text(0, 0, SQLITE_UTF8, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); + return result_blob_text(0, 0, SQLITE_UTF8, env, + PtrGet_sqlite3_context(jpCx), jBa, nMax); } JDECL(void,1result_1text64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax, jint eTextRep){ - return result_blob_text(0, 1, eTextRep, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); + return result_blob_text(0, 1, eTextRep, env, + PtrGet_sqlite3_context(jpCx), jBa, nMax); } JDECL(void,1result_1value)(JENV_CSELF, jobject jpCx, jobject jpSVal){ - sqlite3_result_value(PtrGet_sqlite3_context(jpCx), PtrGet_sqlite3_value(jpSVal)); + sqlite3_result_value(PtrGet_sqlite3_context(jpCx), + PtrGet_sqlite3_value(jpSVal)); } JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){ @@ -3069,10 +3054,11 @@ JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){ } JDECL(jint,1result_1zeroblob64)(JENV_CSELF, jobject jpCx, jlong v){ - return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); + return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), + (sqlite3_int64)v); } -JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ +JDECL(jobject,1rollback_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ return s3jni_commit_rollback_hook(0, env, jDb, jHook); } @@ -3094,6 +3080,7 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW("sqlite3_set_authorizer() callback"); EXCEPTION_CLEAR; + if( !rc ) rc = SQLITE_ERROR; } UNREF_L(s0); UNREF_L(s1); @@ -3173,8 +3160,8 @@ static int s3jni_strlike_glob(int isLike, JNIEnv *const env, int rc = 0; jbyte * const pG = JBA_TOC(baG); jbyte * const pT = pG ? JBA_TOC(baT) : 0; - OOM_CHECK(pT); + OOM_CHECK(pT); /* Note that we're relying on the byte arrays having been NUL-terminated on the Java side. */ rc = isLike @@ -3195,6 +3182,7 @@ 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 ); @@ -3417,7 +3405,8 @@ JDECL(jlong,1value_1int64)(JENV_CSELF, jobject jpSVal){ } JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){ - ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), RESULT_JAVA_VAL_STRING); + ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), + ResultJavaValuePtrStr); return rv ? rv->jObj : NULL; } @@ -4304,8 +4293,8 @@ Java_org_sqlite_jni_tester_SQLTester_strglob( int rc = 0; jbyte * const pG = JBA_TOC(baG); jbyte * const pT = pG ? JBA_TOC(baT) : 0; - OOM_CHECK(pT); + OOM_CHECK(pT); /* Note that we're relying on the byte arrays having been NUL-terminated on the Java side. */ rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 2629c394c9..d5ae0c44e5 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -1121,7 +1121,7 @@ public class Tester1 { outln("Woke up."); } - private void runTests() throws Exception { + private void runTests(boolean fromThread) throws Exception { if(false) testCompileOption(); test1(); testOpenDb1(); @@ -1148,13 +1148,19 @@ public class Tester1 { testUpdateHook(); testAuthorizer(); testFts5(); - testAutoExtension(); + if(!fromThread){ + testAutoExtension(); + } + } + + public void run() throws Exception{ + runTests(true); } public static void main(String[] args) throws Exception { final long timeStart = System.nanoTime(); - new Tester1("main thread").runTests(); + new Tester1("main thread").runTests(false); final long timeEnd = System.nanoTime(); final java.util.List liArgs = diff --git a/manifest b/manifest index baff99aba0..c9a1ac5402 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\sthe\sFIXME\smarkers\srelated\sto\sthreading.\sCode\sstyle\scleanups. -D 2023-08-17T10:49:06.982 +C Minor\sinternal\sJNI\scleanups\sand\sfixes. +D 2023-08-17T12:44:52.096 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c f3c4512da82b1e4b735bd350912f7434006536daba42034e67ca1c9f55a3311c +F ext/jni/src/c/sqlite3-jni.c 4af91793e92f5d195c8668cf323ca7dfd25549efa9dfc6a0344020da3c42cb4b F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -255,7 +255,7 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 99334f54f5f41feb4c14dc988b93219e37799e032f2bc07bda6323b1dfb99e75 -F ext/jni/src/org/sqlite/jni/Tester1.java 63f02d45ad073ac9d98eb7d681a024b38f6abf978dd1454be9346cbf347b1b57 +F ext/jni/src/org/sqlite/jni/Tester1.java f2f8fa157ddc42f91b6102d7ed78d1045d5072ae702bcefd868984518c97f9ae F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2091,8 +2091,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 f104c14c26c123ee78c09fc1bc59efb8668dc624da05c1d8dbeaf3c9dd02a393 -R 752949cb94cc31a882224e4bb6c2f9c9 +P 154ab26dc6ba2d1fd976e8fe6dc1b1a06c734f7e9a276a3edc5c2f30b0d6d36a +R b13f61ed94c6ec470e8a131c5363f0b3 U stephan -Z 3fb553739319fd15e325c90ff8ac1d2c +Z c23fb72e5b16d04504744e0f8c1cbcfd # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f9ca946375..5a48967def 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -154ab26dc6ba2d1fd976e8fe6dc1b1a06c734f7e9a276a3edc5c2f30b0d6d36a \ No newline at end of file +0e9437de026cbfb333b90bb3400f1c015f85d49d73a25ad1000623216b88bfa0 \ No newline at end of file From 6c5f96fc4df5c92a3ba109b999ff3c6422e88b05 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 17 Aug 2023 13:13:22 +0000 Subject: [PATCH 12/37] Tighten up the JNI auto-ext handling. FossilOrigin-Name: c09c8d05a20d916a9d9304eeea723ef7666a862a9e53f5feeeb1b03f9153d4b2 --- ext/jni/src/c/sqlite3-jni.c | 54 +++++++++++++++---------- ext/jni/src/org/sqlite/jni/Tester1.java | 36 +++++++++++++++++ manifest | 14 +++---- manifest.uuid | 2 +- 4 files changed, 76 insertions(+), 30 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index bfc885eb44..aafb99efe5 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1236,13 +1236,19 @@ static void S3JniAutoExtension_clear(JNIEnv * const env, /** Initializes a pre-allocated S3JniAutoExtension object. Returns non-0 if there is an error collecting the required state from - jAutoExt (which must be an AutoExtension object). + jAutoExt (which must be an AutoExtension object). On error, it + passes ax to S3JniAutoExtension_clear(). */ static int S3JniAutoExtension_init(JNIEnv *const env, S3JniAutoExtension * const ax, jobject const jAutoExt){ jclass klazz; klazz = (*env)->GetObjectClass(env, jAutoExt); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + assert(!klazz); + } if(!klazz){ S3JniAutoExtension_clear(env, ax); return SQLITE_ERROR; @@ -1876,7 +1882,7 @@ 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.nAlloc ){ + if( i >= S3JniGlobal.autoExt.nExt ){ ax = 0; go = 0; }else{ @@ -1893,7 +1899,7 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, UNREF_L(ex); *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); sqlite3_free(zMsg); - rc = rc ? rc : SQLITE_ERROR; + if( !rc ) rc = SQLITE_ERROR; } } } @@ -1905,25 +1911,20 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){ int i; S3JniAutoExtension * ax; int rc = 0; - int firstEmptySlot = -1; if( !jAutoExt ) return SQLITE_MISUSE; MUTEX_EXT_ENTER; - for( i = 0; i < S3JniGlobal.autoExt.nAlloc; ++i ){ + for( i = 0; i < S3JniGlobal.autoExt.nExt; ++i ){ /* Look for match or first empty slot. */ ax = &S3JniGlobal.autoExt.pExt[i]; if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ MUTEX_EXT_LEAVE; return 0 /* this as a no-op. */; - }else if( !ax->jObj && firstEmptySlot<0 ){ - firstEmptySlot = (int)i; } } - if(i == S3JniGlobal.autoExt.nAlloc ){ - if( firstEmptySlot >= 0 ){ - ax = &S3JniGlobal.autoExt.pExt[firstEmptySlot]; - rc = S3JniAutoExtension_init(env, ax, jAutoExt); - }else{ + 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; S3JniAutoExtension * const aNew = sqlite3_realloc( S3JniGlobal.autoExt.pExt, @@ -1932,17 +1933,25 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){ rc = SQLITE_NOMEM; }else{ S3JniGlobal.autoExt.pExt = aNew; - ax = &S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc]; ++S3JniGlobal.autoExt.nAlloc; - rc = S3JniAutoExtension_init(env, ax, jAutoExt); - assert( rc ? 0==ax->jObj : 0!=ax->jObj ); } } + if( 0==rc ){ + ax = &S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nExt]; + rc = S3JniAutoExtension_init(env, ax, jAutoExt); + assert( rc ? 0==ax->jObj : 0!=ax->jObj ); + } } if( 0==rc ){ - ++S3JniGlobal.autoExt.nExt; if( 0==once && ++once ){ - sqlite3_auto_extension( (void(*)(void))s3jni_run_java_auto_extensions ); + rc = sqlite3_auto_extension( (void(*)(void))s3jni_run_java_auto_extensions ); + if( rc ){ + assert( ax ); + S3JniAutoExtension_clear(env, ax); + } + } + if( 0==rc ){ + ++S3JniGlobal.autoExt.nExt; } } MUTEX_EXT_LEAVE; @@ -2077,15 +2086,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.nAlloc-1; i >= 0; --i ){ + for( i = S3JniGlobal.autoExt.nExt-1; i >= 0; --i ){ ax = &S3JniGlobal.autoExt.pExt[i]; if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ S3JniAutoExtension_clear(env, ax); /* Move final entry into this slot. */ - *ax = S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc - 1]; - memset(&S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nAlloc - 1], 0, - sizeof(S3JniAutoExtension)); --S3JniGlobal.autoExt.nExt; + *ax = S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nExt]; + memset(&S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nExt], 0, + sizeof(S3JniAutoExtension)); + assert(! S3JniGlobal.autoExt.pExt[S3JniGlobal.autoExt.nExt].jObj ); rc = JNI_TRUE; break; } @@ -2868,7 +2878,7 @@ 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.nAlloc; ++i ){ + for( i = 0; i < S3JniGlobal.autoExt.nExt; ++i ){ S3JniAutoExtension_clear( env, &S3JniGlobal.autoExt.pExt[i] ); } S3JniGlobal.autoExt.nExt = 0; diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index d5ae0c44e5..f6ca7a8547 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -1112,7 +1112,43 @@ public class Tester1 { } affirm( err!=null ); affirm( err.getMessage().indexOf(toss.value)>0 ); + toss.value = null; + + val.value = 0; + final AutoExtension ax2 = new AutoExtension(){ + public synchronized int xEntryPoint(sqlite3 db){ + ++val.value; + return 0; + } + }; + rc = sqlite3_auto_extension( ax2 ); + affirm( 0 == rc ); + sqlite3_close(createNewDb()); + affirm( 2 == val.value ); affirm( sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + sqlite3_close(createNewDb()); + affirm( 3 == val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0 == rc ); + sqlite3_close(createNewDb()); + affirm( 5 == val.value ); + affirm( sqlite3_cancel_auto_extension(ax2) ); + affirm( !sqlite3_cancel_auto_extension(ax2) ); + sqlite3_close(createNewDb()); + affirm( 6 == val.value ); + rc = sqlite3_auto_extension( ax2 ); + affirm( 0 == rc ); + sqlite3_close(createNewDb()); + affirm( 8 == val.value ); + + sqlite3_reset_auto_extension(); + sqlite3_close(createNewDb()); + affirm( 8 == val.value ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax2) ); + sqlite3_close(createNewDb()); + affirm( 8 == val.value ); } private static void testSleep(){ diff --git a/manifest b/manifest index c9a1ac5402..ba1b065e5c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\sinternal\sJNI\scleanups\sand\sfixes. -D 2023-08-17T12:44:52.096 +C Tighten\sup\sthe\sJNI\sauto-ext\shandling. +D 2023-08-17T13:13:22.545 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 4af91793e92f5d195c8668cf323ca7dfd25549efa9dfc6a0344020da3c42cb4b +F ext/jni/src/c/sqlite3-jni.c d13cceb21d449d479a7772ad004eeb3a659eebc2759ad22284a7ee6a0a4a9f62 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -255,7 +255,7 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 99334f54f5f41feb4c14dc988b93219e37799e032f2bc07bda6323b1dfb99e75 -F ext/jni/src/org/sqlite/jni/Tester1.java f2f8fa157ddc42f91b6102d7ed78d1045d5072ae702bcefd868984518c97f9ae +F ext/jni/src/org/sqlite/jni/Tester1.java 68b88b3098ce60134f4298488f890871398a77477af0a1b21797c59c911060c1 F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2091,8 +2091,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 154ab26dc6ba2d1fd976e8fe6dc1b1a06c734f7e9a276a3edc5c2f30b0d6d36a -R b13f61ed94c6ec470e8a131c5363f0b3 +P 0e9437de026cbfb333b90bb3400f1c015f85d49d73a25ad1000623216b88bfa0 +R 9c230dc36749890d5273d4c55c46fd59 U stephan -Z c23fb72e5b16d04504744e0f8c1cbcfd +Z d4ea42705a0c21f8fa4c587cb8cbbc10 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5a48967def..d5ad50128f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0e9437de026cbfb333b90bb3400f1c015f85d49d73a25ad1000623216b88bfa0 \ No newline at end of file +c09c8d05a20d916a9d9304eeea723ef7666a862a9e53f5feeeb1b03f9153d4b2 \ No newline at end of file From 383df02b16891a71c726194dbf08383a74465cd1 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 17 Aug 2023 22:04:07 +0000 Subject: [PATCH 13/37] Remove some obsolete JNI-internal docs. FossilOrigin-Name: 00a2a3736a6dcde81d920815520040f3c47f965165e7128ca1f4062e6ec7c17c --- ext/jni/src/c/sqlite3-jni.c | 13 +------------ manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index aafb99efe5..d68c29e220 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -481,20 +481,9 @@ struct S3JniEnv { ** Whether auto extensions are feasible here is currently unknown due ** to... ** -** 1) JNIEnv/threading issues. A db instance is mapped to a specific +** 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? -** -** 2) a chicken/egg problem involving the Java/C mapping of the db: -** when auto extensions are run, the db has not yet been connected to -** Java. If we do that during the auto-ext, sqlite3_open(_v2)() will -** not behave properly because they have a different jobject and the -** API guarantees the user that _that_ object is the one the API will -** bind the native to. -** -** If we change the open(_v2()) interfaces to use -** OutputPointer.sqlite3 instead of the client passing in an instance, -** we could work around (2). */ typedef struct S3JniAutoExtension S3JniAutoExtension; struct S3JniAutoExtension { diff --git a/manifest b/manifest index ba1b065e5c..fb4339f427 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Tighten\sup\sthe\sJNI\sauto-ext\shandling. -D 2023-08-17T13:13:22.545 +C Remove\ssome\sobsolete\sJNI-internal\sdocs. +D 2023-08-17T22:04:07.712 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c d13cceb21d449d479a7772ad004eeb3a659eebc2759ad22284a7ee6a0a4a9f62 +F ext/jni/src/c/sqlite3-jni.c 2682b02c376290924927386e8e0ab16c8b88feb50df584147e81696d8fb459e2 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -2091,8 +2091,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 0e9437de026cbfb333b90bb3400f1c015f85d49d73a25ad1000623216b88bfa0 -R 9c230dc36749890d5273d4c55c46fd59 +P c09c8d05a20d916a9d9304eeea723ef7666a862a9e53f5feeeb1b03f9153d4b2 +R 8dc048810b080415033a7139c2c39321 U stephan -Z d4ea42705a0c21f8fa4c587cb8cbbc10 +Z f47443de82631501c87889e378989189 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index d5ad50128f..38b3001c56 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c09c8d05a20d916a9d9304eeea723ef7666a862a9e53f5feeeb1b03f9153d4b2 \ No newline at end of file +00a2a3736a6dcde81d920815520040f3c47f965165e7128ca1f4062e6ec7c17c \ No newline at end of file From 46d677e713819706b723d5319742d45598c0d957 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 19 Aug 2023 08:22:34 +0000 Subject: [PATCH 14/37] Replace JNI::NewStringUTF() for the remaining cases where output may be incompatible with MUTF-8. It is now only used when we know the output to be plain ASCII. FossilOrigin-Name: 2d955eef25ab116c487ebc34c6f2d2836d310af239ef1993f5aeee5a3f68d590 --- ext/jni/src/c/sqlite3-jni.c | 29 +++++++++---------- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 2 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 19 +++--------- manifest | 16 +++++----- manifest.uuid | 2 +- 5 files changed, 27 insertions(+), 41 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index d68c29e220..3595eba0aa 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2321,7 +2321,8 @@ JDECL(jobject,1commit_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){ - return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ); + return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ) + /* We know these to be ASCII, so MUTF-8 is fine. */; } JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ @@ -2515,7 +2516,7 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); - S3JniEnv * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = ps ? S3JniGlobal_env_cache(env) : 0; char *zDbName; jstring jRv = 0; int nStr = 0; @@ -3066,20 +3067,19 @@ 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; - jstring const s0 = z0 ? (*env)->NewStringUTF(env, z0) : 0; - jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0; - jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0; - jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0; + 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; + jstring const s1 = z1 ? s3jni_utf8_to_jstring(jc, z1, -1) : 0; + jstring const s2 = z2 ? s3jni_utf8_to_jstring(jc, z2, -1) : 0; + jstring const s3 = z3 ? s3jni_utf8_to_jstring(jc, z3, -1) : 0; int rc; assert( pHook->jObj ); rc = (*env)->CallIntMethod(env, pHook->jObj, pHook->midCallback, (jint)op, s0, s1, s3, s3); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("sqlite3_set_authorizer() callback"); - EXCEPTION_CLEAR; - if( !rc ) rc = SQLITE_ERROR; + rc = s3jni_db_exception(env, ps, rc, "sqlite3_set_authorizer() callback"); } UNREF_L(s0); UNREF_L(s1); @@ -3296,13 +3296,11 @@ 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; - /* ACHTUNG: this will break if zDb or zTable contain chars which are - different in MUTF-8 than UTF-8. That seems like a low risk, - but it's possible. */ + S3JniEnv * const jc = S3JniGlobal_env_cache(env); jstring jDbName; jstring jTable; - jDbName = (*env)->NewStringUTF(env, zDb); - jTable = jDbName ? (*env)->NewStringUTF(env, zTable) : 0; + jDbName = s3jni_utf8_to_jstring(jc, zDb, -1); + jTable = jDbName ? s3jni_utf8_to_jstring(jc, zTable, -1) : 0; IFTHREW { s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); }else{ @@ -3311,8 +3309,7 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, (jint)opId, jDbName, jTable, (jlong)nRowid); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW("update hook"); - EXCEPTION_CLEAR; - s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw."); + s3jni_db_exception(env, ps, 0, "update hook callback threw"); } } UNREF_L(jDbName); diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index ac041e3001..4937a32fa9 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -77,7 +77,7 @@ public final class Fts5ExtensionApi extends NativePointerHolder Date: Sat, 19 Aug 2023 10:43:05 +0000 Subject: [PATCH 15/37] Add multi-thread run mode to JNI Tester1. It works but hangs on exit sometimes for Java reasons as yet not understood. FossilOrigin-Name: bdbaf7a4534f40e550b646979e67e7b7731566bb5a2631ed376ac85a9bec40a7 --- ext/jni/GNUmakefile | 9 +- ext/jni/src/c/sqlite3-jni.c | 24 ++- ext/jni/src/org/sqlite/jni/OutputPointer.java | 3 + ext/jni/src/org/sqlite/jni/Tester1.java | 161 +++++++++++------- ext/jni/src/org/sqlite/jni/TesterFts5.java | 2 +- manifest | 20 +-- manifest.uuid | 2 +- 7 files changed, 138 insertions(+), 83 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 22301a3245..6227be435b 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -163,14 +163,11 @@ SQLITE_OPT = \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_SHARED_CACHE \ - -DSQLITE_THREADSAFE=0 \ + -DSQLITE_THREADSAFE=1 \ -DSQLITE_TEMP_STORE=2 \ -DSQLITE_USE_URI=1 \ -DSQLITE_C=$(sqlite3.c) \ -DSQLITE_DEBUG -# -DSQLITE_DEBUG is just to work around a -Wall warning -# for a var which gets set in all builds but only read -# via assert(). SQLITE_OPT += -g -DDEBUG -UNDEBUG @@ -223,7 +220,7 @@ $(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE) $(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h) $(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE) $(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \ - $(sqlite3-jni.c) -shared -o $@ + $(sqlite3-jni.c) -shared -o $@ -lpthread all: $(sqlite3-jni.dll) .PHONY: test @@ -231,7 +228,7 @@ test.flags ?= -v test: $(SQLite3Jni.class) $(sqlite3-jni.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ - org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),) + org.sqlite.jni.Tester1 $(test.flags) tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) tester.flags ?= # --verbose diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 3595eba0aa..ebd1e7940c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -559,9 +559,9 @@ static struct { #define MUTEX_ASSERT_NOTLOCKER_ENV \ assert( (env) != S3JniGlobal.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) #define MUTEX_ENV_ENTER \ - /*MARKER(("Entering ENV mutex@%p %s.\n", env, __func__));*/ \ 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 #define MUTEX_ENV_LEAVE \ @@ -637,6 +637,8 @@ static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){ S3JniGlobal.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."); @@ -918,7 +920,6 @@ static void S3JniDb_set_aside(S3JniDb * const s){ if(s){ JNIEnv * const env = s->env; MUTEX_ASSERT_LOCKED_PDB; - assert(s->pDb && "Else this object is already in the free-list."); //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); assert(s->pPrev != s); assert(s->pNext != s); @@ -966,7 +967,9 @@ static void S3JniDb_free_for_env(JNIEnv *env){ for( ; ps; ps = pNext ){ pNext = ps->pNext; if(ps->env == env){ +#ifndef NDEBUG S3JniDb * const pPrev = ps->pPrev; +#endif S3JniDb_set_aside(ps); assert( pPrev ? pPrev->pNext==pNext : 1 ); assert( ps == S3JniGlobal.perDb.aFree ); @@ -996,6 +999,7 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ if( !row ){ return 0; } + //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 ){ @@ -2104,11 +2108,13 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ ps = S3JniDb_for_db(env, jDb, 0); if(ps){ rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); - MUTEX_PDB_ENTER; - S3JniDb_set_aside(ps) - /* MUST come after close() because of ps->trace. */; - MUTEX_PDB_LEAVE; - NativePointerHolder_set(env, jDb, 0, &S3NphRefs.sqlite3); + if( 0==rc ){ + MUTEX_PDB_ENTER; + S3JniDb_set_aside(ps) + /* MUST come after close() because of ps->trace. */; + MUTEX_PDB_LEAVE; + NativePointerHolder_set(env, jDb, 0, &S3NphRefs.sqlite3); + } } return (jint)rc; } @@ -4391,6 +4397,10 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ jfieldID fieldId; const ConfigFlagEntry * pConfFlag; + if( 0==sqlite3_threadsafe() ){ + (*env)->FatalError(env, "sqlite3 was not built with SQLITE_THREADSAFE."); + return; + } memset(&S3JniGlobal, 0, sizeof(S3JniGlobal)); if( (*env)->GetJavaVM(env, &S3JniGlobal.jvm) ){ (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index 82a90c9185..416ad48e60 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -36,6 +36,9 @@ package org.sqlite.jni; access to the object's value via the `value` property, whereas the JNI-level opaque types do not permit client-level code to set that property. + + Warning: do not share instances of these classes across + threads. Doing so may lead to corrupting sqlite3-internal state. */ public final class OutputPointer { diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index f6ca7a8547..ba32bd15c4 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -15,43 +15,48 @@ package org.sqlite.jni; import static org.sqlite.jni.SQLite3Jni.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; -public class Tester1 { +public class Tester1 implements Runnable { private static final class Metrics { int dbOpen; } - private String name; + private Integer tId; - Tester1(String name){ - this.name = name; + Tester1(Integer id){ + tId = id; } static final Metrics metrics = new Metrics(); - private static final OutputPointer.sqlite3_stmt outStmt - = new OutputPointer.sqlite3_stmt(); - public static void out(Object val){ + public synchronized static void out(Object val){ System.out.print(val); } - public static void outln(Object val){ + public synchronized static void outln(Object val){ + System.out.print(Thread.currentThread().getName()+": "); System.out.println(val); } @SuppressWarnings("unchecked") - public static void out(Object... vals){ + public synchronized static void out(Object... vals){ int n = 0; + System.out.print(Thread.currentThread().getName()+": "); for(Object v : vals) out((n++>0 ? " " : "")+v); } @SuppressWarnings("unchecked") - public static void outln(Object... vals){ + public synchronized static void outln(Object... vals){ out(vals); out("\n"); } - static int affirmCount = 0; - public static void affirm(Boolean v, String comment){ + static volatile int affirmCount = 0; + public synchronized static void affirm(Boolean v, String comment){ ++affirmCount; assert( v /* prefer assert over exception if it's enabled because the JNI layer sometimes has to suppress exceptions, @@ -66,11 +71,8 @@ public class Tester1 { private static void test1(){ outln("libversion_number:", - sqlite3_libversion_number() - + "\n" - + sqlite3_libversion() - + "\n" - + SQLITE_SOURCE_ID); + sqlite3_libversion_number(),"\n", + sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); //outln("threadsafe = "+sqlite3_threadsafe()); affirm(SQLITE_MAX_LENGTH > 0); @@ -106,6 +108,7 @@ public class Tester1 { byte[] sqlChunk = sqlUtf8; int rc = 0; sqlite3_stmt stmt = null; + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); while(pos < sqlChunk.length){ if(pos > 0){ sqlChunk = Arrays.copyOfRange(sqlChunk, pos, @@ -144,7 +147,7 @@ public class Tester1 { } static sqlite3_stmt prepare(sqlite3 db, String sql){ - outStmt.clear(); + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); int rc = sqlite3_prepare(db, sql, outStmt); affirm( 0 == rc ); final sqlite3_stmt rv = outStmt.take(); @@ -202,11 +205,15 @@ public class Tester1 { private static void testPrepare123(){ sqlite3 db = createNewDb(); int rc; + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt); affirm(0 == rc); - sqlite3_stmt stmt = outStmt.get(); + sqlite3_stmt stmt = outStmt.take(); affirm(0 != stmt.getNativePointer()); rc = sqlite3_step(stmt); + if( SQLITE_DONE != rc ){ + outln("step failed ??? ",rc, sqlite3_errmsg(db)); + } affirm(SQLITE_DONE == rc); sqlite3_finalize(stmt); affirm(0 == stmt.getNativePointer()); @@ -811,6 +818,7 @@ public class Tester1 { private static void testBusy(){ final String dbName = "_busy-handler.db"; final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); int rc = sqlite3_open(dbName, outDb); ++metrics.dbOpen; @@ -1163,55 +1171,92 @@ public class Tester1 { testOpenDb1(); testOpenDb2(); testPrepare123(); - testBindFetchInt(); - testBindFetchInt64(); - testBindFetchDouble(); - testBindFetchText(); - testBindFetchBlob(); - testSql(); - testCollation(); - testToUtf8(); - testStatus(); - testUdf1(); - testUdfJavaObject(); - testUdfAggregate(); - testUdfWindow(); - testTrace(); - testBusy(); - testProgress(); - testCommitHook(); - testRollbackHook(); - testUpdateHook(); - testAuthorizer(); - testFts5(); - if(!fromThread){ - testAutoExtension(); + if( true ){ + testBindFetchInt(); + testBindFetchInt64(); + testBindFetchDouble(); + testBindFetchText(); + testBindFetchBlob(); + testSql(); + testCollation(); + testToUtf8(); + testStatus(); + testUdf1(); + testUdfJavaObject(); + testUdfAggregate(); + testUdfWindow(); + testTrace(); + testProgress(); + testCommitHook(); + testRollbackHook(); + testUpdateHook(); + testAuthorizer(); + if(!fromThread){ + // skip for now: messes with affirm() counts. testFts5(); + testBusy(); + testAutoExtension(); + } } } - public void run() throws Exception{ - runTests(true); + public void run(){ + try { + runTests(0!=this.tId); + }catch(Exception e){ + throw new RuntimeException(e); + }finally{ + affirm( SQLite3Jni.uncacheJniEnv() ); + affirm( !SQLite3Jni.uncacheJniEnv() ); + } } public static void main(String[] args) throws Exception { - - final long timeStart = System.nanoTime(); - new Tester1("main thread").runTests(false); - final long timeEnd = System.nanoTime(); - - final java.util.List liArgs = - java.util.Arrays.asList(args); - //testSleep(); - if(liArgs.indexOf("-v")>0){ - sqlite3_do_something_for_developer(); - //listBoundMethods(); + Integer nThread = null; + boolean doSomethingForDev = false; + Integer nRepeat = 1; + for( int i = 0; i < args.length; ){ + String arg = args[i++]; + if(arg.startsWith("-")){ + arg = arg.replaceFirst("-+",""); + if(arg.equals("v")){ + doSomethingForDev = true; + //listBoundMethods(); + }else if(arg.equals("t") || arg.equals("thread")){ + nThread = Integer.parseInt(args[i++]); + }else if(arg.equals("r") || arg.equals("runs")){ + nRepeat = Integer.parseInt(args[i++]); + }else{ + throw new IllegalArgumentException("Unhandled flag:"+arg); + } + } } - affirm( SQLite3Jni.uncacheJniEnv() ); - affirm( !SQLite3Jni.uncacheJniEnv() ); + + final long timeStart = System.currentTimeMillis(); + int nLoop = 0; + for( int n = 0; n < nRepeat; ++n ){ + if( nThread==null || nThread<=1 ){ + new Tester1(0).runTests(false); + }else{ + final ExecutorService ex = Executors.newFixedThreadPool( nThread ); + //final List> futures = new ArrayList<>(); + ++nLoop; + outln("Running loop #",nLoop," over ",nThread," threads."); + for( int i = 0; i < nThread; ++i ){ + ex.submit( new Tester1(i) ); + } + ex.shutdown(); + ex.awaitTermination(2, java.util.concurrent.TimeUnit.SECONDS); + ex.shutdownNow(); + } + } + final long timeEnd = System.currentTimeMillis(); outln("Tests done. Metrics:"); outln("\tAssertions checked: "+affirmCount); outln("\tDatabases opened: "+metrics.dbOpen); + if( doSomethingForDev ){ + sqlite3_do_something_for_developer(); + } int nMethods = 0; int nNatives = 0; final java.lang.reflect.Method[] declaredMethods = @@ -1232,6 +1277,6 @@ public class Tester1 { nNatives+" native methods and "+ (nMethods - nNatives)+" Java impls"); outln("\tTotal test time = " - +((timeEnd - timeStart)/1000000.0)+"ms"); + +(timeEnd - timeStart)+"ms"); } } diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index 6439768e29..3fd7c9bfe0 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -72,7 +72,7 @@ public class TesterFts5 { affirm( xDestroyCalled.value ); } - public TesterFts5(){ + public TesterFts5(boolean outputStats){ int oldAffirmCount = Tester1.affirmCount; Tester1.affirmCount = 0; final long timeStart = System.nanoTime(); diff --git a/manifest b/manifest index 0ca122a07a..0ffb551daf 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Replace\sJNI::NewStringUTF()\sfor\sthe\sremaining\scases\swhere\soutput\smay\sbe\sincompatible\swith\sMUTF-8.\sIt\sis\snow\sonly\sused\swhen\swe\sknow\sthe\soutput\sto\sbe\splain\sASCII. -D 2023-08-19T08:22:34.056 +C Add\smulti-thread\srun\smode\sto\sJNI\sTester1.\sIt\sworks\sbut\shangs\son\sexit\ssometimes\sfor\sJava\sreasons\sas\syet\snot\sunderstood. +D 2023-08-19T10:43:05.945 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,10 +231,10 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile a9e11b92e620058558cbc1a2d49f8ec53c78d6a989b9db0b7d0b649b9f174881 +F ext/jni/GNUmakefile 28ef565d7a2df7b8db61826a4db3806e24bfc25f0bfa2f56fdd5527c93ecdb10 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c a8b51e4c63572d1655dafea38b80fd63528684264c6483b5c4d1eb9098c44712 +F ext/jni/src/c/sqlite3-jni.c a4a762bff193e52a264778f64545674d5b58dbcb45478e9186d603fae2c312cd F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -249,14 +249,14 @@ F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7 F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java d81f8bd43d2296ae373692370cfad16ddde76f5c14cd2760f7b4e1113ef56d4c +F ext/jni/src/org/sqlite/jni/OutputPointer.java 464ea85c3eba673a7b575545f69fcd8aeb398477a26d155d88cee3e2459e7802 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc 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 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 26b3083501a9f42e9aa49b941f6b378213cf91ae1a8f705602773ed750043a3c -F ext/jni/src/org/sqlite/jni/Tester1.java 68b88b3098ce60134f4298488f890871398a77477af0a1b21797c59c911060c1 -F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 +F ext/jni/src/org/sqlite/jni/Tester1.java 655d7109a1079be898f2631930493bd86e0c0259582014bb7af41b87d21d9a27 +F ext/jni/src/org/sqlite/jni/TesterFts5.java 3914b0a7ab0ff752c1082b1ae0c09b32827d81962fff62bcd0e13b9ec3a6f03f F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2091,8 +2091,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 00a2a3736a6dcde81d920815520040f3c47f965165e7128ca1f4062e6ec7c17c -R 844cfc265e8ec15b67feebeb0f56ea87 +P 2d955eef25ab116c487ebc34c6f2d2836d310af239ef1993f5aeee5a3f68d590 +R 8a1b71ae57321d848d8875e47d990d30 U stephan -Z a99a318492abbd023ca99730c8ec8838 +Z 68dec3da5d321e77fd308ae3996624ba # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 6d462b62c8..f2c6616be1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2d955eef25ab116c487ebc34c6f2d2836d310af239ef1993f5aeee5a3f68d590 \ No newline at end of file +bdbaf7a4534f40e550b646979e67e7b7731566bb5a2631ed376ac85a9bec40a7 \ No newline at end of file From 8d9179bd0771f291df7b315de504734a82fc1fc1 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 19 Aug 2023 11:26:52 +0000 Subject: [PATCH 16/37] Minor JNI cleanups. FossilOrigin-Name: 1cecb9e0383aa78c491f9ba88c831a88b4b2d40ceef1b87be494b6ddc0789e41 --- ext/jni/jar-dist.make | 7 +- ext/jni/src/c/sqlite3-jni.c | 34 +++++----- ext/jni/src/org/sqlite/jni/SQLFunction.java | 14 ++-- ext/jni/src/org/sqlite/jni/Tester1.java | 68 ++++++++++--------- .../src/org/sqlite/jni/sqlite3_context.java | 3 +- .../src/org/sqlite/jni/tester/SQLTester.java | 12 ++-- manifest | 22 +++--- manifest.uuid | 2 +- 8 files changed, 82 insertions(+), 80 deletions(-) diff --git a/ext/jni/jar-dist.make b/ext/jni/jar-dist.make index 9f9d13002a..9dd9f7b296 100644 --- a/ext/jni/jar-dist.make +++ b/ext/jni/jar-dist.make @@ -6,7 +6,9 @@ # proper top-level JDK directory and, depending on the platform, add a # platform-specific -I directory. It should build as-is with any # 2020s-era version of gcc or clang. It requires JDK version 8 or -# higher. +# higher and that JAVA_HOME points to the top-most installation +# directory of that JDK. On Ubuntu-style systems the JDK is typically +# installed under /usr/lib/jvm/java-VERSION-PLATFORM. default: all @@ -36,9 +38,6 @@ SQLITE_OPT = \ -DSQLITE_USE_URI=1 \ -DSQLITE_ENABLE_FTS5 \ -DSQLITE_DEBUG -# -DSQLITE_DEBUG is just to work around a -Wall warning -# for a var which gets set in all builds but only read -# via assert(). sqlite3-jni.dll = libsqlite3-jni.so $(sqlite3-jni.dll): diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index ebd1e7940c..347070ccbe 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -511,33 +511,33 @@ static struct { S3JniEnv * aHead /* Linked list of in-use instances */; S3JniEnv * aFree /* Linked list of free instances */; sqlite3_mutex * mutex /* mutex for aHead and aFree */; - void const * locker /* env mutex is held on this object's behalf - (used only for sanity checking). */; + void const * locker /* env mutex is held on this object's behalf. + Used only for sanity checking. */; } envCache; struct { S3JniDb * aUsed /* Linked list of in-use instances */; S3JniDb * aFree /* Linked list of free instances */; sqlite3_mutex * mutex /* mutex for aUsed and aFree */; - void const * locker /* perDb mutex is held on this object's - behalf. Unlike envCache.locker, we - cannot always have this set to the - current JNIEnv object. */; + void const * locker /* perDb mutex is held on this object's + behalf. Unlike envCache.locker, we cannot + always have this set to the current JNIEnv + object. Used only for sanity checking. */; } perDb; /* Internal metrics. */ struct { - unsigned envCacheHits; - unsigned envCacheMisses; - unsigned nMutexEnv /* number of times envCache.mutex was entered */; - unsigned nMutexPerDb /* number of times perDb.mutex was entered */; - unsigned nMutexAutoExt /* number of times autoExt.mutex was entered */; - unsigned nDestroy /* xDestroy() calls across all types */; + volatile unsigned envCacheHits; + volatile unsigned envCacheMisses; + volatile unsigned nMutexEnv /* number of times envCache.mutex was entered */; + 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 */; struct { /* Number of calls for each type of UDF callback. */ - unsigned nFunc; - unsigned nStep; - unsigned nFinal; - unsigned nValue; - unsigned nInverse; + volatile unsigned nFunc; + volatile unsigned nStep; + volatile unsigned nFinal; + volatile unsigned nValue; + volatile unsigned nInverse; } udf; } metrics; /** diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java index 21e5fe622a..28775608ad 100644 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -21,17 +21,19 @@ package org.sqlite.jni; This class is not used by itself, but is a marker base class. The three UDF types are modelled by the inner classes Scalar, - Aggregate, and Window. Most simply, clients may create - anonymous classes from those to implement UDFs. Clients are free to - create their own classes for use with UDFs, so long as they conform - to the public interfaces defined by those three classes. The JNI - layer only actively relies on the SQLFunction base class. + Aggregate, and Window. Most simply, clients may subclass + those, or create anonymous classes from them, to implement + UDFs. Clients are free to create their own classes for use with + UDFs, so long as they conform to the public interfaces defined by + those three classes. The JNI layer only actively relies on the + SQLFunction base class and the method names and signatures used by + the UDF callback interfaces. */ public abstract class SQLFunction { /** PerContextState assists aggregate and window functions in - managinga their accumulator state across calls to the UDF's + managing their accumulator state across calls to the UDF's callbacks. If a given aggregate or window function is called multiple times diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index ba32bd15c4..74cd7d21f4 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -69,10 +69,12 @@ public class Tester1 implements Runnable { affirm(v, "Affirmation failed."); } - private static void test1(){ - outln("libversion_number:", - sqlite3_libversion_number(),"\n", - sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); + private void test1(){ + if( 0==tId ){ + outln("libversion_number:", + sqlite3_libversion_number(),"\n", + sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); + } affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); //outln("threadsafe = "+sqlite3_threadsafe()); affirm(SQLITE_MAX_LENGTH > 0); @@ -156,7 +158,7 @@ public class Tester1 implements Runnable { return rv; } - private static void testCompileOption(){ + private void testCompileOption(){ int i = 0; String optName; outln("compile options:"); @@ -167,7 +169,7 @@ public class Tester1 implements Runnable { } - private static void testOpenDb1(){ + private void testOpenDb1(){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open(":memory:", out); ++metrics.dbOpen; @@ -189,7 +191,7 @@ public class Tester1 implements Runnable { affirm(0 == db.getNativePointer()); } - private static void testOpenDb2(){ + private void testOpenDb2(){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open_v2(":memory:", out, SQLITE_OPEN_READWRITE @@ -202,7 +204,7 @@ public class Tester1 implements Runnable { affirm(0 == db.getNativePointer()); } - private static void testPrepare123(){ + private void testPrepare123(){ sqlite3 db = createNewDb(); int rc; final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); @@ -265,7 +267,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testBindFetchInt(){ + private void testBindFetchInt(){ sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a)"); @@ -312,7 +314,7 @@ public class Tester1 implements Runnable { affirm(0 == db.getNativePointer()); } - private static void testBindFetchInt64(){ + private void testBindFetchInt64(){ sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a)"); sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); @@ -334,7 +336,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testBindFetchDouble(){ + private void testBindFetchDouble(){ sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a)"); sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); @@ -359,7 +361,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testBindFetchText(){ + private void testBindFetchText(){ sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a)"); sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); @@ -388,7 +390,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testBindFetchBlob(){ + private void testBindFetchBlob(){ sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a)"); sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); @@ -417,7 +419,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testSql(){ + private void testSql(){ sqlite3 db = createNewDb(); sqlite3_stmt stmt = prepare(db, "SELECT 1"); affirm( "SELECT 1".equals(sqlite3_sql(stmt)) ); @@ -428,7 +430,7 @@ public class Tester1 implements Runnable { sqlite3_finalize(stmt); } - private static void testCollation(){ + private void testCollation(){ final sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); final ValueHolder xDestroyCalled = new ValueHolder<>(false); @@ -501,7 +503,7 @@ public class Tester1 implements Runnable { affirm(xDestroyCalled.value); } - private static void testToUtf8(){ + private void testToUtf8(){ /** Java docs seem contradictory, claiming to use "modified UTF-8" encoding while also claiming to export using RFC 2279: @@ -515,7 +517,7 @@ public class Tester1 implements Runnable { affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */); } - private static void testStatus(){ + private void testStatus(){ final OutputPointer.Int64 cur64 = new OutputPointer.Int64(); final OutputPointer.Int64 high64 = new OutputPointer.Int64(); final OutputPointer.Int32 cur32 = new OutputPointer.Int32(); @@ -543,7 +545,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testUdf1(){ + private void testUdf1(){ final sqlite3 db = createNewDb(); // These ValueHolders are just to confirm that the func did what we want... final ValueHolder xDestroyCalled = new ValueHolder<>(false); @@ -587,7 +589,7 @@ public class Tester1 implements Runnable { affirm( xDestroyCalled.value ); } - private static void testUdfJavaObject(){ + private void testUdfJavaObject(){ final sqlite3 db = createNewDb(); final ValueHolder testResult = new ValueHolder<>(db); final SQLFunction func = new SQLFunction.Scalar(){ @@ -616,7 +618,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testUdfAggregate(){ + private void testUdfAggregate(){ final sqlite3 db = createNewDb(); final ValueHolder xFinalNull = // To confirm that xFinal() is called with no aggregate state @@ -678,7 +680,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testUdfWindow(){ + private void testUdfWindow(){ final sqlite3 db = createNewDb(); /* Example window function, table, and results taken from: https://sqlite.org/windowfunctions.html#udfwinfunc */ @@ -735,7 +737,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void listBoundMethods(){ + private void listBoundMethods(){ if(false){ final java.lang.reflect.Field[] declaredFields = SQLite3Jni.class.getDeclaredFields(); @@ -766,7 +768,7 @@ public class Tester1 implements Runnable { outln(count+" functions named sqlite3_*."); } - private static void testTrace(){ + private void testTrace(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); /* Ensure that characters outside of the UTF BMP survive the trip @@ -815,7 +817,7 @@ public class Tester1 implements Runnable { affirm( 7 == counter.value ); } - private static void testBusy(){ + private void testBusy(){ final String dbName = "_busy-handler.db"; final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); @@ -866,7 +868,7 @@ public class Tester1 implements Runnable { } } - private static void testProgress(){ + private void testProgress(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); sqlite3_progress_handler(db, 1, new ProgressHandler(){ @@ -884,7 +886,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testCommitHook(){ + private void testCommitHook(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); final ValueHolder hookResult = new ValueHolder<>(0); @@ -933,7 +935,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testUpdateHook(){ + private void testUpdateHook(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); final ValueHolder expectedOp = new ValueHolder<>(0); @@ -982,7 +984,7 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } - private static void testRollbackHook(){ + private void testRollbackHook(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); final RollbackHook theHook = new RollbackHook(){ @@ -1020,7 +1022,7 @@ public class Tester1 implements Runnable { it throws. */ @SuppressWarnings("unchecked") - private static void testFts5() throws Exception { + private void testFts5() throws Exception { if( !SQLITE_ENABLE_FTS5 ){ outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests."); return; @@ -1048,7 +1050,7 @@ public class Tester1 implements Runnable { } } - private static void testAuthorizer(){ + private void testAuthorizer(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); final ValueHolder authRc = new ValueHolder<>(0); @@ -1070,7 +1072,7 @@ public class Tester1 implements Runnable { sqlite3_close(db); } - private static void testAutoExtension(){ + private void testAutoExtension(){ final ValueHolder val = new ValueHolder<>(0); final ValueHolder toss = new ValueHolder<>(null); final AutoExtension ax = new AutoExtension(){ @@ -1159,7 +1161,7 @@ public class Tester1 implements Runnable { affirm( 8 == val.value ); } - private static void testSleep(){ + private void testSleep(){ out("Sleeping briefly... "); sqlite3_sleep(600); outln("Woke up."); @@ -1273,7 +1275,7 @@ public class Tester1 implements Runnable { } } } - outln("\tSQLite3Jni sqlite3_*() methods: "+ + outln("\tSQLite3Jni.sqlite3_*() methods: "+ nNatives+" native methods and "+ (nMethods - nNatives)+" Java impls"); outln("\tTotal test time = " diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java index a61ff21c7e..d582df7838 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_context.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java @@ -19,8 +19,7 @@ package org.sqlite.jni; */ public final class sqlite3_context extends NativePointerHolder { /** - For use only by the JNI layer. It's permitted to set this even - though it's private. + Only set by the JNI layer. */ private long aggregateContext = 0; diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index ffdb867d9b..ef3b839bc1 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -250,14 +250,14 @@ public class SQLTester { } public void runTests() throws Exception { - final long tStart = System.nanoTime(); + final long tStart = System.currentTimeMillis(); for(String f : listInFiles){ reset(); ++nTestFile; final TestScript ts = new TestScript(f); outln(nextStartEmoji(), " starting [",f,"]"); boolean threw = false; - final long timeStart = System.nanoTime(); + final long timeStart = System.currentTimeMillis(); try{ ts.run(this); }catch(SQLTesterException e){ @@ -267,14 +267,14 @@ public class SQLTester { if( keepGoing ) outln("Continuing anyway becaure of the keep-going option."); else if( e.isFatal() ) throw e; }finally{ - final long timeEnd = System.nanoTime(); + final long timeEnd = System.currentTimeMillis(); outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ", - ((timeEnd-timeStart)/1000000.0),"ms."); + (timeEnd-timeStart),"ms."); //ts.getFilename()); } } - final long tEnd = System.nanoTime(); - outln("Total run-time: ",((tEnd-tStart)/1000000.0),"ms"); + final long tEnd = System.currentTimeMillis(); + outln("Total run-time: ",(tEnd-tStart),"ms"); Util.unlink(initialDbName); } diff --git a/manifest b/manifest index 0ffb551daf..ca1c0333b0 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\smulti-thread\srun\smode\sto\sJNI\sTester1.\sIt\sworks\sbut\shangs\son\sexit\ssometimes\sfor\sJava\sreasons\sas\syet\snot\sunderstood. -D 2023-08-19T10:43:05.945 +C Minor\sJNI\scleanups. +D 2023-08-19T11:26:52.575 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -233,8 +233,8 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 28ef565d7a2df7b8db61826a4db3806e24bfc25f0bfa2f56fdd5527c93ecdb10 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb -F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c a4a762bff193e52a264778f64545674d5b58dbcb45478e9186d603fae2c312cd +F ext/jni/jar-dist.make bb29ff5c369c95ffcd3687cacf35f7730fd33be2fe9b1ec31670fcd7d223980e +F ext/jni/src/c/sqlite3-jni.c 8608cb36223d6bc64e8ddd9296b6d63a4fc54efaf8dbc3ea7e5eca81f4801d42 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -253,9 +253,9 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 464ea85c3eba673a7b575545f69fcd8a F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc 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 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 +F ext/jni/src/org/sqlite/jni/SQLFunction.java 8c1ad92c35bcc1b2f7256cf6e229b31340ed6d1a404d487f0a9adb28ba7fc332 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 26b3083501a9f42e9aa49b941f6b378213cf91ae1a8f705602773ed750043a3c -F ext/jni/src/org/sqlite/jni/Tester1.java 655d7109a1079be898f2631930493bd86e0c0259582014bb7af41b87d21d9a27 +F ext/jni/src/org/sqlite/jni/Tester1.java 42341a1031fe6f1433b86a55718c38bd75b96105ef38b0c9ea88003ec637968c F ext/jni/src/org/sqlite/jni/TesterFts5.java 3914b0a7ab0ff752c1082b1ae0c09b32827d81962fff62bcd0e13b9ec3a6f03f F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -264,10 +264,10 @@ F ext/jni/src/org/sqlite/jni/fts5_api.java 5198be71c162e3e0cb1f4962a7cdf0d7596e8 F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc -F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 +F ext/jni/src/org/sqlite/jni/sqlite3_context.java fe7797a696978f057528a57b7a11e7797ed41fd7afcf100c5ebb67055d9f706f F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1f1286428fab38dfefe328e72b5735f533b19af8dd17712dd3df7e044d21c8b8 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2835eb3dd1e14767ca49354c224150c70300d8013d6d51dd875f7d9380faa278 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2091,8 +2091,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 2d955eef25ab116c487ebc34c6f2d2836d310af239ef1993f5aeee5a3f68d590 -R 8a1b71ae57321d848d8875e47d990d30 +P bdbaf7a4534f40e550b646979e67e7b7731566bb5a2631ed376ac85a9bec40a7 +R ff1ad0e885103241e4eaf57ec50ed798 U stephan -Z 68dec3da5d321e77fd308ae3996624ba +Z abc1dde430026808a827edd11bd0eed1 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f2c6616be1..1ceb1bcd4d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -bdbaf7a4534f40e550b646979e67e7b7731566bb5a2631ed376ac85a9bec40a7 \ No newline at end of file +1cecb9e0383aa78c491f9ba88c831a88b4b2d40ceef1b87be494b6ddc0789e41 \ No newline at end of file From 187da4337990932947de3592487d21c01a809e62 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 19 Aug 2023 11:52:36 +0000 Subject: [PATCH 17/37] JNI test code cleanups. FossilOrigin-Name: e202b6e69da8cced114d027cf2e91a04dfdd50b601b3274214783f7d750c558c --- ext/jni/GNUmakefile | 6 ++- ext/jni/jar-dist.make | 4 +- ext/jni/src/org/sqlite/jni/Tester1.java | 61 ++++++++++++------------- manifest | 16 +++---- manifest.uuid | 2 +- 5 files changed, 44 insertions(+), 45 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 6227be435b..a2efff13bb 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -229,6 +229,10 @@ test: $(SQLite3Jni.class) $(sqlite3-jni.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ org.sqlite.jni.Tester1 $(test.flags) +test-mt: $(SQLite3Jni.class) $(sqlite3-jni.dll) + $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.Tester1 -t 4 -r 5 $(test.flags) tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) tester.flags ?= # --verbose @@ -264,7 +268,7 @@ endif tester-ext: tester-local tester: tester-ext -tests: test tester +tests: test tester test-mt package.jar.in := $(abspath $(dir.src)/jar.in) CLEAN_FILES += $(package.jar.in) $(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) diff --git a/ext/jni/jar-dist.make b/ext/jni/jar-dist.make index 9dd9f7b296..ac1a768b8c 100644 --- a/ext/jni/jar-dist.make +++ b/ext/jni/jar-dist.make @@ -33,7 +33,7 @@ SQLITE_OPT = \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_SHARED_CACHE \ - -DSQLITE_THREADSAFE=0 \ + -DSQLITE_THREADSAFE=1 \ -DSQLITE_TEMP_STORE=2 \ -DSQLITE_USE_URI=1 \ -DSQLITE_ENABLE_FTS5 \ @@ -46,7 +46,7 @@ $(sqlite3-jni.dll): echo "*** to configure it for your system. ***"; \ echo "************************************************************************" $(CC) $(CFLAGS) $(SQLITE_OPT) \ - src/sqlite3-jni.c -shared -o $@ + src/sqlite3-jni.c -lpthread -shared -o $@ @echo "Now try running it with: make test" test: $(sqlite3-jni.dll) diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 74cd7d21f4..41cd154b61 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -70,13 +70,7 @@ public class Tester1 implements Runnable { } private void test1(){ - if( 0==tId ){ - outln("libversion_number:", - sqlite3_libversion_number(),"\n", - sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); - } affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); - //outln("threadsafe = "+sqlite3_threadsafe()); affirm(SQLITE_MAX_LENGTH > 0); affirm(SQLITE_MAX_TRIGGER_DEPTH>0); } @@ -1072,7 +1066,7 @@ public class Tester1 implements Runnable { sqlite3_close(db); } - private void testAutoExtension(){ + private synchronized void testAutoExtension(){ final ValueHolder val = new ValueHolder<>(0); final ValueHolder toss = new ValueHolder<>(null); final AutoExtension ax = new AutoExtension(){ @@ -1173,31 +1167,29 @@ public class Tester1 implements Runnable { testOpenDb1(); testOpenDb2(); testPrepare123(); - if( true ){ - testBindFetchInt(); - testBindFetchInt64(); - testBindFetchDouble(); - testBindFetchText(); - testBindFetchBlob(); - testSql(); - testCollation(); - testToUtf8(); - testStatus(); - testUdf1(); - testUdfJavaObject(); - testUdfAggregate(); - testUdfWindow(); - testTrace(); - testProgress(); - testCommitHook(); - testRollbackHook(); - testUpdateHook(); - testAuthorizer(); - if(!fromThread){ - // skip for now: messes with affirm() counts. testFts5(); - testBusy(); - testAutoExtension(); - } + testBindFetchInt(); + testBindFetchInt64(); + testBindFetchDouble(); + testBindFetchText(); + testBindFetchBlob(); + testSql(); + testCollation(); + testToUtf8(); + testStatus(); + testUdf1(); + testUdfJavaObject(); + testUdfAggregate(); + testUdfWindow(); + testTrace(); + testProgress(); + testCommitHook(); + testRollbackHook(); + testUpdateHook(); + testAuthorizer(); + testAutoExtension(); + if(!fromThread){ + // testFts5(); // skip for now: messes with affirm() counts. + testBusy(); } } @@ -1235,6 +1227,9 @@ public class Tester1 implements Runnable { final long timeStart = System.currentTimeMillis(); int nLoop = 0; + outln("libversion_number:", + sqlite3_libversion_number(),"\n", + sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); for( int n = 0; n < nRepeat; ++n ){ if( nThread==null || nThread<=1 ){ new Tester1(0).runTests(false); @@ -1247,7 +1242,7 @@ public class Tester1 implements Runnable { ex.submit( new Tester1(i) ); } ex.shutdown(); - ex.awaitTermination(2, java.util.concurrent.TimeUnit.SECONDS); + ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS); ex.shutdownNow(); } } diff --git a/manifest b/manifest index ca1c0333b0..bc94cb5d24 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\sJNI\scleanups. -D 2023-08-19T11:26:52.575 +C JNI\stest\scode\scleanups. +D 2023-08-19T11:52:36.349 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,9 +231,9 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 28ef565d7a2df7b8db61826a4db3806e24bfc25f0bfa2f56fdd5527c93ecdb10 +F ext/jni/GNUmakefile 4217266579b74499708d0b9fd7de598d67cc1992646518596ec83f134c3e2d85 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb -F ext/jni/jar-dist.make bb29ff5c369c95ffcd3687cacf35f7730fd33be2fe9b1ec31670fcd7d223980e +F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 F ext/jni/src/c/sqlite3-jni.c 8608cb36223d6bc64e8ddd9296b6d63a4fc54efaf8dbc3ea7e5eca81f4801d42 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 @@ -255,7 +255,7 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c 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 26b3083501a9f42e9aa49b941f6b378213cf91ae1a8f705602773ed750043a3c -F ext/jni/src/org/sqlite/jni/Tester1.java 42341a1031fe6f1433b86a55718c38bd75b96105ef38b0c9ea88003ec637968c +F ext/jni/src/org/sqlite/jni/Tester1.java b0ad66a7e7b70dcff98557d3b092e1a2b195d1c7e91e02e51ea8597f842dc5a4 F ext/jni/src/org/sqlite/jni/TesterFts5.java 3914b0a7ab0ff752c1082b1ae0c09b32827d81962fff62bcd0e13b9ec3a6f03f F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2091,8 +2091,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 bdbaf7a4534f40e550b646979e67e7b7731566bb5a2631ed376ac85a9bec40a7 -R ff1ad0e885103241e4eaf57ec50ed798 +P 1cecb9e0383aa78c491f9ba88c831a88b4b2d40ceef1b87be494b6ddc0789e41 +R a49c27c6a99f3ab466acd799b27c01cf U stephan -Z abc1dde430026808a827edd11bd0eed1 +Z a181fae7a989c6647ffb5b92efaaa712 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 1ceb1bcd4d..9f668d406a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1cecb9e0383aa78c491f9ba88c831a88b4b2d40ceef1b87be494b6ddc0789e41 \ No newline at end of file +e202b6e69da8cced114d027cf2e91a04dfdd50b601b3274214783f7d750c558c \ No newline at end of file From bfa486d5fce78053bdcf30921006ffd58ccaa384 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 19 Aug 2023 12:32:00 +0000 Subject: [PATCH 18/37] JNI doc additions. FossilOrigin-Name: 0c7ac34f30e1f7e35a2ac4e5e55e5f24857b24afa81a7abecba60f1c9c68b9ff --- ext/jni/GNUmakefile | 3 +- ext/jni/README.md | 103 +++++++++++++++++++++++--------------------- manifest | 14 +++--- manifest.uuid | 2 +- 4 files changed, 62 insertions(+), 60 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index a2efff13bb..699d5aaf25 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -273,8 +273,7 @@ package.jar.in := $(abspath $(dir.src)/jar.in) CLEAN_FILES += $(package.jar.in) $(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@ - @ls -la $@ - @echo "To use this jar you will need the -Djava.library.path=DIR/WITH/libsqlite3-jni.so flag." + @echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag." @echo "e.g. java -jar $@ -Djava.library.path=bld" $(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in) diff --git a/ext/jni/README.md b/ext/jni/README.md index 80486d4a07..395365292f 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -15,7 +15,10 @@ Technical support is available in the forum: > **FOREWARNING:** this subproject is very much in development and subject to any number of changes. Please do not rely on any - information about its API until this disclaimer is removed. + information about its API until this disclaimer is removed. The JNI + bindgins released with version 3.43 are a "tech preview" and 3.44 + will be "final," at which point strong backward compatibility + guarantees will apply. Project goals/requirements: @@ -40,13 +43,30 @@ Non-goals: - Creation of high-level OO wrapper APIs. Clients are free to create them off of the C-style API. +Hello World +----------------------------------------------------------------------- -Significant TODOs -======================================================================== +```java +import org.sqlite.jni.*; +import static org.sqlite.jni.SQLite3Jni; +... +OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); +int rc = sqlite3_open(":memory:", out); +final sqlite3 db = out.take(); +if( 0 != rc ){ + if( null != db ){ + System.out.print("Error opening db: "+sqlite3_errmsg(db)); + sqlite3_close(db); + }else{ + System.out.print("Error opening db: rc="+rc); + } + ... handle error ... +} -- Lots of APIs left to bind. Most "day-to-day" functionality is already - in place and is believed to work well. +... use db ... +sqlite3_close_v2(db); +``` Building ======================================================================== @@ -59,55 +79,33 @@ The canonical builds assumes a Linux-like environment and requires: Put simply: -``` +```console $ export JAVA_HOME=/path/to/jdk/root $ make $ make test $ make clean ``` +The jar distribution can be created with `make jar`. + One-to-One(-ish) Mapping to C ======================================================================== This JNI binding aims to provide as close to a 1-to-1 experience with -the C API as cross-language semantics allow. Exceptions are -necessarily made where cross-language semantics do not allow a 1-to-1, -and judiciously made where a 1-to-1 mapping would be unduly cumbersome -to use in Java. +the C API as cross-language semantics allow. Changes are necessarily +made where cross-language semantics do not allow a 1-to-1, and +judiciously made where a 1-to-1 mapping would be unduly cumbersome to +use in Java. -Golden Rule: _Never_ Throw from Callbacks +Golden Rule: _Never_ Throw from Callbacks (Unless...) ------------------------------------------------------------------------ -JNI bindings which accept client-defined functions _must never throw -exceptions_ unless _very explicitly documented_ as being -throw-safe. Exceptions are generally reserved for higher-level -bindings which are constructed to specifically deal with them and -ensure that they do not leak C-level resources. Some of the JNI -bindings are provided as Java functions which expect this rule to -always hold. - -UTF-8(-ish) ------------------------------------------------------------------------- - -SQLite internally uses UTF-8 encoding, whereas Java natively uses -UTF-16. Java JNI has routines for converting to and from UTF-8, _but_ -Java uses what its docs call "[modified UTF-8][modutf8]." Care must be -taken when converting Java strings to UTF-8 to ensure that the proper -conversion is performed. In short, -`String.getBytes(StandardCharsets.UTF_8)` performs the proper -conversion in Java, and there is no JNI C API for that conversion -(JNI's `NewStringUTF()` returns MUTF-8). - -Known consequences and limitations of this discrepancy include: - -- Names of databases, tables, and collations must not contain - characters which differ in MUTF-8 and UTF-8, or certain APIs will - mis-translate them on their way between languages. APIs which - transfer other client-side data to Java take extra care to - convert the data at the cost of performance. - -[modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 +Client-defined callbacks _must never throw exceptions_ unless _very +explicitly documented_ as being throw-safe. Exceptions are generally +reserved for higher-level bindings which are constructed to +specifically deal with them and ensure that they do not leak C-level +resources. Unwieldy Constructs are Re-mapped @@ -125,7 +123,7 @@ A prime example of where interface changes for Java are necessary for usability is [registration of a custom collation](https://sqlite.org/c3ref/create_collation.html): -``` +```c // C: int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep, void *pUserData, @@ -144,7 +142,7 @@ passed that object as their first argument. That data is passed around bind that part as-is to Java, the result would be awkward to use (^Yes, we tried this.): -``` +```java // Java: int sqlite3_create_collation(sqlite3 db, String name, int eTextRep, Object pUserData, xCompareType xCompare); @@ -159,7 +157,7 @@ for callbacks and (B) having their internal state provided separately, which is ill-fitting in Java. For the sake of usability, C APIs which follow that pattern use a slightly different Java interface: -``` +```java int sqlite3_create_collation(sqlite3 db, String name, int eTextRep, Collation collation); ``` @@ -168,7 +166,7 @@ Where the `Collation` class has an abstract `xCompare()` method and no-op `xDestroy()` method which can be overridden if needed, leading to a much more Java-esque usage: -``` +```java int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(){ // Required comparison function: @@ -187,8 +185,8 @@ int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation( Noting that: -- It is still possible to bind in call-scope-local state via closures, - if desired. +- It is possible to bind in call-scope-local state via closures, if + desired, as opposed to packing it into the Collation object. - No capabilities of the C API are lost or unduly obscured via the above API reshaping, so power users need not make any compromises. @@ -199,6 +197,7 @@ Noting that: overriding the `xDestroy()` method effectively gives it v2 semantics. + ### User-defined SQL Functions (a.k.a. UDFs) The [`sqlite3_create_function()`](https://sqlite.org/c3ref/create_function.html) @@ -206,12 +205,13 @@ family of APIs make heavy use of function pointers to provide client-defined callbacks, necessitating interface changes in the JNI binding. The Java API has only one core function-registration function: -``` +```java int sqlite3_create_function(sqlite3 db, String funcName, int nArgs, int encoding, SQLFunction func); ``` -> Design question: does the encoding argument serve any purpose in JS? +> Design question: does the encoding argument serve any purpose in + Java? That's as-yet undetermined. If not, it will be removed. `SQLFunction` is not used directly, but is instead instantiated via one of its three subclasses: @@ -229,5 +229,8 @@ Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for Reminder: see the disclaimer at the top of this document regarding the in-flux nature of this API. -[jsrc]: /file/ -[www]: https://sqlite.org +### And so on... + +Various APIs which accept callbacks, e.g. `sqlite3_trace_v2()` and +`sqlite3_update_hook()`, use interfaces similar to those shown above. + diff --git a/manifest b/manifest index bc94cb5d24..560dbc569e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C JNI\stest\scode\scleanups. -D 2023-08-19T11:52:36.349 +C JNI\sdoc\sadditions. +D 2023-08-19T12:32:00.131 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,8 +231,8 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 4217266579b74499708d0b9fd7de598d67cc1992646518596ec83f134c3e2d85 -F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb +F ext/jni/GNUmakefile ace719d6d7025a0a454b7c661de2beef9a0e819535ad0ce0742034da8ff8d27f +F ext/jni/README.md 975b35173debbbf3a4ab7166e14d2ffa2bacff9b6850414f09cc919805e81ba4 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 F ext/jni/src/c/sqlite3-jni.c 8608cb36223d6bc64e8ddd9296b6d63a4fc54efaf8dbc3ea7e5eca81f4801d42 F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 @@ -2091,8 +2091,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 1cecb9e0383aa78c491f9ba88c831a88b4b2d40ceef1b87be494b6ddc0789e41 -R a49c27c6a99f3ab466acd799b27c01cf +P e202b6e69da8cced114d027cf2e91a04dfdd50b601b3274214783f7d750c558c +R 109361c70c87f2804d75b9c237ee841f U stephan -Z a181fae7a989c6647ffb5b92efaaa712 +Z 56d27110aeccda606f4b75566f7a0870 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 9f668d406a..a2e0dd926c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e202b6e69da8cced114d027cf2e91a04dfdd50b601b3274214783f7d750c558c \ No newline at end of file +0c7ac34f30e1f7e35a2ac4e5e55e5f24857b24afa81a7abecba60f1c9c68b9ff \ No newline at end of file From 10892771054286b05ef843e8d726a6270f508125 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 19 Aug 2023 14:49:08 +0000 Subject: [PATCH 19/37] JNI cleanups. FossilOrigin-Name: 0a84131008a2e7886dac64a3545dea634811f6eac2b90885ec9c61ed1e6544c3 --- ext/jni/src/c/sqlite3-jni.c | 13 ++---- ext/jni/src/c/sqlite3-jni.h | 20 +++------ .../src/org/sqlite/jni/Fts5ExtensionApi.java | 42 +++++++++---------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 36 ++++++++-------- ext/jni/src/org/sqlite/jni/Tester1.java | 8 +++- ext/jni/src/org/sqlite/jni/TesterFts5.java | 10 ++--- manifest | 22 +++++----- manifest.uuid | 2 +- 8 files changed, 72 insertions(+), 81 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 347070ccbe..2216af6259 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2225,12 +2225,12 @@ JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, +JDECL(jbyteArray,1column_1text_1utf8)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); const int n = sqlite3_column_bytes(stmt, (int)ndx); const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx); - return s3jni_new_jbyteArray(env, p, n); + return p ? s3jni_new_jbyteArray(env, p, n) : NULL; } JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, @@ -3412,18 +3412,11 @@ JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){ return rv ? rv->jObj : NULL; } -JDECL(jstring,1value_1text)(JENV_CSELF, jobject jpSVal){ - sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); - int const n = sqlite3_value_bytes16(sv); - const void * const p = sqlite3_value_text16(sv); - return s3jni_text16_to_jstring(env, p, n); -} - JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); int const n = sqlite3_value_bytes(sv); const unsigned char * const p = sqlite3_value_text(sv); - return s3jni_new_jbyteArray(env, p, n); + return p ? s3jni_new_jbyteArray(env, p, n) : 0; } static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){ diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 5d741859f1..e189df6947 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1013,18 +1013,18 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1table_ /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_column_text16 - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + * Method: sqlite3_column_text_utf8 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text16 +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text_1utf8 (JNIEnv *, jclass, jobject, jint); /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_column_text - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + * Method: sqlite3_column_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text16 (JNIEnv *, jclass, jobject, jint); /* @@ -1659,14 +1659,6 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int64 JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1java_1object (JNIEnv *, jclass, jobject); -/* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text - * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text - (JNIEnv *, jclass, jobject); - /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_value_text_utf8 diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index 4937a32fa9..4b04ed48a6 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -23,7 +23,7 @@ import java.nio.charset.StandardCharsets; public final class Fts5ExtensionApi extends NativePointerHolder { //! Only called from JNI private Fts5ExtensionApi(){} - private int iVersion = 2; + private final int iVersion = 2; /* Callback type for used by xQueryPhrase(). */ public static interface xQueryPhraseCallback { @@ -33,54 +33,54 @@ public final class Fts5ExtensionApi extends NativePointerHolder> futures = new ArrayList<>(); ++nLoop; diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index 3fd7c9bfe0..bc66f5c9fa 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -72,16 +72,14 @@ public class TesterFts5 { affirm( xDestroyCalled.value ); } - public TesterFts5(boolean outputStats){ + public TesterFts5(){ int oldAffirmCount = Tester1.affirmCount; - Tester1.affirmCount = 0; - final long timeStart = System.nanoTime(); + final long timeStart = System.currentTimeMillis(); test1(); - final long timeEnd = System.nanoTime(); + final long timeEnd = System.currentTimeMillis(); outln("FTS5 Tests done. Metrics:"); outln("\tAssertions checked: "+Tester1.affirmCount); outln("\tTotal time = " - +((timeEnd - timeStart)/1000000.0)+"ms"); - Tester1.affirmCount = oldAffirmCount; + +(timeEnd - timeStart)+"ms"); } } diff --git a/manifest b/manifest index 7a9e3a9c76..24c718d165 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\strunk\sinto\sjni-threading\sbranch. -D 2023-08-19T12:34:23.970 +C JNI\scleanups. +D 2023-08-19T14:49:08.030 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -235,8 +235,8 @@ 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 8608cb36223d6bc64e8ddd9296b6d63a4fc54efaf8dbc3ea7e5eca81f4801d42 -F ext/jni/src/c/sqlite3-jni.h f10d2f38720687c70ecdd5e44f6e8db98efee2caa05fc86b2d9e0c76e6cc0a18 +F ext/jni/src/c/sqlite3-jni.c fc9ab59b3d966219f20fdc93fae7bc5ce19900f7c979b84489be4384c8532120 +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/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -245,7 +245,7 @@ F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 93c0643e77a0226dad31610489490c6348ed9842c91e98f3ab0c440a173e75e7 +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 7ecfce8075381999fcdfb94467535be9c63df0332e63bf57cbcb072036e1d113 F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 @@ -255,9 +255,9 @@ 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 26b3083501a9f42e9aa49b941f6b378213cf91ae1a8f705602773ed750043a3c -F ext/jni/src/org/sqlite/jni/Tester1.java b0ad66a7e7b70dcff98557d3b092e1a2b195d1c7e91e02e51ea8597f842dc5a4 -F ext/jni/src/org/sqlite/jni/TesterFts5.java 3914b0a7ab0ff752c1082b1ae0c09b32827d81962fff62bcd0e13b9ec3a6f03f +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5c469585946b63592cafe134b01af0b9144a12131f22ea352e12f4c3ec70efb2 +F ext/jni/src/org/sqlite/jni/Tester1.java d1e59c7601100e60f5467e52a04f032881344246436601912eafc5f61aeea134 +F ext/jni/src/org/sqlite/jni/TesterFts5.java bd4b6316ef83e2c85b5f1f9729383c736c1771652339d1135493b5217c9d1bb3 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -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 0c7ac34f30e1f7e35a2ac4e5e55e5f24857b24afa81a7abecba60f1c9c68b9ff c319033276c3565d0f1f2cae1c91791940d322fe79696bc26d74fddeb2664373 -R 35896553571ede397fc34e5af8e0e6bc +P 8254479c6ff1ea3cc9e56de1698db8405c03da90b9bf4c401182e47e0842baf8 +R 846f6f71bef64f8d4f48e59243174d3b U stephan -Z 1f979ceda0d0b798d0ca7e29bc8a0301 +Z 63d30481f30daf5779abda92ef1829f2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index aaad1b3cae..7a96dc38dd 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8254479c6ff1ea3cc9e56de1698db8405c03da90b9bf4c401182e47e0842baf8 \ No newline at end of file +0a84131008a2e7886dac64a3545dea634811f6eac2b90885ec9c61ed1e6544c3 \ No newline at end of file From 484f9bed4e96e67386304d6c8dd5b8263ec85c49 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 21 Aug 2023 23:45:19 +0000 Subject: [PATCH 20/37] Minor JNI cleanups. FossilOrigin-Name: b88910aaaaaaa0936974379bb3eb8a5a3a634395b14e67cc9030f8a520f471f1 --- ext/jni/src/c/sqlite3-jni.c | 17 +++++++++------- ext/jni/src/org/sqlite/jni/Fts5.java | 2 +- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 2 +- ext/jni/src/org/sqlite/jni/Tester1.java | 13 ++++++++---- ext/jni/src/org/sqlite/jni/TesterFts5.java | 5 +++-- manifest | 20 +++++++++---------- manifest.uuid | 2 +- 7 files changed, 35 insertions(+), 26 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 2216af6259..25f2420996 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -3735,6 +3735,7 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, int rc; char const * zName; Fts5JniAux * pAux; + assert(pApi); zName = JSTR_TOC(jName); if(!zName) return SQLITE_NOMEM; @@ -3756,15 +3757,15 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, } -typedef struct s3jni_fts5AuxData s3jni_fts5AuxData; -struct s3jni_fts5AuxData { +typedef struct S3JniFts5AuxData S3JniFts5AuxData; +struct S3JniFts5AuxData { JNIEnv *env; jobject jObj; }; -static void s3jni_fts5AuxData_xDestroy(void *x){ +static void S3JniFts5AuxData_xDestroy(void *x){ if(x){ - s3jni_fts5AuxData * const p = x; + S3JniFts5AuxData * const p = x; if(p->jObj){ JNIEnv *env = p->env; s3jni_call_xDestroy(env, p->jObj, 0); @@ -3777,7 +3778,7 @@ static void s3jni_fts5AuxData_xDestroy(void *x){ JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){ Fts5ExtDecl; jobject rv = 0; - s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); + S3JniFts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); if(pAux){ if(bClear){ if( pAux->jObj ){ @@ -3966,6 +3967,7 @@ JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, S3JniEnv * const jc = S3JniGlobal_env_cache(env); struct s3jni_xQueryPhraseState s; jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ) return SQLITE_MISUSE; s.env = env; s.jc = jc; @@ -3997,7 +3999,7 @@ JDECLFtsXA(jlong,xRowid)(JENV_OSELF,jobject jCtx){ JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ Fts5ExtDecl; int rc; - s3jni_fts5AuxData * pAux; + S3JniFts5AuxData * pAux; pAux = sqlite3_malloc(sizeof(*pAux)); if(!pAux){ if(jAux){ @@ -4010,7 +4012,7 @@ JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ pAux->env = env; pAux->jObj = REF_G(jAux); rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux, - s3jni_fts5AuxData_xDestroy); + S3JniFts5AuxData_xDestroy); return rc; } @@ -4055,6 +4057,7 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, S3NphRef const *pRef, jbyte * const pText = jCallback ? JBA_TOC(jbaText) : 0; jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0; jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ) return SQLITE_MISUSE; memset(&s, 0, sizeof(s)); s.env = env; diff --git a/ext/jni/src/org/sqlite/jni/Fts5.java b/ext/jni/src/org/sqlite/jni/Fts5.java index 102cf575a8..443a69a409 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5.java +++ b/ext/jni/src/org/sqlite/jni/Fts5.java @@ -27,7 +27,7 @@ public final class Fts5 { //! Callback type for use with xTokenize() variants public static interface xTokenizeCallback { - int xToken(int tFlags, byte txt[], int iStart, int iEnd); + int xToken(int tFlags, byte[] txt, int iStart, int iEnd); } public static final int FTS5_TOKENIZE_QUERY = 0x0001; diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index 4b04ed48a6..205f110f41 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -82,5 +82,5 @@ public final class Fts5ExtensionApi extends NativePointerHolder Date: Tue, 22 Aug 2023 11:34:34 +0000 Subject: [PATCH 21/37] Move most of the per-JNIEnv global Java class refs into the global state, saving a bit of per-thread overhead. FossilOrigin-Name: 7342bf578790e1a87c128a7c1c7745fe2e7c442890370feb160d406597d4d8ec --- ext/jni/src/c/sqlite3-jni.c | 506 ++++++++++++++++++------------------ manifest | 12 +- manifest.uuid | 2 +- 3 files changed, 254 insertions(+), 266 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 25f2420996..d3fd0db82a 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -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, - "", "(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, - "", "([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, + "", "(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, + "", "([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 ){ diff --git a/manifest b/manifest index 98f0ce772d..62da389266 100644 --- a/manifest +++ b/manifest @@ -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. diff --git a/manifest.uuid b/manifest.uuid index 48b34f86bd..ee5d057164 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b88910aaaaaaa0936974379bb3eb8a5a3a634395b14e67cc9030f8a520f471f1 \ No newline at end of file +7342bf578790e1a87c128a7c1c7745fe2e7c442890370feb160d406597d4d8ec \ No newline at end of file From 9828aa223ab09cb208d639e0e3c367c8ef34faa8 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 22 Aug 2023 15:30:35 +0000 Subject: [PATCH 22/37] Move the JNI per-thread cache of NativePointerHolder refs into global space. This allows better-targeted mutex locks and incidentally eliminates the lagginess and post-run hangs in Tester1's multi-thread mode (presumably caused by deadlocks). FossilOrigin-Name: e209f56a9745695aadc04418c7bebe62b79e38e5aee26c3248a30f73bfa460c2 --- ext/jni/GNUmakefile | 2 +- ext/jni/src/c/sqlite3-jni.c | 402 +++++++++++------------- ext/jni/src/org/sqlite/jni/Tester1.java | 29 +- manifest | 16 +- manifest.uuid | 2 +- 5 files changed, 218 insertions(+), 233 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 0bb3bec441..de9204fd64 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -232,7 +232,7 @@ test: $(SQLite3Jni.class) $(sqlite3-jni.dll) test-mt: $(SQLite3Jni.class) $(sqlite3-jni.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ - org.sqlite.jni.Tester1 -t 4 -r 5 $(test.flags) + org.sqlite.jni.Tester1 -t 7 -r 50 $(test.flags) tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) tester.flags ?= # --verbose diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index d3fd0db82a..bce6a1a63c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -217,13 +217,14 @@ static inline void delete_local_ref(JNIEnv * const env, jobject const v){ */ typedef struct S3NphRef S3NphRef; struct S3NphRef { - const int index /* index into S3JniEnv->nph[] */; + const int index /* index into S3JniGlobal.nph[] */; const char * const zName /* Full Java name of the class */; }; /** - Keys for each concrete NativePointerHolder subclass. These are to - be used with S3JniGlobal_nph_cache() and friends. + Keys for each concrete NativePointerHolder subclass. These are to + be used with S3JniGlobal_nph_cache() and friends. These are + initialized on-demand by S3JniGlobal_nph_cache(). */ static const struct { const S3NphRef sqlite3; @@ -346,23 +347,18 @@ enum { */ typedef struct S3JniNphClass S3JniNphClass; struct S3JniNphClass { - const S3NphRef * pRef /* Entry from S3NphRefs. */; + volatile const S3NphRef * pRef /* Entry from S3NphRefs. */; jclass klazz /* global ref to the concrete NativePointerHolder subclass represented by zClassName */; - jmethodID midCtor /* klazz's no-arg constructor. Used by - new_NativePointerHolder_object(). */; - jfieldID fidValue /* NativePointerHolder.nativePointer and - OutputPointer.X.value */; - jfieldID fidSetAgg /* sqlite3_context::aggregateContext. Used only - by the sqlite3_context binding. */; + volatile jmethodID midCtor /* klazz's no-arg constructor. Used by + new_NativePointerHolder_object(). */; + volatile jfieldID fidValue /* NativePointerHolder.nativePointer or + OutputPointer.T.value */; + volatile jfieldID fidAggCtx /* sqlite3_context::aggregateContext. Used only + by the sqlite3_context binding. */; }; -static void S3JniNphClass_clear(JNIEnv * const env, S3JniNphClass * const p){ - UNREF_G(p->klazz); - memset(p, 0, sizeof(S3JniNphClass)); -} - /** State for various hook callbacks. */ typedef struct S3JniHook S3JniHook; struct S3JniHook{ @@ -444,14 +440,6 @@ struct S3JniEnv { S3JniDb * pdbOpening; S3JniEnv * pPrev /* Previous entry in the linked list */; S3JniEnv * pNext /* Next entry in the linked list */; - /* - ** 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]; }; /* @@ -480,10 +468,18 @@ struct S3JniGlobalType { ** JNIEnv when necessary. */ JavaVM * jvm; + /* Cache of Java refs/IDs for NativePointerHolder subclasses. + ** Initialized on demand. + */ + S3JniNphClass nph[NphCache_SIZE]; + /* + ** Cache of per-thread state. + */ struct { S3JniEnv * aHead /* Linked list of in-use instances */; S3JniEnv * aFree /* Linked list of free instances */; - sqlite3_mutex * mutex /* mutex for aHead and aFree */; + sqlite3_mutex * mutex /* mutex for aHead and aFree as well for + first-time inits of nph members. */; void const * locker /* env mutex is held on this object's behalf. Used only for sanity checking. */; } envCache; @@ -524,7 +520,11 @@ struct S3JniGlobalType { struct { volatile unsigned envCacheHits; volatile unsigned envCacheMisses; - volatile unsigned nMutexEnv /* number of times envCache.mutex was entered */; + volatile unsigned envCacheAllocs; + volatile unsigned nMutexEnv /* number of times envCache.mutex was entered for + a S3JniEnv operation. */; + volatile unsigned nMutexEnv2 /* number of times envCache.mutex was entered for + a S3JniNphClass operation. */; 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 */; @@ -553,39 +553,50 @@ struct S3JniGlobalType { static S3JniGlobalType S3JniGlobal = {}; #define SJG S3JniGlobal -#define MUTEX_ASSERT_LOCKER_ENV \ +#define MUTEX_ENV_ASSERT_LOCKER \ assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) -#define MUTEX_ASSERT_NOTLOCKER_ENV \ +#define MUTEX_ENV_ASSERT_NOTLOCKER \ assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) -#define MUTEX_ENV_ENTER \ - MUTEX_ASSERT_NOTLOCKER_ENV; \ - /*MARKER(("Entering ENV mutex@%p %s.\n", env, __func__));*/ \ - sqlite3_mutex_enter( SJG.envCache.mutex ); \ - ++SJG.metrics.nMutexEnv; \ +#define MUTEX_ENV_ENTER \ + MUTEX_ENV_ASSERT_NOTLOCKER; \ + /*MARKER(("Entering ENV mutex@%p %s.\n", env));*/ \ + 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; \ - SJG.envCache.locker = 0; \ +#define MUTEX_ENV_LEAVE \ + /*MARKER(("Leaving ENV mutex @%p %s.\n", env));*/ \ + MUTEX_ENV_ASSERT_LOCKER; \ + SJG.envCache.locker = 0; \ sqlite3_mutex_leave( SJG.envCache.mutex ) -#define MUTEX_ASSERT_LOCKED_PDB \ - 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( SJG.perDb.mutex ); \ - ++SJG.metrics.nMutexPerDb; \ - SJG.perDb.locker = env; -#define MUTEX_PDB_LEAVE \ - /*MARKER(("Leaving PerDb mutex@%p %s.\n", env, __func__));*/ \ - 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( SJG.autoExt.mutex ); \ +#define MUTEX_EXT_ENTER \ + /*MARKER(("Entering autoExt mutex@%p %s.\n", env));*/ \ + sqlite3_mutex_enter( SJG.autoExt.mutex ); \ ++SJG.metrics.nMutexAutoExt -#define MUTEX_EXT_LEAVE \ - /*MARKER(("Leaving autoExt mutex@%p %s.\n", env, __func__));*/ \ +#define MUTEX_EXT_LEAVE \ + /*MARKER(("Leaving autoExt mutex@%p %s.\n", env));*/ \ sqlite3_mutex_leave( SJG.autoExt.mutex ) +#define MUTEX_NPH_ENTER \ + MUTEX_ENV_ASSERT_NOTLOCKER; \ + /*MARKER(("Entering NPH mutex@%p %s.\n", env));*/ \ + sqlite3_mutex_enter( SJG.envCache.mutex ); \ + ++SJG.metrics.nMutexEnv2; \ + SJG.envCache.locker = env +#define MUTEX_NPH_LEAVE \ + /*MARKER(("Leaving NPH mutex @%p %s.\n", env));*/ \ + MUTEX_ENV_ASSERT_LOCKER; \ + SJG.envCache.locker = 0; \ + sqlite3_mutex_leave( SJG.envCache.mutex ) +#define MUTEX_PDB_ENTER \ + /*MARKER(("Entering PerDb mutex@%p %s.\n", 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));*/ \ + SJG.perDb.locker = 0; \ + sqlite3_mutex_leave( SJG.perDb.mutex ) +#define MUTEX_PDB_ASSERT_LOCKED \ + assert( 0 != SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) #define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) static void s3jni_oom(JNIEnv * const env){ @@ -629,6 +640,7 @@ static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){ if( row->pNext ) row->pNext->pPrev = 0; }else{ row = s3jni_malloc(env, sizeof(S3JniEnv)); + ++SJG.metrics.envCacheAllocs; } memset(row, 0, sizeof(*row)); row->pNext = SJG.envCache.aHead; @@ -847,7 +859,6 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ assert(klazz); } method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); - //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method)); if(method){ ++SJG.metrics.nDestroy; (*env)->CallVoidMethod(env, jObj, method); @@ -883,7 +894,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; - MUTEX_ASSERT_LOCKED_PDB; + MUTEX_PDB_ASSERT_LOCKED; //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); assert(s->pPrev != s); assert(s->pNext != s); @@ -949,8 +960,7 @@ static void S3JniDb_free_for_env(JNIEnv *env){ */ static int S3JniGlobal_env_uncache(JNIEnv * const env){ struct S3JniEnv * row; - int i; - MUTEX_ASSERT_LOCKER_ENV; + MUTEX_ENV_ASSERT_LOCKER; row = SJG.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ @@ -968,9 +978,6 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ SJG.envCache.aHead = row->pNext; } S3JniDb_free_for_env(env); - for( i = 0; i < NphCache_SIZE; ++i ){ - S3JniNphClass_clear(env, &row->nph[i]); - } memset(row, 0, sizeof(S3JniEnv)); row->pNext = SJG.envCache.aFree; if( row->pNext ) row->pNext->pPrev = row; @@ -1008,27 +1015,35 @@ static S3JniNphClass * S3JniGlobal_nph_cache(JNIEnv * const env, S3NphRef const* because all nph entries are per-thread and envCache.mutex already guards the fetching of envRow. */ - struct S3JniEnv * const envRow = S3JniGlobal_env_cache(env); - S3JniNphClass * pCache; - assert(envRow); - pCache = &envRow->nph[pRef->index]; - if( !pCache->pRef ){ - pCache->pRef = pRef; - pCache->klazz = (*env)->FindClass(env, pRef->zName); - EXCEPTION_IS_FATAL("FindClass() unexpectedly threw"); - pCache->klazz = REF_G(pCache->klazz); + S3JniNphClass * const pNC = &SJG.nph[pRef->index]; + if( !pNC->pRef ){ + MUTEX_NPH_ENTER; + if( !pNC->pRef ){ + pNC->pRef = pRef; + pNC->klazz = (*env)->FindClass(env, pRef->zName); + EXCEPTION_IS_FATAL("FindClass() unexpectedly threw"); + pNC->klazz = REF_G(pNC->klazz); + } + MUTEX_NPH_LEAVE; } - return pCache; + return pNC; } /** Returns the ID of the "nativePointer" field from the given NativePointerHolder class. - */ -static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ - jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J"); - EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field."); - return rv; +*/ +static jfieldID NativePointerHolder_getField(JNIEnv * const env, S3NphRef const* pRef){ + S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef); + if(!pNC->fidValue){ + MUTEX_NPH_ENTER; + if(!pNC->fidValue){ + pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz, "nativePointer", "J"); + EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field."); + } + MUTEX_NPH_LEAVE; + } + return pNC->fidValue; } /** @@ -1038,18 +1053,8 @@ static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ */ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, S3NphRef const* pRef){ - jfieldID setter = 0; - S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef); - if(pNC->fidValue){ - setter = pNC->fidValue; - assert(setter); - }else{ - jclass const klazz = pNC->klazz - ? pNC->klazz - : (pNC->klazz = (*env)->GetObjectClass(env, ppOut)); - setter = pNC->fidValue = NativePointerHolder_getField(env, klazz); - } - (*env)->SetLongField(env, ppOut, setter, (jlong)p); + (*env)->SetLongField(env, ppOut, NativePointerHolder_getField(env, pRef), + (jlong)p); EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer."); } @@ -1060,19 +1065,10 @@ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, */ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, S3NphRef const* pRef){ if( pObj ){ - jfieldID getter = 0; - void * rv = 0; - S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef); - if(pNC->fidValue){ - getter = pNC->fidValue; - }else{ - jclass const klazz = pNC->klazz - ? pNC->klazz - : (pNC->klazz = (*env)->GetObjectClass(env, pObj)); - getter = pNC->fidValue = NativePointerHolder_getField(env, klazz); - } - rv = (void*)(*env)->GetLongField(env, pObj, getter); - IFTHREW_REPORT; + void * rv = (void*)(*env)->GetLongField( + env, pObj, NativePointerHolder_getField(env, pRef) + ); + EXCEPTION_IS_FATAL("Cannot fetch NativePointerHolder.nativePointer."); return rv; }else{ return 0; @@ -1201,12 +1197,12 @@ static int S3JniAutoExtension_init(JNIEnv *const env, } ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", "(Lorg/sqlite/jni/sqlite3;)I"); + //UNREF_L(klazz); if(!ax->midFunc){ MARKER(("Error getting xEntryPoint(sqlite3) from object.")); S3JniAutoExtension_clear(env, ax); return SQLITE_ERROR; } - UNREF_L(klazz); ax->jObj = REF_G(jAutoExt); return 0; } @@ -1232,34 +1228,22 @@ static int S3JniAutoExtension_init(JNIEnv *const env, static int udf_setAggregateContext(JNIEnv * env, jobject jCx, sqlite3_context * pCx, int isFinal){ - jfieldID member; void * pAgg; int rc = 0; - S3JniNphClass * const pCache = + S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, &S3NphRefs.sqlite3_context); - if(pCache && pCache->klazz && pCache->fidSetAgg){ - member = pCache->fidSetAgg; - assert(member); - }else{ - jclass const klazz = - pCache ? pCache->klazz : (*env)->GetObjectClass(env, jCx); - member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J"); - if( !member ){ - IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; } - return s3jni_db_error(sqlite3_context_db_handle(pCx), - SQLITE_ERROR, - "Internal error: cannot find " - "sqlite3_context::aggregateContext field."); - } - if(pCache){ - assert(pCache->klazz); - assert(!pCache->fidSetAgg); - pCache->fidSetAgg = member; + if(!pNC->fidAggCtx){ + MUTEX_NPH_ENTER; + if(!pNC->fidAggCtx){ + pNC->fidAggCtx = (*env)->GetFieldID(env, pNC->klazz, + "aggregateContext", "J"); + EXCEPTION_IS_FATAL("Cannot get sqlite3_contex.aggregateContext member."); } + MUTEX_NPH_LEAVE; } - pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4); + pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : sizeof(void*)); if( pAgg || isFinal ){ - (*env)->SetLongField(env, jCx, member, (jlong)pAgg); + (*env)->SetLongField(env, jCx, pNC->fidAggCtx, (jlong)pAgg); }else{ assert(!pAgg); rc = SQLITE_NOMEM; @@ -1268,42 +1252,39 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, } /** - Common init for OutputPointer_set_Int32() and friends. zClassName must be a - pointer from S3NphRefs. jOut must be an instance of that - class. Fetches the jfieldID for jOut's [value] property, which must - be of the type represented by the JNI type signature zTypeSig, and - stores it in pFieldId. Fails fatally if the property is not found, - as that presents a serious internal misuse. + Common init for OutputPointer_set_Int32() and friends. pRef must be + a pointer from S3NphRefs. jOut must be an instance of that + class. If necessary, this fetches the jfieldID for jOut's [value] + property, which must be of the type represented by the JNI type + signature zTypeSig, and stores it in pRef's S3JniGlobal.nph entry. + Fails fatally if the property is not found, as that presents a + serious internal misuse. - Property lookups are cached on a per-zClassName basis. Do not use - this routine with the same zClassName but different zTypeSig: it - will misbehave. + Property lookups are cached on a per-pRef basis. Do not use this + routine with the same pRef but different zTypeSig: it will + misbehave. */ -static void setupOutputPointer(JNIEnv * const env, S3NphRef const * pRef, - const char * const zTypeSig, - jobject const jOut, jfieldID * const pFieldId){ - jfieldID setter = 0; - S3JniNphClass * const pCache = S3JniGlobal_nph_cache(env, pRef); - if(pCache && pCache->klazz && pCache->fidValue){ - setter = pCache->fidValue; - }else{ - const jclass klazz = (*env)->GetObjectClass(env, jOut); - /*MARKER(("%s => %s\n", zClassName, zTypeSig));*/ - setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig); - EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); - if(pCache){ - assert(!pCache->fidValue); - pCache->fidValue = setter; +static jfieldID setupOutputPointer(JNIEnv * const env, S3NphRef const * pRef, + const char * const zTypeSig, + jobject const jOut){ + S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef); + if(!pNC->fidValue){ + MUTEX_NPH_ENTER; + if(!pNC->fidValue){ + pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz, "value", zTypeSig); + EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); } + MUTEX_NPH_LEAVE; } - *pFieldId = setter; + return pNC->fidValue; } /* Sets the value property of the OutputPointer.Int32 jOut object to v. */ static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){ - jfieldID setter = 0; - setupOutputPointer(env, &S3NphRefs.OutputPointer_Int32, "I", jOut, &setter); + jfieldID const setter = setupOutputPointer( + env, &S3NphRefs.OutputPointer_Int32, "I", jOut + ); (*env)->SetIntField(env, jOut, setter, (jint)v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } @@ -1311,26 +1292,28 @@ static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int /* Sets the value property of the OutputPointer.Int64 jOut object to v. */ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ - jfieldID setter = 0; - setupOutputPointer(env, &S3NphRefs.OutputPointer_Int64, "J", jOut, &setter); + jfieldID const setter = setupOutputPointer( + env, &S3NphRefs.OutputPointer_Int64, "J", jOut + ); (*env)->SetLongField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); } static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, jobject jDb){ - jfieldID setter = 0; - setupOutputPointer(env, &S3NphRefs.OutputPointer_sqlite3, - "Lorg/sqlite/jni/sqlite3;", jOut, &setter); + jfieldID const setter = setupOutputPointer( + env, &S3NphRefs.OutputPointer_sqlite3, "Lorg/sqlite/jni/sqlite3;", jOut + ); (*env)->SetObjectField(env, jOut, setter, jDb); EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value"); } static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, jobject jStmt){ - jfieldID setter = 0; - setupOutputPointer(env, &S3NphRefs.OutputPointer_sqlite3_stmt, - "Lorg/sqlite/jni/sqlite3_stmt;", jOut, &setter); + jfieldID const setter = setupOutputPointer( + env, &S3NphRefs.OutputPointer_sqlite3_stmt, + "Lorg/sqlite/jni/sqlite3_stmt;", jOut + ); (*env)->SetObjectField(env, jOut, setter, jStmt); EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value"); } @@ -1341,9 +1324,9 @@ static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOu to v. */ static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, jbyteArray const v){ - jfieldID setter = 0; - setupOutputPointer(env, &S3NphRefs.OutputPointer_ByteArray, "[B", - jOut, &setter); + jfieldID const setter = setupOutputPointer( + env, &S3NphRefs.OutputPointer_ByteArray, "[B", jOut + ); (*env)->SetObjectField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); } @@ -1353,9 +1336,9 @@ static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, to v. */ static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, jstring const v){ - jfieldID setter = 0; - setupOutputPointer(env, &S3NphRefs.OutputPointer_String, - "Ljava/lang/String;", jOut, &setter); + jfieldID const setter = setupOutputPointer( + env, &S3NphRefs.OutputPointer_String, "Ljava/lang/String;", jOut + ); (*env)->SetObjectField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); } @@ -1486,28 +1469,16 @@ static void ResultJavaVal_finalizer(void *v){ static jobject new_NativePointerHolder_object(JNIEnv * const env, S3NphRef const * pRef, const void * pNative){ jobject rv = 0; - jclass klazz = 0; - jmethodID ctor = 0; - S3JniNphClass * const pCache = S3JniGlobal_nph_cache(env, pRef); - if(pCache->midCtor){ - assert( pCache->klazz ); - klazz = pCache->klazz; - ctor = pCache->midCtor; - }else{ - klazz = pCache - ? pCache->klazz - : (*env)->FindClass(env, pRef->zName); - ctor = klazz ? (*env)->GetMethodID(env, klazz, "", "()V") : 0; - EXCEPTION_IS_FATAL("Cannot find constructor for class."); - if(pCache){ - assert(pCache->klazz); - assert(!pCache->midCtor); - pCache->midCtor = ctor; + S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef); + if(!pNC->midCtor){ + MUTEX_NPH_ENTER; + if(!pNC->midCtor){ + pNC->midCtor = (*env)->GetMethodID(env, pNC->klazz, "", "()V"); + EXCEPTION_IS_FATAL("Cannot find constructor for class."); } + MUTEX_NPH_LEAVE; } - assert(klazz); - assert(ctor); - rv = (*env)->NewObject(env, klazz, ctor); + rv = (*env)->NewObject(env, pNC->klazz, pNC->midCtor); EXCEPTION_IS_FATAL("No-arg constructor threw."); OOM_CHECK(rv); if(rv) NativePointerHolder_set(env, rv, pNative, pRef); @@ -1819,35 +1790,45 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, env = s3jni_get_env(); jc = S3JniGlobal_env_cache(env); ps = jc->pdbOpening; + if( !ps ){ + MARKER(("Unexpected arrival of null S3JniDb in auto-extension runner.\n")); + *pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in auto-extension runner."); + return SQLITE_ERROR; + } jc->pdbOpening = 0; - assert( ps && "Unexpected arrival of null S3JniDb in auto-extension runner." ); //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); assert( !ps->pDb && "it's still being opened" ); ps->pDb = pDb; assert( ps->jDb ); NativePointerHolder_set(env, ps->jDb, pDb, &S3NphRefs.sqlite3); for( i = 0; go && 0==rc; ++i ){ - S3JniAutoExtension const * ax; + S3JniAutoExtension ax = {0,0} + /* We need a copy of the auto-extension object, with our own + ** local reference to it, to avoid a race condition with another + ** thread manipulating the list during the call and invaliding + ** what ax points to. */; MUTEX_EXT_ENTER; if( i >= SJG.autoExt.nExt ){ - ax = 0; go = 0; }else{ - ax = &SJG.autoExt.pExt[i]; + ax.jObj = REF_L(SJG.autoExt.pExt[i].jObj); + ax.midFunc = SJG.autoExt.pExt[i].midFunc; } MUTEX_EXT_LEAVE; - if( ax && ax->jObj ){ - rc = (*env)->CallIntMethod(env, ax->jObj, ax->midFunc, ps->jDb); + if( ax.jObj ){ + //MARKER(("Running auto-ext #%d from env@%p\n", i, env)); + rc = (*env)->CallIntMethod(env, ax.jObj, ax.midFunc, ps->jDb); IFTHREW { jthrowable const ex = (*env)->ExceptionOccurred(env); char * zMsg; EXCEPTION_CLEAR; zMsg = s3jni_exception_error_msg(env, ex); - UNREF_L(ex); + //UNREF_L(ex); *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); sqlite3_free(zMsg); if( !rc ) rc = SQLITE_ERROR; } + UNREF_L(ax.jObj); } } return rc; @@ -2580,11 +2561,12 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); } -//! Pre-open() code common to sqlite3_open(_v2)(). +/* Pre-open() code common to sqlite3_open(_v2)(). */ static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, jstring jDbName, char **zDbName, - S3JniDb ** ps, jobject *jDb){ + S3JniDb ** ps){ int rc = 0; + jobject jDb = 0; *jc = S3JniGlobal_env_cache(env); if(!*jc){ rc = SQLITE_NOMEM; @@ -2595,20 +2577,20 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, rc = SQLITE_NOMEM; goto end; } - *jDb = new_sqlite3_wrapper(env, 0); - if( !*jDb ){ + jDb = new_sqlite3_wrapper(env, 0); + if( !jDb ){ sqlite3_free(*zDbName); *zDbName = 0; rc = SQLITE_NOMEM; goto end; } - *ps = S3JniDb_alloc(env, 0, *jDb); + *ps = S3JniDb_alloc(env, 0, jDb); if(*ps){ (*jc)->pdbOpening = *ps; }else{ + UNREF_L(jDb); rc = SQLITE_NOMEM; } - //MARKER(("pre-open ps@%p\n", *ps)); end: return rc; } @@ -2627,7 +2609,6 @@ end: static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc, S3JniDb * ps, sqlite3 **ppDb, jobject jOut, int theRc){ - //MARKER(("post-open() ps@%p db@%p\n", ps, *ppDb)); jc->pdbOpening = 0; if(*ppDb){ assert(ps->jDb); @@ -2650,14 +2631,12 @@ static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc, JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ sqlite3 * pOut = 0; char *zName = 0; - jobject jDb = 0; S3JniDb * ps = 0; S3JniEnv * jc = 0; - int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + int rc; + rc = s3jni_open_pre(env, &jc, strName, &zName, &ps); if( 0==rc ){ rc = sqlite3_open(zName, &pOut); - //MARKER(("env=%p, *env=%p\n", env, *env)); - //MARKER(("open() ps@%p db@%p\n", ps, pOut)); rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc); assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); @@ -2669,11 +2648,10 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, jobject jOut, jint flags, jstring strVfs){ sqlite3 * pOut = 0; char *zName = 0; - jobject jDb = 0; S3JniDb * ps = 0; S3JniEnv * jc = 0; char *zVfs = 0; - int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps); if( 0==rc && strVfs ){ zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); if( !zVfs ){ @@ -2683,9 +2661,6 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, if( 0==rc ){ rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); } - //MARKER(("open_v2() ps@%p db@%p\n", ps, pOut)); - /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", - zName, zVfs, pOut, (int)flags, nrc));*/ rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc); assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); @@ -3437,13 +3412,17 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ SO(S3JniAutoExtension); SO(S3JniUdf); printf("Cache info:\n"); - printf("\tJNIEnv cache %u misses, %u hits\n", + printf("\tJNIEnv cache: %u allocs, %u misses, %u hits\n", + SJG.metrics.envCacheAllocs, SJG.metrics.envCacheMisses, SJG.metrics.envCacheHits); - printf("Mutex entry:\n\t%u env\n\t%u perDb\n\t%u autoExt\n", - SJG.metrics.nMutexEnv, - SJG.metrics.nMutexPerDb, - SJG.metrics.nMutexAutoExt); + printf("Mutex entry:" + "\n\tenv %u" + "\n\tnph inits %u" + "\n\tperDb %u" + "\n\tautoExt %u container access\n", + SJG.metrics.nMutexEnv, SJG.metrics.nMutexEnv2, + SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt); 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); @@ -4397,6 +4376,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ 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); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 96e1da1b05..1543a80052 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -37,8 +37,8 @@ public class Tester1 implements Runnable { static final Metrics metrics = new Metrics(); - public synchronized static void out(Object val){ - System.out.print(val); + public synchronized static void outln(){ + System.out.println(""); } public synchronized static void outln(Object val){ @@ -46,11 +46,14 @@ public class Tester1 implements Runnable { System.out.println(val); } + public synchronized static void out(Object val){ + System.out.print(val); + } + @SuppressWarnings("unchecked") public synchronized static void out(Object... vals){ - int n = 0; System.out.print(Thread.currentThread().getName()+": "); - for(Object v : vals) out((n++>0 ? " " : "")+v); + for(Object v : vals) out(v); } @SuppressWarnings("unchecked") @@ -211,7 +214,7 @@ public class Tester1 implements Runnable { affirm(0 != stmt.getNativePointer()); rc = sqlite3_step(stmt); if( SQLITE_DONE != rc ){ - outln("step failed ??? ",rc, sqlite3_errmsg(db)); + outln("step failed ??? ",rc, " ",sqlite3_errmsg(db)); } affirm(SQLITE_DONE == rc); sqlite3_finalize(stmt); @@ -741,7 +744,7 @@ public class Tester1 implements Runnable { outln("Bound constants:\n"); for(java.lang.reflect.Field field : declaredFields) { if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) { - outln("\t"+field.getName()); + outln("\t",field.getName()); } } } @@ -760,9 +763,9 @@ public class Tester1 implements Runnable { java.util.Collections.sort(funcList); for(String n : funcList){ ++count; - outln("\t"+n+"()"); + outln("\t",n,"()"); } - outln(count+" functions named sqlite3_*."); + outln(count," functions named sqlite3_*."); } private void testTrace(){ @@ -1232,9 +1235,10 @@ public class Tester1 implements Runnable { final long timeStart = System.currentTimeMillis(); int nLoop = 0; - outln("libversion_number:", + outln("libversion_number: ", sqlite3_libversion_number(),"\n", sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); + outln("Running ",nRepeat," loop(s) over ",nThread," thread(s)."); for( int n = 0; n < nRepeat; ++n ){ if( nThread==null || nThread<=1 ){ new Tester1(0).runTests(false); @@ -1243,7 +1247,7 @@ public class Tester1 implements Runnable { final ExecutorService ex = Executors.newFixedThreadPool( nThread ); //final List> futures = new ArrayList<>(); ++nLoop; - outln("Running loop #",nLoop," over ",nThread," threads."); + out(nLoop+" "); for( int i = 0; i < nThread; ++i ){ ex.submit( new Tester1(i), i ); } @@ -1257,11 +1261,12 @@ public class Tester1 implements Runnable { } } } + outln(); final long timeEnd = System.currentTimeMillis(); outln("Tests done. Metrics:"); - outln("\tAssertions checked: "+affirmCount); - outln("\tDatabases opened: "+metrics.dbOpen); + outln("\tAssertions checked: ",affirmCount); + outln("\tDatabases opened: ",metrics.dbOpen); if( doSomethingForDev ){ sqlite3_do_something_for_developer(); } diff --git a/manifest b/manifest index 62da389266..105f01ad91 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -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 +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 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,10 +232,10 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 4849b0ac41c3a92777aebf0ec3d51c2be7c78d3ea9b91ece03ade6f9fa13d99a +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 96252788a8eea13e8da997103c05b8c1d2c878eb2c5a71a123baeb2bd5e91027 +F ext/jni/src/c/sqlite3-jni.c ad002976687e294936ce8f50b9efb1e531fa1d78a076b8a09089328082b48af4 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 @@ -256,7 +256,7 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c 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 a3eef17c60b4770c6fcf70d6efc2273e74470f7c8067c0748d12f393bf260d34 +F ext/jni/src/org/sqlite/jni/Tester1.java 63e1e4285a0f050580490323f656809bdadc0f1f28c4454f5cca82a6ccdfaf0f 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 b88910aaaaaaa0936974379bb3eb8a5a3a634395b14e67cc9030f8a520f471f1 -R 76968b303e9bd6c480af577a480bf905 +P 7342bf578790e1a87c128a7c1c7745fe2e7c442890370feb160d406597d4d8ec +R 2848048c8acd75d9850655e629905695 U stephan -Z edf907aa4738f3b50b53a088953efc54 +Z 76a56828e8164766c79107d6b6e04f3f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ee5d057164..4a981ac71b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7342bf578790e1a87c128a7c1c7745fe2e7c442890370feb160d406597d4d8ec \ No newline at end of file +e209f56a9745695aadc04418c7bebe62b79e38e5aee26c3248a30f73bfa460c2 \ No newline at end of file From a7e3a1c09b9d0d6a8f4da623012d3dae0d8f64a7 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 22 Aug 2023 17:36:59 +0000 Subject: [PATCH 23/37] JNI internal cleanups and correct two leaked db handles in test code. FossilOrigin-Name: f927a30b5bba35991f472084ebaf02779e84c343a4e84f0efb3df7679ff212f8 --- ext/jni/src/c/sqlite3-jni.c | 178 +++++++----------- ext/jni/src/org/sqlite/jni/AutoExtension.java | 17 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 37 ++-- ext/jni/src/org/sqlite/jni/Tester1.java | 18 +- manifest | 18 +- manifest.uuid | 2 +- 6 files changed, 122 insertions(+), 148 deletions(-) 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 From c675add616c98422b3f0ec7f945fddd27a6a1afe Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 22 Aug 2023 17:51:57 +0000 Subject: [PATCH 24/37] Correct JNI binding of sqlite3_shutdown() to clean up all cached JNIEnv objects. FossilOrigin-Name: 02e868690f97ca728b0f2dd018aa79a9d13c85dd85b164caa895d319ae8f3ff5 --- ext/jni/GNUmakefile | 2 +- ext/jni/src/c/sqlite3-jni.c | 17 ++--------------- ext/jni/src/org/sqlite/jni/Tester1.java | 1 + manifest | 16 ++++++++-------- manifest.uuid | 2 +- 5 files changed, 13 insertions(+), 25 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index de9204fd64..788c072062 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -268,7 +268,7 @@ endif tester-ext: tester-local tester: tester-ext -tests: test tester test-mt +tests: test test-mt tester package.jar.in := $(abspath $(dir.src)/jar.in) CLEAN_FILES += $(package.jar.in) $(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 8ae78ea3d8..fc96bdd384 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -376,7 +376,7 @@ struct S3JniHook{ /* ** Per-(sqlite3*) state for various JNI bindings. This state is ** allocated as needed, cleaned up in sqlite3_close(_v2)(), and -** recycled when possible. It is freed during sqlite3_shutdown(). +** recycled when possible. */ typedef struct S3JniDb S3JniDb; struct S3JniDb { @@ -1136,19 +1136,6 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, return rv; } -#if 0 -static void S3JniDb_dump(S3JniDb *s){ - MARKER(("S3JniDb->env @ %p\n", s->env)); - MARKER(("S3JniDb->pDb @ %p\n", s->pDb)); - MARKER(("S3JniDb->trace.jObj @ %p\n", s->trace.jObj)); - MARKER(("S3JniDb->progress.jObj @ %p\n", s->progress.jObj)); - MARKER(("S3JniDb->commitHook.jObj @ %p\n", s->commitHook.jObj)); - MARKER(("S3JniDb->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); - MARKER(("S3JniDb->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); - MARKER(("S3JniDb->env @ %p\n", s->env)); -} -#endif - /** Returns the S3JniDb object for the given db. @@ -3079,7 +3066,7 @@ JDECL(jint,1shutdown)(JENV_CSELF){ s3jni_reset_auto_extension(env); MUTEX_ENV_ENTER; while( SJG.envCache.aHead ){ - S3JniGlobal_env_uncache( env );//SJG.envCache.aHead->env ); + S3JniGlobal_env_uncache( SJG.envCache.aHead->env ); } MUTEX_ENV_LEAVE; /* Do not clear S3JniGlobal.jvm: it's legal to call diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index fe8bc542c6..9677ca57d1 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -1270,6 +1270,7 @@ public class Tester1 implements Runnable { if( doSomethingForDev ){ sqlite3_do_something_for_developer(); } + sqlite3_shutdown(); int nMethods = 0; int nNatives = 0; final java.lang.reflect.Method[] declaredMethods = diff --git a/manifest b/manifest index 94ab0dbcd7..39fe198d94 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C JNI\sinternal\scleanups\sand\scorrect\stwo\sleaked\sdb\shandles\sin\stest\scode. -D 2023-08-22T17:36:59.339 +C Correct\sJNI\sbinding\sof\ssqlite3_shutdown()\sto\sclean\sup\sall\scached\sJNIEnv\sobjects. +D 2023-08-22T17:51:57.423 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,10 +232,10 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 30f0926a69edbd9e9932283ec8e4cea02b785f373395f2093dbbc6d65866a196 +F ext/jni/GNUmakefile 1ccd09095447709ffd7a4f32514fd586512491c6bed06d009bab4294b451ed62 F ext/jni/README.md 975b35173debbbf3a4ab7166e14d2ffa2bacff9b6850414f09cc919805e81ba4 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c b54056176060ef68dba31baaee43ad90a8ac3d4e7a477224377026110bb213ac +F ext/jni/src/c/sqlite3-jni.c fb2ca8b6f846632b3432096d5533bef8251965dd5ed9f490441293da641a3fb5 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 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd @@ -256,7 +256,7 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c 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 0eea21f1015704e495b1a47aa9c7c90d081f51777981fe3f07760486aed092d8 -F ext/jni/src/org/sqlite/jni/Tester1.java b6be63a8e80c7362073f2a799719315a1459d1eff97cebb944b1309522758de2 +F ext/jni/src/org/sqlite/jni/Tester1.java e83a5635878cf73b463014b5137ce5c057ee9c5f6d67fbb40496894552785f46 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 e209f56a9745695aadc04418c7bebe62b79e38e5aee26c3248a30f73bfa460c2 -R 187f43ad9bb697fc679503d683587e56 +P f927a30b5bba35991f472084ebaf02779e84c343a4e84f0efb3df7679ff212f8 +R fd18badbeb7e1f743addadd7405d6449 U stephan -Z 1a62043be3662d747eb273c051b3d9b0 +Z 7d01e74d30abc2fec1f43939a768f2c1 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 296aadaa87..3da3391a9e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f927a30b5bba35991f472084ebaf02779e84c343a4e84f0efb3df7679ff212f8 \ No newline at end of file +02e868690f97ca728b0f2dd018aa79a9d13c85dd85b164caa895d319ae8f3ff5 \ No newline at end of file From 87bb103038cc4eee800f9f75f9bcb616a752ce8d Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 22 Aug 2023 18:36:30 +0000 Subject: [PATCH 25/37] Disassociate JNI db handles from the thread that created them, as it's no longer relevant. FossilOrigin-Name: 8b78b737e66a399b04e555a8197f63a73198a4105cb2f37ffd5b0e6014302caf --- ext/jni/src/c/sqlite3-jni.c | 39 ++------------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 20 +++----- ext/jni/src/org/sqlite/jni/Tester1.java | 55 +++++++++++++--------- manifest | 16 +++---- manifest.uuid | 2 +- 5 files changed, 52 insertions(+), 80 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index fc96bdd384..47269824b5 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -380,10 +380,6 @@ struct S3JniHook{ */ typedef struct S3JniDb S3JniDb; struct S3JniDb { - 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 @@ -558,6 +554,9 @@ struct S3JniGlobalType { static S3JniGlobalType S3JniGlobal = {}; #define SJG S3JniGlobal +/* Helpers for working with specific mutexes. */ +#define MUTEX_ENV_ASSERT_LOCKED \ + assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) #define MUTEX_ENV_ASSERT_LOCKER \ assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) #define MUTEX_ENV_ASSERT_NOTLOCKER \ @@ -604,7 +603,7 @@ static S3JniGlobalType S3JniGlobal = {}; assert( 0 != SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) #define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) -static void s3jni_oom(JNIEnv * const env){ +static inline void s3jni_oom(JNIEnv * const env){ (*env)->FatalError(env, "Out of memory.") /* does not return */; } @@ -941,41 +940,15 @@ static void S3JniDb_set_aside(S3JniDb * const s){ //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); } } -/** - Cleans up all state in S3JniGlobal.perDb for th given JNIEnv. - Results are undefined if a Java-side db uses the API - from the given JNIEnv after this call. -*/ -static void S3JniDb_free_for_env(JNIEnv *env){ - S3JniDb * ps; - S3JniDb * pNext = 0; - MUTEX_PDB_ENTER; - ps = SJG.perDb.aUsed; - for( ; ps; ps = pNext ){ - pNext = ps->pNext; - if(ps->env == env){ -#ifndef NDEBUG - S3JniDb * const pPrev = ps->pPrev; -#endif - S3JniDb_set_aside(ps); - assert( pPrev ? pPrev->pNext==pNext : 1 ); - assert( ps == SJG.perDb.aFree ); - } - } - MUTEX_PDB_LEAVE; -} /** Uncache any state for the given JNIEnv, clearing all Java references the cache owns. Returns true if env was cached and false if it was not found in the cache. - - Also passes env to S3JniDb_free_for_env() to free up - what would otherwise be stale references. */ static int S3JniGlobal_env_uncache(JNIEnv * const env){ struct S3JniEnv * row; - MUTEX_ENV_ASSERT_LOCKER; + MUTEX_ENV_ASSERT_LOCKED; row = SJG.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ @@ -992,7 +965,6 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ assert( !row->pPrev ); SJG.envCache.aHead = row->pNext; } - S3JniDb_free_for_env(env); memset(row, 0, sizeof(S3JniEnv)); row->pNext = SJG.envCache.aFree; if( row->pNext ) row->pNext->pPrev = row; @@ -1130,7 +1102,6 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, } rv->jDb = REF_G(jDb); rv->pDb = pDb; - rv->env = env; } MUTEX_PDB_LEAVE; return rv; diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index cfca4fc77c..22178faed6 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -122,21 +122,13 @@ public final class SQLite3Jni { uncacheJniEnv() when it is done with the library - either right before it terminates or when it is finished using the SQLite API. This will clean up any cached per-JNIEnv info. Calling into the - library again after that "should" re-initialize the cache on - demand, but that's untested. + library will re-initialize the cache on demand. - This call forcibly wipes out all cached information for the - current JNIEnv, a side-effect of which is that behavior is - undefined if any database objects are (A) still active at the - time it is called _and_ (B) calls are subsequently made into the - library with such a database. Doing so will, at best, lead to a - crash. At worst, it will lead to the db possibly misbehaving - because some of its Java-bound state has been cleared. There is - no immediate harm in (A) so long as condition (B) is not met. - This process does _not_ actually close any databases or finalize - any prepared statements. For proper library behavior, and to - avoid C-side leaks, be sure to close them before calling this - function. + This process does _not_ close any databases or finalize + any prepared statements because their ownership does not depend on + a given thread. For proper library behavior, and to + avoid C-side leaks, be sure to finalize all statements and close + all databases before calling this function. Calling this from the main application thread is not strictly required but is "polite." Additional threads must call this diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 9677ca57d1..e010e24703 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -24,6 +24,7 @@ import java.util.concurrent.Future; public class Tester1 implements Runnable { //! True when running in multi-threaded mode. private static boolean mtMode = false; + private static boolean takeNaps = false; private static final class Metrics { int dbOpen; @@ -1167,32 +1168,38 @@ public class Tester1 implements Runnable { outln("Woke up."); } + private void nap() throws InterruptedException { + if( takeNaps ){ + Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 28), 0); + } + } + private void runTests(boolean fromThread) throws Exception { if(false) testCompileOption(); testToUtf8(); test1(); - testOpenDb1(); - testOpenDb2(); - testCollation(); - testPrepare123(); - testBindFetchInt(); - testBindFetchInt64(); - testBindFetchDouble(); - testBindFetchText(); - testBindFetchBlob(); - testSql(); - testStatus(); - testUdf1(); - testUdfJavaObject(); - testUdfAggregate(); - testUdfWindow(); - testTrace(); - testProgress(); - testCommitHook(); - testRollbackHook(); - testUpdateHook(); - testAuthorizer(); - testAutoExtension(); + nap(); testOpenDb1(); + nap(); testOpenDb2(); + nap(); testCollation(); + nap(); testPrepare123(); + nap(); testBindFetchInt(); + nap(); testBindFetchInt64(); + nap(); testBindFetchDouble(); + nap(); testBindFetchText(); + nap(); testBindFetchBlob(); + nap(); testSql(); + nap(); testStatus(); + nap(); testUdf1(); + nap(); testUdfJavaObject(); + nap(); testUdfAggregate(); + nap(); testUdfWindow(); + nap(); testTrace(); + nap(); testProgress(); + nap(); testCommitHook(); + nap(); testRollbackHook(); + nap(); testUpdateHook(); + nap(); testAuthorizer(); + nap(); testAutoExtension(); if(!fromThread){ testBusy(); if( !mtMode ){ @@ -1227,6 +1234,8 @@ public class Tester1 implements Runnable { nThread = Integer.parseInt(args[i++]); }else if(arg.equals("r") || arg.equals("runs")){ nRepeat = Integer.parseInt(args[i++]); + }else if(arg.equals("naps")){ + takeNaps = true; }else{ throw new IllegalArgumentException("Unhandled flag:"+arg); } @@ -1247,7 +1256,7 @@ public class Tester1 implements Runnable { final ExecutorService ex = Executors.newFixedThreadPool( nThread ); //final List> futures = new ArrayList<>(); ++nLoop; - out(nLoop+" "); + out((1==nLoop ? "" : " ")+nLoop); for( int i = 0; i < nThread; ++i ){ ex.submit( new Tester1(i), i ); } diff --git a/manifest b/manifest index 39fe198d94..71e4b52cfe 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Correct\sJNI\sbinding\sof\ssqlite3_shutdown()\sto\sclean\sup\sall\scached\sJNIEnv\sobjects. -D 2023-08-22T17:51:57.423 +C Disassociate\sJNI\sdb\shandles\sfrom\sthe\sthread\sthat\screated\sthem,\sas\sit's\sno\slonger\srelevant. +D 2023-08-22T18:36:30.981 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 1ccd09095447709ffd7a4f32514fd586512491c6bed06d009bab4294b451ed62 F ext/jni/README.md 975b35173debbbf3a4ab7166e14d2ffa2bacff9b6850414f09cc919805e81ba4 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c fb2ca8b6f846632b3432096d5533bef8251965dd5ed9f490441293da641a3fb5 +F ext/jni/src/c/sqlite3-jni.c 50edc462e8fdf54f9b8ede692a7c865c5e4315930899276664dd6744764d4723 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 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd @@ -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 0eea21f1015704e495b1a47aa9c7c90d081f51777981fe3f07760486aed092d8 -F ext/jni/src/org/sqlite/jni/Tester1.java e83a5635878cf73b463014b5137ce5c057ee9c5f6d67fbb40496894552785f46 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2f36370cfdec01d309720392b2c3e4af6afce0b6ece8188b5c3ed688a5a1e63a +F ext/jni/src/org/sqlite/jni/Tester1.java 58a058f718215ff32fbdf8026a2d4eb88f9d7e939a5640d5a944efafdfda4b7c 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 f927a30b5bba35991f472084ebaf02779e84c343a4e84f0efb3df7679ff212f8 -R fd18badbeb7e1f743addadd7405d6449 +P 02e868690f97ca728b0f2dd018aa79a9d13c85dd85b164caa895d319ae8f3ff5 +R 4fc9fbe1829e4bd8b0e17e9933291453 U stephan -Z 7d01e74d30abc2fec1f43939a768f2c1 +Z 9fda786e7e193bbf92304385fbc27c96 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3da3391a9e..cfa2599e0d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -02e868690f97ca728b0f2dd018aa79a9d13c85dd85b164caa895d319ae8f3ff5 \ No newline at end of file +8b78b737e66a399b04e555a8197f63a73198a4105cb2f37ffd5b0e6014302caf \ No newline at end of file From d1c7216b2f5ac4011780d234df7566d250cf57dc Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 22 Aug 2023 20:10:28 +0000 Subject: [PATCH 26/37] More work on the JNI multi-threaded test runner. FossilOrigin-Name: 9a74ad716bded1e14333bf7c72392916f800d58a96240eabe4988ca5fc9e8752 --- ext/jni/src/c/sqlite3-jni.c | 24 ++-- .../org/sqlite/jni/NativePointerHolder.java | 2 +- ext/jni/src/org/sqlite/jni/Tester1.java | 107 +++++++++++++----- manifest | 16 +-- manifest.uuid | 2 +- 5 files changed, 101 insertions(+), 50 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 47269824b5..2299a4c6c0 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -599,8 +599,6 @@ static S3JniGlobalType S3JniGlobal = {}; /*MARKER(("Leaving PerDb mutex@%p %s.\n", env));*/ \ SJG.perDb.locker = 0; \ sqlite3_mutex_leave( SJG.perDb.mutex ) -#define MUTEX_PDB_ASSERT_LOCKED \ - assert( 0 != SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) #define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) static inline void s3jni_oom(JNIEnv * const env){ @@ -903,12 +901,12 @@ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDest } /** - Clears s's state and moves it to the free-list. + Clears s's state and moves it to the free-list. Requires that + S3JniGlobal.perDb.mutex be unlocked. */ -static void S3JniDb_set_aside(S3JniDb * const s){ +static void S3JniDb_set_aside(JNIEnv * env, S3JniDb * const s){ if(s){ - LocalJniGetEnv; - MUTEX_PDB_ASSERT_LOCKED; + MUTEX_PDB_ENTER; //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); assert(s->pPrev != s); assert(s->pNext != s); @@ -938,6 +936,7 @@ static void S3JniDb_set_aside(S3JniDb * const 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)); + MUTEX_PDB_LEAVE; } } @@ -1962,10 +1961,8 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ if(ps){ rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); if( 0==rc ){ - MUTEX_PDB_ENTER; - S3JniDb_set_aside(ps) + S3JniDb_set_aside(env, ps) /* MUST come after close() because of ps->trace. */; - MUTEX_PDB_LEAVE; NativePointerHolder_set(env, jDb, 0, &S3NphRefs.sqlite3); } } @@ -2537,9 +2534,7 @@ static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc, assert( ps->pDb == *ppDb /* set up via s3jni_run_java_auto_extensions() */); } }else{ - MUTEX_PDB_ENTER; - S3JniDb_set_aside(ps); - MUTEX_PDB_LEAVE; + S3JniDb_set_aside(env, ps); ps = 0; } OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); @@ -3306,7 +3301,8 @@ JDECL(jbyteArray,1value_1text16be)(JENV_CSELF, jobject jpSVal){ } JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ - MARKER(("\nVarious bits of internal info:\n")); + MARKER(("\nVarious bits of internal info:\n" + "Any metrics here are invalid in multi-thread use.\n")); puts("FTS5 is " #ifdef SQLITE_ENABLE_FTS5 "available" @@ -3338,7 +3334,7 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ "\n\tenv %u" "\n\tnph inits %u" "\n\tperDb %u" - "\n\tautoExt %u container access\n", + "\n\tautoExt %u list accesses\n", SJG.metrics.nMutexEnv, SJG.metrics.nMutexEnv2, SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt); printf("S3JniDb: %u alloced (*%u = %u bytes), %u recycled\n", diff --git a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java index afe2618a00..32aee978df 100644 --- a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java +++ b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java @@ -28,6 +28,6 @@ package org.sqlite.jni; */ public class NativePointerHolder { //! Only set from JNI, where access permissions don't matter. - private long nativePointer = 0; + private volatile long nativePointer = 0; public final long getNativePointer(){ return nativePointer; } } diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index e010e24703..951ded16aa 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -25,7 +25,9 @@ public class Tester1 implements Runnable { //! True when running in multi-threaded mode. private static boolean mtMode = false; private static boolean takeNaps = false; - + private static boolean shuffle = false; + private static boolean listRunTests = false; + private static List testMethods = null; private static final class Metrics { int dbOpen; } @@ -159,7 +161,7 @@ public class Tester1 implements Runnable { return rv; } - private void testCompileOption(){ + private void showCompileOption(){ int i = 0; String optName; outln("compile options:"); @@ -1175,31 +1177,41 @@ public class Tester1 implements Runnable { } private void runTests(boolean fromThread) throws Exception { - if(false) testCompileOption(); + if(false) showCompileOption(); + List mlist = testMethods; + affirm( null!=mlist ); + if( shuffle ){ + mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) ); + java.util.Collections.shuffle( + mlist + //java.util.concurrent.ThreadLocalRandom.current() + ); + } + if( listRunTests ){ + synchronized(this.getClass()){ + out("Initial test"," list: "); + for(java.lang.reflect.Method m : testMethods){ + out(m.getName()+" "); + } + outln(); + + out("Running"," tests: "); + for(java.lang.reflect.Method m : mlist){ + out(m.getName()+" "); + } + outln(); + out("(That list excludes some which are hard-coded to run.)\n"); + } + } testToUtf8(); test1(); - nap(); testOpenDb1(); - nap(); testOpenDb2(); - nap(); testCollation(); - nap(); testPrepare123(); - nap(); testBindFetchInt(); - nap(); testBindFetchInt64(); - nap(); testBindFetchDouble(); - nap(); testBindFetchText(); - nap(); testBindFetchBlob(); - nap(); testSql(); - nap(); testStatus(); - nap(); testUdf1(); - nap(); testUdfJavaObject(); - nap(); testUdfAggregate(); - nap(); testUdfWindow(); - nap(); testTrace(); - nap(); testProgress(); - nap(); testCommitHook(); - nap(); testRollbackHook(); - nap(); testUpdateHook(); - nap(); testAuthorizer(); - nap(); testAutoExtension(); + int n = 0; + for(java.lang.reflect.Method m : mlist){ + ++n; + nap(); + m.invoke(this); + } + affirm( n == mlist.size() ); if(!fromThread){ testBusy(); if( !mtMode ){ @@ -1219,6 +1231,27 @@ public class Tester1 implements Runnable { } } + /** + Runs the basic sqlite3 JNI binding sanity-check suite. + + CLI flags: + + -t|-thread N: runs the tests in N threads + concurrently. Default=1. + + -r|-repeat N: repeats the tests in a loop N times, each one + consisting of the -thread value's threads. + + -shuffle: randomizes the order of most of the test functions. + + -naps: sleep small random intervals between tests in order to add + some chaos for cross-thread contention. + + -list-tests: outputs the list of tests being run, minus some + which are hard-coded, + + -v: emit some developer-mode info at the end. + */ public static void main(String[] args) throws Exception { Integer nThread = null; boolean doSomethingForDev = false; @@ -1232,8 +1265,12 @@ public class Tester1 implements Runnable { //listBoundMethods(); }else if(arg.equals("t") || arg.equals("thread")){ nThread = Integer.parseInt(args[i++]); - }else if(arg.equals("r") || arg.equals("runs")){ + }else if(arg.equals("r") || arg.equals("repeat")){ nRepeat = Integer.parseInt(args[i++]); + }else if(arg.equals("shuffle")){ + shuffle = true; + }else if(arg.equals("list-tests")){ + listRunTests = true; }else if(arg.equals("naps")){ takeNaps = true; }else{ @@ -1242,6 +1279,24 @@ public class Tester1 implements Runnable { } } + { + // Build list of tests to run from the methods named test*(). + testMethods = new ArrayList<>(); + final List excludes = new ArrayList<>(); + // Tests we want to control the order of: + excludes.add("testSleep"); + excludes.add("testToUtf8"); + excludes.add("test1"); + excludes.add("testBusy"); + excludes.add("testFts5"); + for(java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){ + final String name = m.getName(); + if( name.startsWith("test") && excludes.indexOf(name)<0 ){ + testMethods.add(m); + } + } + } + final long timeStart = System.currentTimeMillis(); int nLoop = 0; outln("libversion_number: ", diff --git a/manifest b/manifest index 71e4b52cfe..eb006ab13a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Disassociate\sJNI\sdb\shandles\sfrom\sthe\sthread\sthat\screated\sthem,\sas\sit's\sno\slonger\srelevant. -D 2023-08-22T18:36:30.981 +C More\swork\son\sthe\sJNI\smulti-threaded\stest\srunner. +D 2023-08-22T20:10:28.237 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 1ccd09095447709ffd7a4f32514fd586512491c6bed06d009bab4294b451ed62 F ext/jni/README.md 975b35173debbbf3a4ab7166e14d2ffa2bacff9b6850414f09cc919805e81ba4 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c 50edc462e8fdf54f9b8ede692a7c865c5e4315930899276664dd6744764d4723 +F ext/jni/src/c/sqlite3-jni.c c38c18875b946a3bdc4eda0b2f19ad53b895118979ec85a630706c1c5575079b 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 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd @@ -249,14 +249,14 @@ F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 10cb2e0eb4dc5cf4241a7ccc0442a F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 -F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee +F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 8110d4cfb20884e8ed241de7420c615b040a9f9c441d9cff06f34833399244a8 F ext/jni/src/org/sqlite/jni/OutputPointer.java 464ea85c3eba673a7b575545f69fcd8aeb398477a26d155d88cee3e2459e7802 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc 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 2f36370cfdec01d309720392b2c3e4af6afce0b6ece8188b5c3ed688a5a1e63a -F ext/jni/src/org/sqlite/jni/Tester1.java 58a058f718215ff32fbdf8026a2d4eb88f9d7e939a5640d5a944efafdfda4b7c +F ext/jni/src/org/sqlite/jni/Tester1.java da8bc65f52d310ae17b372eeaef25726be47d3a2052e8a33ce44606a7dc451d7 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 02e868690f97ca728b0f2dd018aa79a9d13c85dd85b164caa895d319ae8f3ff5 -R 4fc9fbe1829e4bd8b0e17e9933291453 +P 8b78b737e66a399b04e555a8197f63a73198a4105cb2f37ffd5b0e6014302caf +R 31a03bd5ee6de6309ec09a8781691d85 U stephan -Z 9fda786e7e193bbf92304385fbc27c96 +Z 973ac484c97d56f1bb0793972fc0262a # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index cfa2599e0d..0f65722775 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8b78b737e66a399b04e555a8197f63a73198a4105cb2f37ffd5b0e6014302caf \ No newline at end of file +9a74ad716bded1e14333bf7c72392916f800d58a96240eabe4988ca5fc9e8752 \ No newline at end of file From 3600976bf1b4224858e16ae513049abb80325067 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 22 Aug 2023 22:13:08 +0000 Subject: [PATCH 27/37] Fix Tester1 so that exceptions triggered via threads are not silently ignored. Disable auto-extension tests in multi-thread mode because concurrent threads rightfully interfere with that. FossilOrigin-Name: 56b2a077ace6e6ad5834e1a597b710f212a5b7d5db5b9a27a41f2aa0f6952c55 --- ext/jni/GNUmakefile | 4 +- ext/jni/src/c/sqlite3-jni.c | 62 ++++++++++++---- ext/jni/src/org/sqlite/jni/Tester1.java | 99 ++++++++++++++++--------- manifest | 16 ++-- manifest.uuid | 2 +- 5 files changed, 120 insertions(+), 63 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 788c072062..0cda6bf2e4 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -166,8 +166,8 @@ SQLITE_OPT = \ -DSQLITE_THREADSAFE=1 \ -DSQLITE_TEMP_STORE=2 \ -DSQLITE_USE_URI=1 \ - -DSQLITE_C=$(sqlite3.c) -# -DSQLITE_DEBUG + -DSQLITE_C=$(sqlite3.c) \ + -DSQLITE_DEBUG SQLITE_OPT += -g -DDEBUG -UNDEBUG diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 2299a4c6c0..f1bc049545 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -450,6 +450,16 @@ struct S3JniAutoExtension { jmethodID midFunc /* xEntryPoint() callback */; }; +/* +** If true, modifying S3JniGlobal.metrics is protected by a mutex, +** else it isn't. +*/ +#ifdef SQLITE_DEBUG +#define S3JNI_METRICS_MUTEX 1 +#else +#define S3JNI_METRICS_MUTEX 0 +#endif + /* ** Global state, e.g. caches and metrics. */ @@ -537,6 +547,10 @@ struct S3JniGlobalType { volatile unsigned nValue; volatile unsigned nInverse; } udf; + unsigned nMetrics /* Total number of mutex-locked metrics increments. */; +#if S3JNI_METRICS_MUTEX + sqlite3_mutex * mutex; +#endif } metrics; /** The list of bound auto-extensions (Java-side: @@ -554,6 +568,18 @@ struct S3JniGlobalType { static S3JniGlobalType S3JniGlobal = {}; #define SJG S3JniGlobal +static void s3jni_incr( volatile unsigned int * const p ){ +#if S3JNI_METRICS_MUTEX + sqlite3_mutex * const m = SJG.metrics.mutex; + sqlite3_mutex_enter(m); + ++SJG.metrics.nMetrics; + ++(*p); + sqlite3_mutex_leave(m); +#else + ++(*p); +#endif +} + /* Helpers for working with specific mutexes. */ #define MUTEX_ENV_ASSERT_LOCKED \ assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) @@ -629,12 +655,12 @@ static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){ row = SJG.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ - ++SJG.metrics.envCacheHits; + s3jni_incr( &SJG.metrics.envCacheHits ); MUTEX_ENV_LEAVE; return row; } } - ++SJG.metrics.envCacheMisses; + s3jni_incr( &SJG.metrics.envCacheMisses ); row = SJG.envCache.aFree; if( row ){ assert(!row->pPrev); @@ -642,7 +668,7 @@ static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){ if( row->pNext ) row->pNext->pPrev = 0; }else{ row = s3jni_malloc(env, sizeof(S3JniEnv)); - ++SJG.metrics.envCacheAllocs; + s3jni_incr( &SJG.metrics.envCacheAllocs ); } memset(row, 0, sizeof(*row)); row->pNext = SJG.envCache.aHead; @@ -872,7 +898,7 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ } method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); if(method){ - ++SJG.metrics.nDestroy; + s3jni_incr( &SJG.metrics.nDestroy ); (*env)->CallVoidMethod(env, jObj, method); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback"); @@ -1083,13 +1109,13 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, rv->pNext->pPrev = 0; rv->pNext = 0; } - ++SJG.metrics.nPdbRecycled; + s3jni_incr( &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; + s3jni_incr( &SJG.metrics.nPdbAlloc ); } } if(rv){ @@ -1631,29 +1657,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); - ++SJG.metrics.udf.nFunc; + s3jni_incr( &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); - ++SJG.metrics.udf.nStep; + s3jni_incr( &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); - ++SJG.metrics.udf.nFinal; + s3jni_incr( &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); - ++SJG.metrics.udf.nValue; + s3jni_incr( &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); - ++SJG.metrics.udf.nInverse; + s3jni_incr( &SJG.metrics.udf.nInverse ); udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse"); } @@ -3301,8 +3327,7 @@ JDECL(jbyteArray,1value_1text16be)(JENV_CSELF, jobject jpSVal){ } JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ - MARKER(("\nVarious bits of internal info:\n" - "Any metrics here are invalid in multi-thread use.\n")); + MARKER(("\nVarious bits of internal info:\n")); puts("FTS5 is " #ifdef SQLITE_ENABLE_FTS5 "available" @@ -3334,9 +3359,11 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ "\n\tenv %u" "\n\tnph inits %u" "\n\tperDb %u" - "\n\tautoExt %u list accesses\n", + "\n\tautoExt %u list accesses" + "\n\tmetrics %u\n", SJG.metrics.nMutexEnv, SJG.metrics.nMutexEnv2, - SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt); + SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt, + SJG.metrics.nMetrics); printf("S3JniDb: %u alloced (*%u = %u bytes), %u recycled\n", SJG.metrics.nPdbAlloc, (unsigned) sizeof(S3JniDb), (unsigned)(SJG.metrics.nPdbAlloc * sizeof(S3JniDb)), @@ -4295,6 +4322,11 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); OOM_CHECK( SJG.autoExt.mutex ); +#if S3JNI_METRICS_MUTEX + SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + OOM_CHECK( SJG.metrics.mutex ); +#endif + #if 0 /* Just for sanity checking... */ (void)S3JniGlobal_env_cache(env); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 951ded16aa..602134f155 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -28,6 +28,7 @@ public class Tester1 implements Runnable { private static boolean shuffle = false; private static boolean listRunTests = false; private static List testMethods = null; + private static List listErrors = new ArrayList<>(); private static final class Metrics { int dbOpen; } @@ -67,7 +68,7 @@ public class Tester1 implements Runnable { static volatile int affirmCount = 0; public synchronized static void affirm(Boolean v, String comment){ ++affirmCount; - assert( v /* prefer assert over exception if it's enabled because + if( false ) assert( v /* prefer assert over exception if it's enabled because the JNI layer sometimes has to suppress exceptions, so they might be squelched on their way back to the top. */); @@ -1106,7 +1107,7 @@ public class Tester1 implements Runnable { sqlite3 db = createNewDb(); affirm( 4==val.value ); execSql(db, "ATTACH ':memory' as foo"); - affirm( 4==val.value /* ATTACH uses the same connection, not sub-connections. */ ); + affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." ); sqlite3_close(db); db = null; @@ -1176,55 +1177,62 @@ public class Tester1 implements Runnable { } } + private void testFail(){ + affirm( false, "Intentional failure." ); + } + private void runTests(boolean fromThread) throws Exception { if(false) showCompileOption(); List mlist = testMethods; affirm( null!=mlist ); if( shuffle ){ mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) ); - java.util.Collections.shuffle( - mlist - //java.util.concurrent.ThreadLocalRandom.current() - ); + java.util.Collections.shuffle(mlist); } if( listRunTests ){ synchronized(this.getClass()){ - out("Initial test"," list: "); - for(java.lang.reflect.Method m : testMethods){ - out(m.getName()+" "); + if( !fromThread ){ + out("Initial test"," list: "); + for(java.lang.reflect.Method m : testMethods){ + out(m.getName()+" "); + } + outln(); + outln("(That list excludes some which are hard-coded to run.)"); } - outln(); - out("Running"," tests: "); for(java.lang.reflect.Method m : mlist){ out(m.getName()+" "); } outln(); - out("(That list excludes some which are hard-coded to run.)\n"); } } testToUtf8(); test1(); - int n = 0; for(java.lang.reflect.Method m : mlist){ - ++n; nap(); - m.invoke(this); + try{ + m.invoke(this); + }catch(java.lang.reflect.InvocationTargetException e){ + outln("FAILURE: ",m.getName(),"(): ", e.getCause()); + throw e; + } } - affirm( n == mlist.size() ); - if(!fromThread){ + if( !fromThread ){ testBusy(); if( !mtMode ){ + testAutoExtension() /* threads rightfully muck up these results */; testFts5(); } } } - public void run(){ + public void run() { try { runTests(0!=this.tId); }catch(Exception e){ - throw new RuntimeException(e); + synchronized( listErrors ){ + listErrors.add(e); + } }finally{ affirm( SQLite3Jni.uncacheJniEnv() ); affirm( !SQLite3Jni.uncacheJniEnv() ); @@ -1256,6 +1264,7 @@ public class Tester1 implements Runnable { Integer nThread = null; boolean doSomethingForDev = false; Integer nRepeat = 1; + boolean forceFail = false; for( int i = 0; i < args.length; ){ String arg = args[i++]; if(arg.startsWith("-")){ @@ -1269,8 +1278,13 @@ public class Tester1 implements Runnable { nRepeat = Integer.parseInt(args[i++]); }else if(arg.equals("shuffle")){ shuffle = true; + outln("WARNING: -shuffle mode is known to run ", + "the same number of tests but provide far ", + "lower, unpredictable metrics for unknown reasons."); }else if(arg.equals("list-tests")){ listRunTests = true; + }else if(arg.equals("fail")){ + forceFail = true; }else if(arg.equals("naps")){ takeNaps = true; }else{ @@ -1284,11 +1298,13 @@ public class Tester1 implements Runnable { testMethods = new ArrayList<>(); final List excludes = new ArrayList<>(); // Tests we want to control the order of: - excludes.add("testSleep"); - excludes.add("testToUtf8"); + if( !forceFail ) excludes.add("testFail"); excludes.add("test1"); + excludes.add("testAutoExtension"); excludes.add("testBusy"); excludes.add("testFts5"); + excludes.add("testSleep"); + excludes.add("testToUtf8"); for(java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){ final String name = m.getName(); if( name.startsWith("test") && excludes.indexOf(name)<0 ){ @@ -1306,23 +1322,32 @@ public class Tester1 implements Runnable { for( int n = 0; n < nRepeat; ++n ){ if( nThread==null || nThread<=1 ){ new Tester1(0).runTests(false); - }else{ - Tester1.mtMode = true; - final ExecutorService ex = Executors.newFixedThreadPool( nThread ); - //final List> futures = new ArrayList<>(); - ++nLoop; - out((1==nLoop ? "" : " ")+nLoop); - for( int i = 0; i < nThread; ++i ){ - ex.submit( new Tester1(i), i ); - } - ex.shutdown(); - try { - ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS); - ex.shutdownNow(); - } catch (InterruptedException ie) { - ex.shutdownNow(); - Thread.currentThread().interrupt(); + continue; + } + Tester1.mtMode = true; + final ExecutorService ex = Executors.newFixedThreadPool( nThread ); + //final List> futures = new ArrayList<>(); + ++nLoop; + out((1==nLoop ? "" : " ")+nLoop); + for( int i = 0; i < nThread; ++i ){ + ex.submit( new Tester1(i), i ); + } + ex.shutdown(); + try{ + ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS); + ex.shutdownNow(); + }catch (InterruptedException ie){ + ex.shutdownNow(); + Thread.currentThread().interrupt(); + } + if( !listErrors.isEmpty() ){ + outln("TEST ERRORS:"); + Exception err = null; + for( Exception e : listErrors ){ + e.printStackTrace(); + if( null==err ) err = e; } + if( null!=err ) throw err; } } outln(); diff --git a/manifest b/manifest index eb006ab13a..dbd6267c34 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\swork\son\sthe\sJNI\smulti-threaded\stest\srunner. -D 2023-08-22T20:10:28.237 +C Fix\sTester1\sso\sthat\sexceptions\striggered\svia\sthreads\sare\snot\ssilently\signored.\sDisable\sauto-extension\stests\sin\smulti-thread\smode\sbecause\sconcurrent\sthreads\srightfully\sinterfere\swith\sthat. +D 2023-08-22T22:13:08.053 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,10 +232,10 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 1ccd09095447709ffd7a4f32514fd586512491c6bed06d009bab4294b451ed62 +F ext/jni/GNUmakefile 95dfae98709a8ffd9bb2087fc53239b37a22cad6d7495db77f46ae56835623bc F ext/jni/README.md 975b35173debbbf3a4ab7166e14d2ffa2bacff9b6850414f09cc919805e81ba4 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c c38c18875b946a3bdc4eda0b2f19ad53b895118979ec85a630706c1c5575079b +F ext/jni/src/c/sqlite3-jni.c 1064441d33cef541c9c9c84fb5093573a0767bb36563e6ea538c355b6148c4c6 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 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd @@ -256,7 +256,7 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c 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 2f36370cfdec01d309720392b2c3e4af6afce0b6ece8188b5c3ed688a5a1e63a -F ext/jni/src/org/sqlite/jni/Tester1.java da8bc65f52d310ae17b372eeaef25726be47d3a2052e8a33ce44606a7dc451d7 +F ext/jni/src/org/sqlite/jni/Tester1.java 9e44d27226eea7486c32eaea6789e8505422c9202f328ff0c1473b75f4ebeeb8 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 8b78b737e66a399b04e555a8197f63a73198a4105cb2f37ffd5b0e6014302caf -R 31a03bd5ee6de6309ec09a8781691d85 +P 9a74ad716bded1e14333bf7c72392916f800d58a96240eabe4988ca5fc9e8752 +R 1b5653fffe70ebb6615cb201c18f879a U stephan -Z 973ac484c97d56f1bb0793972fc0262a +Z a4cd6dac629b552f49728a69325b689c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0f65722775..ccf7dfc8fb 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9a74ad716bded1e14333bf7c72392916f800d58a96240eabe4988ca5fc9e8752 \ No newline at end of file +56b2a077ace6e6ad5834e1a597b710f212a5b7d5db5b9a27a41f2aa0f6952c55 \ No newline at end of file From d53565b4f854a78b4b9c673f18d27f7dd2c4b7f0 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 22 Aug 2023 23:00:44 +0000 Subject: [PATCH 28/37] Minor Tester1.java cleanups. FossilOrigin-Name: 70d936953ba55cfed32efe7e3a9d4b71da9a7ffc8818b6540471e0bf311bc688 --- ext/jni/GNUmakefile | 2 +- ext/jni/src/org/sqlite/jni/Tester1.java | 19 +++++++++++++------ ext/jni/src/org/sqlite/jni/TesterFts5.java | 6 ++---- manifest | 16 ++++++++-------- manifest.uuid | 2 +- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 0cda6bf2e4..f1a6c552e9 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -232,7 +232,7 @@ test: $(SQLite3Jni.class) $(sqlite3-jni.dll) test-mt: $(SQLite3Jni.class) $(sqlite3-jni.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ - org.sqlite.jni.Tester1 -t 7 -r 50 $(test.flags) + org.sqlite.jni.Tester1 -t 7 -r 50 -shuffle $(test.flags) tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) tester.flags ?= # --verbose diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 602134f155..cc221d1013 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -24,13 +24,19 @@ import java.util.concurrent.Future; public class Tester1 implements Runnable { //! True when running in multi-threaded mode. private static boolean mtMode = false; + //! True to sleep briefly between tests. private static boolean takeNaps = false; + //! True to shuffle the order of the tests. private static boolean shuffle = false; + //! True to dump the list of to-run tests to stdout. private static boolean listRunTests = false; + //! List of test*() methods to run. private static List testMethods = null; + //! List of exceptions collected by run() private static List listErrors = new ArrayList<>(); private static final class Metrics { - int dbOpen; + //! Number of times createNewDb() (or equivalent) is invoked. + volatile int dbOpen = 0; } private Integer tId; @@ -1173,7 +1179,7 @@ public class Tester1 implements Runnable { private void nap() throws InterruptedException { if( takeNaps ){ - Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 28), 0); + Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0); } } @@ -1256,7 +1262,10 @@ public class Tester1 implements Runnable { some chaos for cross-thread contention. -list-tests: outputs the list of tests being run, minus some - which are hard-coded, + which are hard-coded. This is noisy in multi-threaded mode. + + -fail: forces an exception to be thrown during the test run. Use + with -shuffle to make its appearance unpredictable. -v: emit some developer-mode info at the end. */ @@ -1278,9 +1287,6 @@ public class Tester1 implements Runnable { nRepeat = Integer.parseInt(args[i++]); }else if(arg.equals("shuffle")){ shuffle = true; - outln("WARNING: -shuffle mode is known to run ", - "the same number of tests but provide far ", - "lower, unpredictable metrics for unknown reasons."); }else if(arg.equals("list-tests")){ listRunTests = true; }else if(arg.equals("fail")){ @@ -1319,6 +1325,7 @@ public class Tester1 implements Runnable { sqlite3_libversion_number(),"\n", sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); outln("Running ",nRepeat," loop(s) over ",nThread," thread(s)."); + if( takeNaps ) outln("Napping between tests is enabled."); for( int n = 0; n < nRepeat; ++n ){ if( nThread==null || nThread<=1 ){ new Tester1(0).runTests(false); diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index 7edb29dd4d..3856573169 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -78,9 +78,7 @@ public class TesterFts5 { test1(); final int affirmCount = Tester1.affirmCount - oldAffirmCount; final long timeEnd = System.currentTimeMillis(); - outln("FTS5 Tests done. Metrics:"); - outln("\tAssertions checked: ",affirmCount); - outln("\tTotal time = " - +(timeEnd - timeStart)+"ms"); + outln("FTS5 Tests done. Assertions checked = ",affirmCount, + ", Total time = ",(timeEnd - timeStart),"ms"); } } diff --git a/manifest b/manifest index dbd6267c34..29963f490c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sTester1\sso\sthat\sexceptions\striggered\svia\sthreads\sare\snot\ssilently\signored.\sDisable\sauto-extension\stests\sin\smulti-thread\smode\sbecause\sconcurrent\sthreads\srightfully\sinterfere\swith\sthat. -D 2023-08-22T22:13:08.053 +C Minor\sTester1.java\scleanups. +D 2023-08-22T23:00:44.674 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 95dfae98709a8ffd9bb2087fc53239b37a22cad6d7495db77f46ae56835623bc +F ext/jni/GNUmakefile a38d7c5ad4ab1e209e2a9352ff06add1d9a0bc666351714bfc8858625330c16b F ext/jni/README.md 975b35173debbbf3a4ab7166e14d2ffa2bacff9b6850414f09cc919805e81ba4 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 F ext/jni/src/c/sqlite3-jni.c 1064441d33cef541c9c9c84fb5093573a0767bb36563e6ea538c355b6148c4c6 @@ -256,8 +256,8 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c 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 2f36370cfdec01d309720392b2c3e4af6afce0b6ece8188b5c3ed688a5a1e63a -F ext/jni/src/org/sqlite/jni/Tester1.java 9e44d27226eea7486c32eaea6789e8505422c9202f328ff0c1473b75f4ebeeb8 -F ext/jni/src/org/sqlite/jni/TesterFts5.java c729d5b3cb91888b7e2a3a3ef450852f184697df78721574f6c0bf9043e4b84c +F ext/jni/src/org/sqlite/jni/Tester1.java 1a9d9a0ce93aa105dc409273bb50bf10f15e8b546d9ef0922b14e69dc3f14107 +F ext/jni/src/org/sqlite/jni/TesterFts5.java de095e3b701fba0c56d7b8b2993dc22bcbaa9de8f992904a93729ad729a91576 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -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 9a74ad716bded1e14333bf7c72392916f800d58a96240eabe4988ca5fc9e8752 -R 1b5653fffe70ebb6615cb201c18f879a +P 56b2a077ace6e6ad5834e1a597b710f212a5b7d5db5b9a27a41f2aa0f6952c55 +R 786162271678eb8ff4639708ab69b4fa U stephan -Z a4cd6dac629b552f49728a69325b689c +Z 0962b73394fcdbf4989509de32c7e31a # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ccf7dfc8fb..767f632dca 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -56b2a077ace6e6ad5834e1a597b710f212a5b7d5db5b9a27a41f2aa0f6952c55 \ No newline at end of file +70d936953ba55cfed32efe7e3a9d4b71da9a7ffc8818b6540471e0bf311bc688 \ No newline at end of file From 336bc8a28114364d42987dc1c21f48a6910080f9 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 23 Aug 2023 00:17:28 +0000 Subject: [PATCH 29/37] Improve C-side exception handling from Java-side UDF callbacks. FossilOrigin-Name: aebbc24afb339ed07b7cd767fbc0d25f3e9c3d9bb5ef3d1c10b04b605c7261bc --- ext/jni/README.md | 4 +- ext/jni/src/c/sqlite3-jni.c | 79 ++++++++++++++------- ext/jni/src/org/sqlite/jni/SQLFunction.java | 30 ++++++-- ext/jni/src/org/sqlite/jni/Tester1.java | 55 ++++++++++++-- manifest | 18 ++--- manifest.uuid | 2 +- 6 files changed, 142 insertions(+), 46 deletions(-) diff --git a/ext/jni/README.md b/ext/jni/README.md index 395365292f..5e100c4f07 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -105,7 +105,9 @@ Client-defined callbacks _must never throw exceptions_ unless _very explicitly documented_ as being throw-safe. Exceptions are generally reserved for higher-level bindings which are constructed to specifically deal with them and ensure that they do not leak C-level -resources. +resources. In some cases, callback handlers (see below) are permitted +to throw, in which cases they get translated to C-level result codes +and/or messages. Unwieldy Constructs are Re-mapped diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index f1bc049545..3159116f72 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1577,21 +1577,46 @@ error_oom: return SQLITE_NOMEM; } -static int udf_report_exception(sqlite3_context * cx, - const char *zFuncName, - const char *zFuncType){ - int rc; - char * z = - sqlite3_mprintf("Client-defined function %s.%s() threw. It should " - "not do that.", - zFuncName ? zFuncName : "", zFuncType); - if(z){ - sqlite3_result_error(cx, z, -1); - sqlite3_free(z); - rc = SQLITE_ERROR; +/* +** Must be called immediately after a Java-side UDF callback throws. +** If translateToErr is true then it sets the exception's message in +** the result error using sqlite3_result_error(). If translateToErr is +** false then it emits a warning that the function threw but should +** not do so. In either case, it clears the exception state. +** +** Returns SQLITE_NOMEM if an allocation fails, else SQLITE_ERROR. In +** the latter case it calls sqlite3_result_error_nomem(). +*/ +static int udf_report_exception(JNIEnv * const env, int translateToErr, + sqlite3_context * cx, + const char *zFuncName, const char *zFuncType ){ + jthrowable const ex = (*env)->ExceptionOccurred(env); + int rc = SQLITE_ERROR; + + assert(ex && "This must only be called when a Java exception is pending."); + if( translateToErr ){ + char * zMsg; + char * z; + + EXCEPTION_CLEAR; + zMsg = s3jni_exception_error_msg(env, ex); + z = sqlite3_mprintf("Client-defined SQL function %s.%s() threw: %s", + zFuncName ? zFuncName : "", zFuncType, + zMsg ? zMsg : "Unknown exception" ); + sqlite3_free(zMsg); + if( z ){ + sqlite3_result_error(cx, z, -1); + sqlite3_free(z); + }else{ + sqlite3_result_error_nomem(cx); + rc = SQLITE_NOMEM; + } }else{ - sqlite3_result_error_nomem(cx); - rc = SQLITE_NOMEM; + MARKER(("Client-defined SQL function %s.%s() threw. " + "It should not do that.\n", + zFuncName ? zFuncName : "", zFuncType)); + (*env)->ExceptionDescribe( env ); + EXCEPTION_CLEAR; } return rc; } @@ -1600,24 +1625,25 @@ static int udf_report_exception(sqlite3_context * cx, Sets up the state for calling a Java-side xFunc/xStep/xInverse() UDF, calls it, and returns 0 on success. */ -static int udf_xFSI(sqlite3_context* pCx, int argc, - sqlite3_value** argv, - S3JniUdf * s, +static int udf_xFSI(sqlite3_context* const pCx, int argc, + sqlite3_value** const argv, + S3JniUdf * const s, jmethodID xMethodID, - const char * zFuncType){ + const char * const zFuncType){ LocalJniGetEnv; udf_jargs args = {0,0}; 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)); + if(rc) return rc; if( UDF_SCALAR != s->type ){ rc = udf_setAggregateContext(env, args.jcx, pCx, 0); } if( 0 == rc ){ (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); IFTHREW{ - rc = udf_report_exception(pCx, s->zFuncName, zFuncType); + rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx, + s->zFuncName, zFuncType); } } UNREF_L(args.jcx); @@ -1635,19 +1661,21 @@ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, LocalJniGetEnv; jobject jcx = new_sqlite3_context_wrapper(env, cx); int rc = 0; + int const isFinal = 'F'==zFuncType[1]/*xFinal*/; //MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx)); if(!jcx){ - sqlite3_result_error_nomem(cx); + if( isFinal ) sqlite3_result_error_nomem(cx); return SQLITE_NOMEM; } //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); if( UDF_SCALAR != s->type ){ - rc = udf_setAggregateContext(env, jcx, cx, 1); + rc = udf_setAggregateContext(env, jcx, cx, isFinal); } if( 0 == rc ){ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); IFTHREW{ - rc = udf_report_exception(cx,s->zFuncName, zFuncType); + rc = udf_report_exception(env, isFinal, cx, s->zFuncName, + zFuncType); } } UNREF_L(jcx); @@ -3599,8 +3627,7 @@ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid, jFXA, jpFts, jpCx, jArgv); IFTHREW{ - EXCEPTION_CLEAR; - udf_report_exception(pCx, pAux->zFuncName, "xFunction"); + udf_report_exception(env, 1, pCx, pAux->zFuncName, "xFunction"); } UNREF_L(jpFts); UNREF_L(jpCx); diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java index 28775608ad..c8e87ff827 100644 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -98,7 +98,11 @@ public abstract class SQLFunction { //! Subclass for creating scalar functions. public static abstract class Scalar extends SQLFunction { - //! As for the xFunc() argument of the C API's sqlite3_create_function() + /** + As for the xFunc() argument of the C API's + sqlite3_create_function(). If this function throws, it is + translated into an sqlite3_result_error(). + */ public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args); /** @@ -116,10 +120,18 @@ public abstract class SQLFunction { */ public static abstract class Aggregate extends SQLFunction { - //! As for the xStep() argument of the C API's sqlite3_create_function() + /** + As for the xStep() argument of the C API's + sqlite3_create_function(). If this function throws, the + exception is not propagated and a warning might be emitted to a + debugging channel. + */ public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); - //! As for the xFinal() argument of the C API's sqlite3_create_function() + /** + As for the xFinal() argument of the C API's sqlite3_create_function(). + If this function throws, it is translated into an sqlite3_result_error(). + */ public abstract void xFinal(sqlite3_context cx); /** @@ -165,10 +177,18 @@ public abstract class SQLFunction { */ public static abstract class Window extends Aggregate { - //! As for the xInverse() argument of the C API's sqlite3_create_window_function() + /** + As for the xInverse() argument of the C API's + sqlite3_create_window_function(). If this function throws, the + exception is not propagated and a warning might be emitted + to a debugging channel. + */ public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args); - //! As for the xValue() argument of the C API's sqlite3_create_window_function() + /** + As for the xValue() argument of the C API's sqlite3_create_window_function(). + See xInverse() for the fate of any exceptions this throws. + */ public abstract void xValue(sqlite3_context cx); } } diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index cc221d1013..c075a2d036 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -158,16 +158,22 @@ public class Tester1 implements Runnable { execSql(db, true, sql); } - static sqlite3_stmt prepare(sqlite3 db, String sql){ + static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); int rc = sqlite3_prepare(db, sql, outStmt); - affirm( 0 == rc ); + if( throwOnError ){ + affirm( 0 == rc ); + } final sqlite3_stmt rv = outStmt.take(); affirm( null == outStmt.get() ); - affirm( 0 != rv.getNativePointer() ); + if( throwOnError ){ + affirm( 0 != rv.getNativePointer() ); + } return rv; } - + static sqlite3_stmt prepare(sqlite3 db, String sql){ + return prepare(db, true, sql); + } private void showCompileOption(){ int i = 0; String optName; @@ -598,6 +604,47 @@ public class Tester1 implements Runnable { affirm( xDestroyCalled.value ); } + private void testUdfThrows(){ + final sqlite3 db = createNewDb(); + final ValueHolder xFuncAccum = new ValueHolder<>(0); + + SQLFunction funcAgg = new SQLFunction.Aggregate(){ + @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ + /** Throwing from here should emit loud noise on stdout or stderr + but the exception is supressed because we have no way to inform + sqlite about it from these callbacks. */ + //throw new RuntimeException("Throwing from an xStep"); + } + @Override public void xFinal(sqlite3_context cx){ + throw new RuntimeException("Throwing from an xFinal"); + } + }; + int rc = sqlite3_create_function(db, "myagg", 1, SQLITE_UTF8, funcAgg); + affirm(0 == rc); + affirm(0 == xFuncAccum.value); + sqlite3_stmt stmt = prepare(db, "SELECT myagg(1)"); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + affirm( 0 != rc ); + affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 ); + + SQLFunction funcSc = new SQLFunction.Scalar(){ + @Override public void xFunc(sqlite3_context cx, sqlite3_value[] args){ + throw new RuntimeException("Throwing from an xFunc"); + } + }; + rc = sqlite3_create_function(db, "mysca", 0, SQLITE_UTF8, funcSc); + affirm(0 == rc); + affirm(0 == xFuncAccum.value); + stmt = prepare(db, "SELECT mysca()"); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + affirm( 0 != rc ); + affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 ); + + sqlite3_close_v2(db); + } + private void testUdfJavaObject(){ final sqlite3 db = createNewDb(); final ValueHolder testResult = new ValueHolder<>(db); diff --git a/manifest b/manifest index 29963f490c..56dd2871d2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\sTester1.java\scleanups. -D 2023-08-22T23:00:44.674 +C Improve\sC-side\sexception\shandling\sfrom\sJava-side\sUDF\scallbacks. +D 2023-08-23T00:17:28.362 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -233,9 +233,9 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile a38d7c5ad4ab1e209e2a9352ff06add1d9a0bc666351714bfc8858625330c16b -F ext/jni/README.md 975b35173debbbf3a4ab7166e14d2ffa2bacff9b6850414f09cc919805e81ba4 +F ext/jni/README.md ddcc6be0c0d65f1e2fd687de9f40d38c82630fd61f83cc9550773caa19dd8be1 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c 1064441d33cef541c9c9c84fb5093573a0767bb36563e6ea538c355b6148c4c6 +F ext/jni/src/c/sqlite3-jni.c 8e7dac68246edec8d85e34104d5c749c544ae10c55b1bbf6eeff116447da7f54 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 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd @@ -254,9 +254,9 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 464ea85c3eba673a7b575545f69fcd8a F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc 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/SQLFunction.java f697cf2a81c4119f2baf0682af689686f0466f1dd83dba00885f5603e693fe16 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2f36370cfdec01d309720392b2c3e4af6afce0b6ece8188b5c3ed688a5a1e63a -F ext/jni/src/org/sqlite/jni/Tester1.java 1a9d9a0ce93aa105dc409273bb50bf10f15e8b546d9ef0922b14e69dc3f14107 +F ext/jni/src/org/sqlite/jni/Tester1.java 93bd76f2294fa2f592395c9d823adb38a9be3846bff00debeff02310f4e9e6be F ext/jni/src/org/sqlite/jni/TesterFts5.java de095e3b701fba0c56d7b8b2993dc22bcbaa9de8f992904a93729ad729a91576 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 56b2a077ace6e6ad5834e1a597b710f212a5b7d5db5b9a27a41f2aa0f6952c55 -R 786162271678eb8ff4639708ab69b4fa +P 70d936953ba55cfed32efe7e3a9d4b71da9a7ffc8818b6540471e0bf311bc688 +R f4b3990a7d714ecf1a549380689ce9aa U stephan -Z 0962b73394fcdbf4989509de32c7e31a +Z b8494717453324d170d4924e0442cf8e # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 767f632dca..d1c49b53b7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -70d936953ba55cfed32efe7e3a9d4b71da9a7ffc8818b6540471e0bf311bc688 \ No newline at end of file +aebbc24afb339ed07b7cd767fbc0d25f3e9c3d9bb5ef3d1c10b04b605c7261bc \ No newline at end of file From 4c8ef3894e98ed3a32418b6fb354b338cbc7cbf2 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 23 Aug 2023 09:05:16 +0000 Subject: [PATCH 30/37] Numerous minor cleanups and code style conformance improvements. FossilOrigin-Name: 6c92d884920e4ace54913fc60ceef6e43a4351f45a4cb3c4a0ed3d29d544a31b --- ext/jni/src/c/sqlite3-jni.c | 621 +++++++++++++++++++----------------- manifest | 12 +- manifest.uuid | 2 +- 3 files changed, 327 insertions(+), 308 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 3159116f72..e6a4b71c30 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -174,9 +174,11 @@ ** checked for exceptions. */ #define IFTHREW if((*env)->ExceptionCheck(env)) -#define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) #define EXCEPTION_CLEAR (*env)->ExceptionClear(env) #define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) +#define EXCEPTION_IGNORE IFTHREW EXCEPTION_CLEAR +#define EXCEPTION_WARN_IGNORE \ + IFTHREW {EXCEPTION_REPORT; EXCEPTION_CLEAR;}(void)0 #define EXCEPTION_WARN_CALLBACK_THREW(STR) \ MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \ (*env)->ExceptionDescribe(env) @@ -568,17 +570,18 @@ struct S3JniGlobalType { static S3JniGlobalType S3JniGlobal = {}; #define SJG S3JniGlobal -static void s3jni_incr( volatile unsigned int * const p ){ +/* Increments *p, possibly protected by a mutex. */ #if S3JNI_METRICS_MUTEX +static void s3jni_incr( volatile unsigned int * const p ){ sqlite3_mutex * const m = SJG.metrics.mutex; sqlite3_mutex_enter(m); ++SJG.metrics.nMetrics; ++(*p); sqlite3_mutex_leave(m); -#else - ++(*p); -#endif } +#else +#define s3jni_incr(PTR) ++(*(PTR)) +#endif /* Helpers for working with specific mutexes. */ #define MUTEX_ENV_ASSERT_LOCKED \ @@ -694,7 +697,8 @@ static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){ ** ** Returns err_code. */ -static int s3jni_db_error(sqlite3* const db, int err_code, const char * const zMsg){ +static int s3jni_db_error(sqlite3* const db, int err_code, + const char * const zMsg){ if( db!=0 ){ if( 0==zMsg ){ sqlite3Error(db, err_code); @@ -706,17 +710,23 @@ static int s3jni_db_error(sqlite3* const db, int err_code, const char * const zM return err_code; } -/** - Creates a new jByteArray of length nP, copies p's contents into it, and - returns that byte array. - */ -static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * const p, int nP){ +/* +** Creates a new jByteArray of length nP, copies p's contents into it, and +** returns that byte array (NULL on OOM). +*/ +static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, + const unsigned char * const p, int nP){ jbyteArray jba = (*env)->NewByteArray(env, (jint)nP); if(jba){ (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p); } return jba; } + +/* +** Returns the current JNIEnv object. Fails fatally if it cannot find +** it. +*/ static JNIEnv * s3jni_get_env(void){ JNIEnv * env = 0; if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env, @@ -726,19 +736,20 @@ static JNIEnv * s3jni_get_env(void){ } return env; } +/* Declares local var env = s3jni_get_env(). */ #define LocalJniGetEnv JNIEnv * const env = s3jni_get_env() -/** - Uses the java.lang.String(byte[],Charset) constructor to create a - new String from UTF-8 string z. n is the number of bytes to - copy. If n<0 then sqlite3Strlen30() is used to calculate it. - - Returns NULL if z is NULL or on OOM, else returns a new jstring - owned by the caller. - - Sidebar: this is a painfully inefficient way to convert from - standard UTF-8 to a Java string, but JNI offers only algorithms for - working with MUTF-8, not UTF-8. +/* +** Uses the java.lang.String(byte[],Charset) constructor to create a +** new String from UTF-8 string z. n is the number of bytes to +** copy. If n<0 then sqlite3Strlen30() is used to calculate it. +** +** Returns NULL if z is NULL or on OOM, else returns a new jstring +** owned by the caller. +** +** Sidebar: this is a painfully inefficient way to convert from +** standard UTF-8 to a Java string, but JNI offers only algorithms for +** working with MUTF-8, not UTF-8. */ static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc, const char * const z, int n){ @@ -762,20 +773,20 @@ static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc, return rv; } -/** - Converts the given java.lang.String object into a NUL-terminated - UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8). - Returns NULL if jstr is NULL or on allocation error. If jstr is not - NULL and nLen is not NULL then nLen is set to the length of the - returned string, not including the terminating NUL. If jstr is not - NULL and it returns NULL, this indicates an allocation error. In - that case, if nLen is not NULL then it is either set to 0 (if - fetching of jstr's bytes fails to allocate) or set to what would - have been the length of the string had C-string allocation - succeeded. - - The returned memory is allocated from sqlite3_malloc() and - ownership is transferred to the caller. +/* +** Converts the given java.lang.String object into a NUL-terminated +** UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8). +** Returns NULL if jstr is NULL or on allocation error. If jstr is not +** NULL and nLen is not NULL then nLen is set to the length of the +** returned string, not including the terminating NUL. If jstr is not +** NULL and it returns NULL, this indicates an allocation error. In +** that case, if nLen is not NULL then it is either set to 0 (if +** fetching of jstr's bytes fails to allocate) or set to what would +** have been the length of the string had C-string allocation +** succeeded. +** +** The returned memory is allocated from sqlite3_malloc() and +** ownership is transferred to the caller. */ static char * s3jni_jstring_to_utf8(S3JniEnv * const jc, jstring jstr, int *nLen){ @@ -804,12 +815,12 @@ static char * s3jni_jstring_to_utf8(S3JniEnv * const jc, return rv; } -/** - Expects to be passed a pointer from sqlite3_column_text16() or - sqlite3_value_text16() and a byte-length value from - sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a - Java String of exactly half that character length, returning NULL - if !p or (*env)->NewString() fails. +/* +** Expects to be passed a pointer from sqlite3_column_text16() or +** sqlite3_value_text16() and a byte-length value from +** sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a +** Java String of exactly half that character length, returning NULL +** if !p or (*env)->NewString() fails. */ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){ return p @@ -817,19 +828,18 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, : NULL; } -/** - Requires jx to be a Throwable. Calls its toString() method and - returns its value converted to a UTF-8 string. The caller owns the - returned string and must eventually sqlite3_free() it. Returns 0 - if there is a problem fetching the info or on OOM. - - Design note: we use toString() instead of getMessage() because the - former includes the exception type's name: - - Exception e = new RuntimeException("Hi"); - System.out.println(e.toString()); // java.lang.RuntimeException: Hi - System.out.println(e.getMessage()); // Hi - } +/* +** Requires jx to be a Throwable. Calls its toString() method and +** returns its value converted to a UTF-8 string. The caller owns the +** returned string and must eventually sqlite3_free() it. Returns 0 +** if there is a problem fetching the info or on OOM. +** +** Design note: we use toString() instead of getMessage() because the +** former includes the exception type's name: +** +** Exception e = new RuntimeException("Hi"); +** System.out.println(e.toString()); // java.lang.RuntimeException: Hi +** System.out.println(e.getMessage()); // Hi */ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ S3JniEnv * const jc = S3JniGlobal_env_cache(env); @@ -854,17 +864,17 @@ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ return zMsg; } -/** - Extracts the current JNI exception, sets ps->pDb's error message to - its message string, and clears the exception. If errCode is non-0, - it is used as-is, else SQLITE_ERROR is assumed. If there's a - problem extracting the exception's message, it's treated as - non-fatal and zDfltMsg is used in its place. - - This must only be called if a JNI exception is pending. - - Returns errCode unless it is 0, in which case SQLITE_ERROR is - returned. +/* +** Extracts the current JNI exception, sets ps->pDb's error message to +** its message string, and clears the exception. If errCode is non-0, +** it is used as-is, else SQLITE_ERROR is assumed. If there's a +** problem extracting the exception's message, it's treated as +** non-fatal and zDfltMsg is used in its place. +** +** This must only be called if a JNI exception is pending. +** +** Returns errCode unless it is 0, in which case SQLITE_ERROR is +** returned. */ static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, int errCode, const char *zDfltMsg){ @@ -882,12 +892,12 @@ static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, return errCode; } -/** - Extracts the (void xDestroy()) method from the given jclass and - applies it to jobj. If jObj is NULL, this is a no-op. If klazz is - NULL then it's derived from jobj. The lack of an xDestroy() method - is silently ignored and any exceptions thrown by the method trigger - a warning to stdout or stderr and then the exception is suppressed. +/* +** Extracts the (void xDestroy()) method from the given jclass and +** applies it to jobj. If jObj is NULL, this is a no-op. If klazz is +** NULL then it's derived from jobj. The lack of an xDestroy() method +** is silently ignored and any exceptions thrown by xDestroy() trigger +** a warning to stdout or stderr and then the exception is suppressed. */ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ if(jObj){ @@ -910,12 +920,12 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ } } -/** - Removes any Java references from s and clears its state. If - doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's - s is passed to s3jni_call_xDestroy() before any references are - cleared. It is legal to call this when the object has no Java - references. +/* +** Removes any Java references from s and clears its state. If +** doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's +** s is passed to s3jni_call_xDestroy() before any references are +** cleared. It is legal to call this when the object has no Java +** references. */ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDestroy){ if(doXDestroy && s->klazz && s->jObj){ @@ -926,14 +936,12 @@ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDest memset(s, 0, sizeof(*s)); } -/** - Clears s's state and moves it to the free-list. Requires that - S3JniGlobal.perDb.mutex be unlocked. +/* +** Clears s's state and moves it to the free-list. */ static void S3JniDb_set_aside(JNIEnv * env, S3JniDb * const s){ if(s){ MUTEX_PDB_ENTER; - //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); assert(s->pPrev != s); assert(s->pNext != s); assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); @@ -960,16 +968,14 @@ static void S3JniDb_set_aside(JNIEnv * env, S3JniDb * const s){ s->pNext = SJG.perDb.aFree; if(s->pNext) s->pNext->pPrev = 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)); MUTEX_PDB_LEAVE; } } -/** - Uncache any state for the given JNIEnv, clearing all Java - references the cache owns. Returns true if env was cached and false - if it was not found in the cache. +/* +** Uncache any state for the given JNIEnv, clearing all Java +** references the cache owns. Returns true if env was cached and false +** if it was not found in the cache. */ static int S3JniGlobal_env_uncache(JNIEnv * const env){ struct S3JniEnv * row; @@ -983,7 +989,6 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ if( !row ){ return 0; } - //MARKER(("Uncaching JNIEnv@%p\n", env)); if( row->pNext ) row->pNext->pPrev = row->pPrev; if( row->pPrev ) row->pPrev->pNext = row->pNext; if( SJG.envCache.aHead == row ){ @@ -1001,15 +1006,16 @@ static int S3JniGlobal_env_uncache(JNIEnv * const env){ ** Searches the NativePointerHolder cache for the given combination of ** args. It returns a cache entry with its klazz member set. ** -** It is up to the caller to populate the other members of the returned -** object if needed. +** It is up to the caller to populate the other members of the +** returned object if needed, taking care to lock the population with +** MUTEX_NPH_ENTER/LEAVE. ** ** This simple cache catches >99% of searches in the current ** (2023-07-31) tests. */ static S3JniNphClass * S3JniGlobal_nph_cache(JNIEnv * const env, S3NphRef const* pRef){ /** - According to: + According to: https://developer.ibm.com/articles/j-jni/ @@ -1022,10 +1028,6 @@ static S3JniNphClass * S3JniGlobal_nph_cache(JNIEnv * const env, S3NphRef const* you should look them up once and then reuse them. Similarly, looking up class objects can be expensive, so they should be cached as well. - - Reminder: we do not need a mutex for the envRow->nph cache - because all nph entries are per-thread and envCache.mutex already - guards the fetching of envRow. */ S3JniNphClass * const pNC = &SJG.nph[pRef->index]; if( !pNC->pRef ){ @@ -1041,9 +1043,9 @@ static S3JniNphClass * S3JniGlobal_nph_cache(JNIEnv * const env, S3NphRef const* return pNC; } -/** - Returns the ID of the "nativePointer" field from the given - NativePointerHolder class. +/* +** Returns the ID of the "nativePointer" field from the given +** NativePointerHolder class. */ static jfieldID NativePointerHolder_getField(JNIEnv * const env, S3NphRef const* pRef){ S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef); @@ -1058,10 +1060,10 @@ static jfieldID NativePointerHolder_getField(JNIEnv * const env, S3NphRef const* return pNC->fidValue; } -/** - Sets a native ptr value in NativePointerHolder object ppOut. - zClassName must be a static string so we can use its address - as a cache key. +/* +** Sets a native ptr value in NativePointerHolder object ppOut. +** zClassName must be a static string so we can use its address +** as a cache key. */ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, S3NphRef const* pRef){ @@ -1070,10 +1072,10 @@ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer."); } -/** - Fetches a native ptr value from NativePointerHolder object ppOut. - zClassName must be a static string so we can use its address as a - cache key. This is a no-op if pObj is NULL. +/* +** Fetches a native ptr value from NativePointerHolder object ppOut. +** zClassName must be a static string so we can use its address as a +** cache key. This is a no-op if pObj is NULL. */ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, S3NphRef const* pRef){ if( pObj ){ @@ -1087,17 +1089,17 @@ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, S3NphRef const } } -/** - Extracts the new S3JniDb instance from the free-list, or allocates - one if needed, associats it with pDb, and returns. Returns NULL on - OOM. pDb MUST, on success of the calling operation, subsequently be - associated with jDb via NativePointerHolder_set(). +/* +** Extracts the new S3JniDb instance from the free-list, or allocates +** one if needed, associats it with pDb, and returns. Returns NULL on +** OOM. pDb MUST, on success of the calling operation, subsequently be +** associated with jDb via NativePointerHolder_set(). */ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, jobject jDb){ S3JniDb * rv; MUTEX_PDB_ENTER; - if(SJG.perDb.aFree){ + if( SJG.perDb.aFree ){ rv = SJG.perDb.aFree; SJG.perDb.aFree = rv->pNext; assert(rv->pNext != rv); @@ -1112,13 +1114,12 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, s3jni_incr( &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){ + if( rv ){ memset(rv, 0, sizeof(S3JniDb)); s3jni_incr( &SJG.metrics.nPdbAlloc ); } } - if(rv){ + if( rv ){ rv->pNext = SJG.perDb.aUsed; SJG.perDb.aUsed = rv; if(rv->pNext){ @@ -1132,16 +1133,16 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, return rv; } -/** - Returns the S3JniDb object for the given db. - - The 3rd argument should normally only be non-0 for routines which - are called from the C library and pass a native db handle instead of - a Java handle. In normal usage, the 2nd argument is a Java-side sqlite3 - object, from which the db is fished out. - - Returns NULL if jDb and pDb are both NULL or if there is no - matching S3JniDb entry for pDb or the pointer fished out of jDb. +/* +** Returns the S3JniDb object for the given db. +** +** The 3rd argument should normally only be non-0 for routines which +** are called from the C library and pass a native db handle instead of +** a Java handle. In normal usage, the 2nd argument is a Java-side sqlite3 +** object, from which the db is fished out. +** +** Returns NULL if jDb and pDb are both NULL or if there is no +** matching S3JniDb entry for pDb or the pointer fished out of jDb. */ static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){ S3JniDb * s = 0; @@ -1162,8 +1163,8 @@ static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){ return s; } -/** - Unref any Java-side state in ax. +/* +** Unref any Java-side state in ax and zero out ax. */ static void S3JniAutoExtension_clear(JNIEnv * const env, S3JniAutoExtension * const ax){ @@ -1173,31 +1174,27 @@ static void S3JniAutoExtension_clear(JNIEnv * const env, } } -/** - Initializes a pre-allocated S3JniAutoExtension object. Returns - non-0 if there is an error collecting the required state from - jAutoExt (which must be an AutoExtension object). On error, it - passes ax to S3JniAutoExtension_clear(). +/* +** Initializes a pre-allocated S3JniAutoExtension object. Returns +** non-0 if there is an error collecting the required state from +** jAutoExt (which must be an AutoExtension object). On error, it +** passes ax to S3JniAutoExtension_clear(). */ static int S3JniAutoExtension_init(JNIEnv *const env, S3JniAutoExtension * const ax, jobject const jAutoExt){ jclass klazz; klazz = (*env)->GetObjectClass(env, jAutoExt); - IFTHREW{ - EXCEPTION_REPORT; - EXCEPTION_CLEAR; - assert(!klazz); - } if(!klazz){ S3JniAutoExtension_clear(env, ax); return SQLITE_ERROR; } ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", "(Lorg/sqlite/jni/sqlite3;)I"); - //UNREF_L(klazz); + EXCEPTION_WARN_IGNORE; + UNREF_L(klazz); if(!ax->midFunc){ - MARKER(("Error getting xEntryPoint(sqlite3) from object.")); + MARKER(("Error getting xEntryPoint(sqlite3) from AutoExtension object.")); S3JniAutoExtension_clear(env, ax); return SQLITE_ERROR; } @@ -1205,23 +1202,23 @@ static int S3JniAutoExtension_init(JNIEnv *const env, return 0; } -/** - Requires that jCx be a Java-side sqlite3_context wrapper for pCx. - This function calls sqlite3_aggregate_context() to allocate a tiny - sliver of memory, the address of which is set in - jCx->aggregateContext. The memory is only used as a key for - mapping client-side results of aggregate result sets across - calls to the UDF's callbacks. - - isFinal must be 1 for xFinal() calls and 0 for all others, the - difference being that the xFinal() invocation will not allocate - new memory if it was not already, resulting in a value of 0 - for jCx->aggregateContext. - - Returns 0 on success. Returns SQLITE_NOMEM on allocation error, - noting that it will not allocate when isFinal is true. It returns - SQLITE_ERROR if there's a serious internal error in dealing with - the JNI state. +/* +** Requires that jCx be a Java-side sqlite3_context wrapper for pCx. +** This function calls sqlite3_aggregate_context() to allocate a tiny +** sliver of memory, the address of which is set in +** jCx->aggregateContext. The memory is only used as a key for +** mapping client-side results of aggregate result sets across +** calls to the UDF's callbacks. +** +** isFinal must be 1 for xFinal() calls and 0 for all others, the +** difference being that the xFinal() invocation will not allocate +** new memory if it was not already, resulting in a value of 0 +** for jCx->aggregateContext. +** +** Returns 0 on success. Returns SQLITE_NOMEM on allocation error, +** noting that it will not allocate when isFinal is true. It returns +** SQLITE_ERROR if there's a serious internal error in dealing with +** the JNI state. */ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, sqlite3_context * pCx, @@ -1249,18 +1246,18 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, return rc; } -/** - Common init for OutputPointer_set_Int32() and friends. pRef must be - a pointer from S3NphRefs. jOut must be an instance of that - class. If necessary, this fetches the jfieldID for jOut's [value] - property, which must be of the type represented by the JNI type - signature zTypeSig, and stores it in pRef's S3JniGlobal.nph entry. - Fails fatally if the property is not found, as that presents a - serious internal misuse. - - Property lookups are cached on a per-pRef basis. Do not use this - routine with the same pRef but different zTypeSig: it will - misbehave. +/* +** Common init for OutputPointer_set_Int32() and friends. pRef must be +** a pointer from S3NphRefs. jOut must be an instance of that +** class. If necessary, this fetches the jfieldID for jOut's [value] +** property, which must be of the type represented by the JNI type +** signature zTypeSig, and stores it in pRef's S3JniGlobal.nph entry. +** Fails fatally if the property is not found, as that presents a +** serious internal misuse. +** +** Property lookups are cached on a per-pRef basis. Do not use this +** routine with the same pRef but different zTypeSig: it will +** misbehave. */ static jfieldID setupOutputPointer(JNIEnv * const env, S3NphRef const * pRef, const char * const zTypeSig, @@ -1277,8 +1274,10 @@ static jfieldID setupOutputPointer(JNIEnv * const env, S3NphRef const * pRef, return pNC->fidValue; } -/* Sets the value property of the OutputPointer.Int32 jOut object - to v. */ +/* +** Sets the value property of the OutputPointer.Int32 jOut object to +** v. +*/ static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){ jfieldID const setter = setupOutputPointer( env, &S3NphRefs.OutputPointer_Int32, "I", jOut @@ -1287,8 +1286,10 @@ static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } -/* Sets the value property of the OutputPointer.Int64 jOut object - to v. */ +/* +** Sets the value property of the OutputPointer.Int64 jOut object to +** v. +*/ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ jfieldID const setter = setupOutputPointer( env, &S3NphRefs.OutputPointer_Int64, "J", jOut @@ -1297,6 +1298,10 @@ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlon EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); } +/* +** Sets the value property of the OutputPointer.sqlite3 jOut object to +** v. +*/ static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, jobject jDb){ jfieldID const setter = setupOutputPointer( @@ -1306,6 +1311,10 @@ static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value"); } +/* +** Sets the value property of the OutputPointer.sqlite3_stmt jOut object to +** v. +*/ static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, jobject jStmt){ jfieldID const setter = setupOutputPointer( @@ -1318,8 +1327,10 @@ static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOu #ifdef SQLITE_ENABLE_FTS5 #if 0 -/* Sets the value property of the OutputPointer.ByteArray jOut object - to v. */ +/* +** Sets the value property of the OutputPointer.ByteArray jOut object +** to v. +*/ static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, jbyteArray const v){ jfieldID const setter = setupOutputPointer( @@ -1330,8 +1341,10 @@ static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, } #endif -/* Sets the value property of the OutputPointer.String jOut object - to v. */ +/* +** Sets the value property of the OutputPointer.String jOut object to +** v. +*/ static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, jstring const v){ jfieldID const setter = setupOutputPointer( @@ -1342,6 +1355,10 @@ static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, } #endif /* SQLITE_ENABLE_FTS5 */ +/* +** Returns true if eTextRep is a valid sqlite3 encoding constant, else +** returns false. +*/ static int encodingTypeIsValid(int eTextRep){ switch(eTextRep){ case SQLITE_UTF8: case SQLITE_UTF16: @@ -1352,6 +1369,9 @@ static int encodingTypeIsValid(int eTextRep){ } } +/* +** Proxy for Java-side Collation.xCompare() callbacks. +*/ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, int nRhs, const void *rhs){ S3JniDb * const ps = pArg; @@ -1359,11 +1379,10 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, jint rc = 0; jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL; - //MARKER(("native xCompare nLhs=%d nRhs=%d\n", nLhs, nRhs)); if(!jbaRhs){ + UNREF_L(jbaLhs); s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); return 0; - //(*env)->FatalError(env, "Out of memory. Cannot allocate arrays for collation."); } (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); @@ -1386,7 +1405,8 @@ static void CollationState_xDestroy(void *pArg){ ** sqlite3_value_java_object(). ** ** TODO: this middle-man struct is no longer necessary. Conider -** removing it and passing around jObj itself instead. +** removing it and passing around jObj itself instead. OTOH, we might +** find more state to pack in here. */ typedef struct { jobject jObj; @@ -1395,6 +1415,11 @@ typedef struct { /* For use with sqlite3_result/value_pointer() */ #define ResultJavaValuePtrStr "ResultJavaVal" +/* +** Allocate a new ResultJavaVal and assign it a new global ref of +** jObj. Caller owns the returned object and must eventually pass it +** to ResultJavaVal_finalizer(). +*/ static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal)); if(rv){ @@ -1403,6 +1428,10 @@ static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ return rv; } +/* +** If v is not NULL, it must point to a a ResultJavaVal object. Its +** object reference is relinquished and v is freed. +*/ static void ResultJavaVal_finalizer(void *v){ if(v){ ResultJavaVal * const rv = (ResultJavaVal*)v; @@ -1414,16 +1443,16 @@ static void ResultJavaVal_finalizer(void *v){ -/** - Returns a new Java instance of the class named by zClassName, which - MUST be interface-compatible with NativePointerHolder and MUST have - a no-arg constructor. The NativePointerHolder_set() method is - passed the new Java object and pNative. Hypothetically returns NULL - if Java fails to allocate, but the JNI docs are not entirely clear - on that detail. - - Always use an static pointer from the S3NphRefs struct for the 2nd - argument so that we can use its address as a cache key. +/* +** Returns a new Java instance of the class named by zClassName, which +** MUST be interface-compatible with NativePointerHolder and MUST have +** a no-arg constructor. The NativePointerHolder_set() method is +** passed the new Java object and pNative. Hypothetically returns NULL +** if Java fails to allocate, but the JNI docs are not entirely clear +** on that detail. +** +** Always use a static pointer from the S3NphRefs struct for the 2nd +** argument so that we can use pRef->index as an O(1) cache key. */ static jobject new_NativePointerHolder_object(JNIEnv * const env, S3NphRef const * pRef, const void * pNative){ @@ -1457,6 +1486,9 @@ static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_valu return new_NativePointerHolder_object(env, &S3NphRefs.sqlite3_value, sv); } +/* +** Type IDs for SQL function categories. +*/ enum UDFType { UDF_SCALAR = 1, UDF_AGGREGATE, @@ -1621,9 +1653,9 @@ static int udf_report_exception(JNIEnv * const env, int translateToErr, return rc; } -/** - Sets up the state for calling a Java-side xFunc/xStep/xInverse() - UDF, calls it, and returns 0 on success. +/* +** Sets up the state for calling a Java-side xFunc/xStep/xInverse() +** UDF, calls it, and returns 0 on success. */ static int udf_xFSI(sqlite3_context* const pCx, int argc, sqlite3_value** const argv, @@ -1651,9 +1683,9 @@ static int udf_xFSI(sqlite3_context* const pCx, int argc, return rc; } -/** - Sets up the state for calling a Java-side xFinal/xValue() UDF, - calls it, and returns 0 on success. +/* +** Sets up the state for calling a Java-side xFinal/xValue() UDF, +** calls it, and returns 0 on success. */ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, jmethodID xMethodID, @@ -1682,28 +1714,33 @@ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, return rc; } +/* Proxy for C-to-Java xFunc. */ static void udf_xFunc(sqlite3_context* cx, int argc, sqlite3_value** argv){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); s3jni_incr( &SJG.metrics.udf.nFunc ); udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc"); } +/* Proxy for C-to-Java xStep. */ static void udf_xStep(sqlite3_context* cx, int argc, sqlite3_value** argv){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); s3jni_incr( &SJG.metrics.udf.nStep ); udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep"); } +/* Proxy for C-to-Java xFinal. */ static void udf_xFinal(sqlite3_context* cx){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); s3jni_incr( &SJG.metrics.udf.nFinal ); udf_xFV(cx, s, s->jmidxFinal, "xFinal"); } +/* Proxy for C-to-Java xValue. */ static void udf_xValue(sqlite3_context* cx){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); s3jni_incr( &SJG.metrics.udf.nValue ); udf_xFV(cx, s, s->jmidxValue, "xValue"); } +/* Proxy for C-to-Java xInverse. */ static void udf_xInverse(sqlite3_context* cx, int argc, sqlite3_value** argv){ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); @@ -1714,8 +1751,8 @@ static void udf_xInverse(sqlite3_context* cx, int argc, //////////////////////////////////////////////////////////////////////// // What follows is the JNI/C bindings. They are in alphabetical order -// except for this macro-generated subset which are kept together here -// at the front... +// except for this macro-generated subset which are kept together +// (alphabetized) here at the front... //////////////////////////////////////////////////////////////////////// WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count) WRAP_INT_DB(1changes, sqlite3_changes) @@ -1767,7 +1804,6 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, return SQLITE_ERROR; } jc->pdbOpening = 0; - //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); assert( !ps->pDb && "it's still being opened" ); ps->pDb = pDb; assert( ps->jDb ); @@ -1787,14 +1823,13 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, } MUTEX_EXT_LEAVE; if( ax.jObj ){ - //MARKER(("Running auto-ext #%d from env@%p\n", i, env)); rc = (*env)->CallIntMethod(env, ax.jObj, ax.midFunc, ps->jDb); IFTHREW { jthrowable const ex = (*env)->ExceptionOccurred(env); char * zMsg; EXCEPTION_CLEAR; zMsg = s3jni_exception_error_msg(env, ex); - //UNREF_L(ex); + UNREF_L(ex); *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); sqlite3_free(zMsg); if( !rc ) rc = SQLITE_ERROR; @@ -1925,6 +1960,7 @@ JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); } +/* Central C-to-Java busy handler proxy. */ static int s3jni_busy_handler(void* pState, int n){ S3JniDb * const ps = (S3JniDb *)pState; int rc = 0; @@ -1933,9 +1969,9 @@ static int s3jni_busy_handler(void* pState, int n){ rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, ps->busyHandler.midCallback, (jint)n); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("busy-handler callback"); - EXCEPTION_CLEAR; - rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "busy-handle callback threw."); + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_busy_handler() callback"); + rc = s3jni_db_exception(env, ps, SQLITE_ERROR, + "sqlite3_busy_handler() callback threw."); } } return rc; @@ -2004,9 +2040,7 @@ JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ } -/** - Wrapper for sqlite3_close(_v2)(). -*/ +/* Wrapper for sqlite3_close(_v2)(). */ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ int rc = 0; S3JniDb * ps = 0; @@ -2031,9 +2065,9 @@ JDECL(jint,1close)(JENV_CSELF, jobject pDb){ return s3jni_close_db(env, pDb, 1); } -/** - Assumes z is an array of unsigned short and returns the index in - that array of the first element with the value 0. +/* +** Assumes z is an array of unsigned short and returns the index in +** that array of the first element with the value 0. */ static unsigned int s3jni_utf16_strlen(void const * z){ unsigned int i = 0; @@ -2042,9 +2076,7 @@ static unsigned int s3jni_utf16_strlen(void const * z){ return i; } -/** - sqlite3_collation_needed16() hook impl. - */ +/* Central C-to-Java sqlite3_collation_needed16() hook impl. */ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, int eTextRep, const void * z16Name){ S3JniDb * const ps = pState; @@ -2059,10 +2091,10 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, ps->collationNeeded.midCallback, ps->jDb, (jint)eTextRep, jName); IFTHREW{ - s3jni_db_exception(env, ps, 0, "collation-needed callback threw"); + s3jni_db_exception(env, ps, 0, "sqlite3_collation_needed() callback threw"); } + UNREF_L(jName); } - UNREF_L(jName); } JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ @@ -2281,8 +2313,8 @@ JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, return (jint)rc; } -static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, - jint nArg, jint eTextRep, jobject jFunctor){ +JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, + jint nArg, jint eTextRep, jobject jFunctor){ S3JniUdf * s = 0; int rc; sqlite3 * const pDb = PtrGet_sqlite3(jDb); @@ -2290,7 +2322,7 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, if( !encodingTypeIsValid(eTextRep) ){ return s3jni_db_error(pDb, SQLITE_FORMAT, - "Invalid function encoding option."); + "Invalid function encoding option."); } s = S3JniUdf_alloc(env, jFunctor); if( !s ) return SQLITE_NOMEM; @@ -2327,20 +2359,16 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, if( 0==rc ){ s->zFuncName = sqlite3_mprintf("%s", zFuncName) /* OOM here is non-fatal. Ignore it. Handling it would require - re-calling the appropriate create_function() func with 0 - for all xAbc args so that s would be finalized. */; + ** re-calling the appropriate create_function() func with 0 + ** for all xAbc args so that s would be finalized. */; } error_cleanup: JSTR_RELEASE(jFuncName, zFuncName); - /* on create_function() error, s will be destroyed via create_function() */ + /* on sqlite3_create_function() error, s will be destroyed via + ** create_function(), so we're not leaking s. */ return (jint)rc; } -JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, - jint nArg, jint eTextRep, jobject jFunctor){ - return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor); -} - /* sqlite3_db_config() for (int,const char *) */ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( JENV_CSELF, jobject jDb, jint op, jstring jStr @@ -2371,8 +2399,11 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( } /* sqlite3_db_config() for (int,int*) */ -/* ACHTUNG: openjdk v19 creates a different mangled name for this - function than openjdk v8 does. */ +/* +** ACHTUNG: openjdk v19 creates a different mangled name for this +** function than openjdk v8 does. We account for that by exporting +** both versions of the name. +*/ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut ){ @@ -2410,11 +2441,11 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer return (jint)rc; } -/** - This is a workaround for openjdk v19 (and possibly others) encoding - this function's name differently than JDK v8 does. If we do not - install both names for this function then Java will not be able to - find the function in both environments. +/* +** This is a workaround for openjdk v19 (and possibly others) encoding +** this function's name differently than JDK v8 does. If we do not +** install both names for this function then Java will not be able to +** find the function in both environments. */ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)( JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut @@ -2472,8 +2503,8 @@ JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){ return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)) - /* We know these values to be plain ASCII, so pose no - MUTF-8 incompatibility */; + /* We know these values to be plain ASCII, so pose no MUTF-8 + ** incompatibility */; } JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){ @@ -2564,16 +2595,16 @@ end: return rc; } -/** - Post-open() code common to both the sqlite3_open() and - sqlite3_open_v2() bindings. ps->jDb must be the - org.sqlite.jni.sqlite3 object which will hold the db's native - pointer. theRc must be the result code of the open() op. If - *ppDb is NULL then ps is set aside and its state cleared, - else ps is associated with *ppDb. If *ppDb is not NULL then - ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance). - - Returns theRc. +/* +** Post-open() code common to both the sqlite3_open() and +** sqlite3_open_v2() bindings. ps->jDb must be the +** org.sqlite.jni.sqlite3 object which will hold the db's native +** pointer. theRc must be the result code of the open() op. If +** *ppDb is NULL then ps is set aside and its state cleared, +** else ps is associated with *ppDb. If *ppDb is not NULL then +** ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance). +** +** Returns theRc. */ static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc, S3JniDb * ps, sqlite3 **ppDb, @@ -2673,7 +2704,8 @@ end: JBA_RELEASE(baSql,pBuf); if( 0==rc ){ if( 0!=outTail ){ - /* Noting that pBuf is deallocated now but its address is all we need. */ + /* Noting that pBuf is deallocated now but its address is all we need for + ** what follows... */ assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1); assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); @@ -2681,7 +2713,7 @@ end: if( pStmt ){ NativePointerHolder_set(env, jStmt, pStmt, &S3NphRefs.sqlite3_stmt); }else{ - /* Happens for comments and whitespace */ + /* Happens for comments and whitespace. */ UNREF_L(jStmt); jStmt = 0; } @@ -2713,7 +2745,7 @@ JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArra prepFlags, jOutStmt, outTail); } - +/* Central C-to-Java sqlite3_progress_handler() proxy. */ static int s3jni_progress_handler_impl(void *pP){ S3JniDb * const ps = (S3JniDb *)pP; LocalJniGetEnv; @@ -2767,6 +2799,7 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ return rc; } +/* Clears all entries from S3JniGlobal.autoExt. */ static void s3jni_reset_auto_extension(JNIEnv *env){ int i; MUTEX_EXT_ENTER; @@ -3163,8 +3196,8 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ (jint)traceflag, jP, jX); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback"); - EXCEPTION_CLEAR; - rc = SQLITE_ERROR; + rc = s3jni_db_exception(env, ps, SQLITE_ERROR, + "sqlite3_trace_v2() callback threw."); } UNREF_L(jPUnref); UNREF_L(jX); @@ -3210,8 +3243,9 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, ps->updateHook.midCallback, (jint)opId, jDbName, jTable, (jlong)nRowid); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("update hook"); - s3jni_db_exception(env, ps, 0, "update hook callback threw"); + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_update_hook() callback"); + s3jni_db_exception(env, ps, 0, + "sqlite3_update_hook() callback threw"); } } UNREF_L(jDbName); @@ -3469,15 +3503,14 @@ static void Fts5JniAux_xDestroy(void *p){ static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ Fts5JniAux * s = sqlite3_malloc(sizeof(Fts5JniAux)); if(s){ - const char * zSig = - "(Lorg/sqlite/jni/Fts5ExtensionApi;" - "Lorg/sqlite/jni/Fts5Context;" - "Lorg/sqlite/jni/sqlite3_context;" - "[Lorg/sqlite/jni/sqlite3_value;)V"; memset(s, 0, sizeof(Fts5JniAux)); s->jObj = REF_G(jObj); s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); - s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig); + s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", + "(Lorg/sqlite/jni/Fts5ExtensionApi;" + "Lorg/sqlite/jni/Fts5Context;" + "Lorg/sqlite/jni/sqlite3_context;" + "[Lorg/sqlite/jni/sqlite3_value;)V"); IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; @@ -3600,9 +3633,9 @@ JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jO return (jint)rc; } -/** - Proxy for fts5_extension_function instances plugged in via - fts5_api::xCreateFunction(). +/* +** Proxy for fts5_extension_function instances plugged in via +** fts5_api::xCreateFunction(). */ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, Fts5Context *pFts, @@ -3821,11 +3854,8 @@ JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){ return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); } -/** - State for use with xQueryPhrase() and xTokenize(). -*/ +/* State for use with xQueryPhrase() and xTokenize(). */ struct s3jni_xQueryPhraseState { - JNIEnv *env; Fts5ExtensionApi const * fext; S3JniEnv const * jc; jmethodID midCallback; @@ -3849,7 +3879,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, SJG.fts5.jFtsExt, s->jFcx); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase callback"); + EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase() callback"); EXCEPTION_CLEAR; rc = SQLITE_ERROR; } @@ -3864,7 +3894,6 @@ JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; if( !klazz ) return SQLITE_MISUSE; - s.env = env; s.jc = jc; s.jCallback = jCallback; s.jFcx = jFcx; @@ -3898,8 +3927,8 @@ JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ pAux = sqlite3_malloc(sizeof(*pAux)); if(!pAux){ if(jAux){ - // Emulate how xSetAuxdata() behaves when it cannot alloc - // its auxdata wrapper. + /* Emulate how xSetAuxdata() behaves when it cannot alloc + ** its auxdata wrapper. */ s3jni_call_xDestroy(env, jAux, 0); } return SQLITE_NOMEM; @@ -3910,9 +3939,7 @@ JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ return rc; } -/** - xToken() impl for xTokenize(). -*/ +/* xToken() impl for xTokenize(). */ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, int nZ, int iStart, int iEnd){ int rc; @@ -3938,8 +3965,9 @@ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, return rc; } -/** - Proxy for Fts5ExtensionApi.xTokenize() and fts5_tokenizer.xTokenize() +/* +** Proxy for Fts5ExtensionApi.xTokenize() and +** fts5_tokenizer.xTokenize() */ static jint s3jni_fts5_xTokenize(JENV_OSELF, S3NphRef const *pRef, jint tokFlags, jobject jFcx, @@ -3954,7 +3982,6 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, S3NphRef const *pRef, if( !klazz ) return SQLITE_MISUSE; memset(&s, 0, sizeof(s)); - s.env = env; s.jc = jc; s.jCallback = jCallback; s.jFcx = jFcx; @@ -4216,11 +4243,11 @@ Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ //////////////////////////////////////////////////////////////////////// -/** - Uncaches the current JNIEnv from the S3JniGlobal state, clearing any - resources owned by that cache entry and making that slot available - for re-use. It is important that the Java-side decl of this - function be declared as synchronous. +/* +** Uncaches the current JNIEnv from the S3JniGlobal state, clearing any +** resources owned by that cache entry and making that slot available +** for re-use. It is important that the Java-side decl of this +** function be declared as synchronous. */ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ @@ -4231,12 +4258,12 @@ Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ return rc ? JNI_TRUE : JNI_FALSE; } -/** - Called during static init of the SQLite3Jni class to sync certain - compile-time constants to Java-space. - - This routine is part of the reason why we have to #include - sqlite3.c instead of sqlite3.h. +/* +** Called during static init of the SQLite3Jni class to sync certain +** compile-time constants to Java-space. +** +** This routine is part of the reason why we have to #include +** sqlite3.c instead of sqlite3.h. */ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ @@ -4321,14 +4348,13 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ { /* StandardCharsets.UTF_8 */ jfieldID fUtf8; - jclass const klazzSC = - (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); + klazz = (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class."); - fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", + fUtf8 = (*env)->GetStaticFieldID(env, klazz, "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)); + REF_G((*env)->GetStaticObjectField(env, klazz, fUtf8)); EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); } @@ -4354,22 +4380,15 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ OOM_CHECK( SJG.metrics.mutex ); #endif -#if 0 - /* Just for sanity checking... */ - (void)S3JniGlobal_env_cache(env); - if( !SJG.envCache.aHead ){ - (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache."); - return; - } - assert( 1 == SJG.metrics.envCacheMisses ); - assert( env == SJG.envCache.aHead->env ); -#endif + sqlite3_shutdown() + /* So that it becomes legal for Java-level code to call + ** sqlite3_config(), if it's ever implemented. */; + /* Set up static "consts" of the SQLite3Jni class. */ for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; fieldId = (*env)->GetStaticFieldID(env, jKlazz, pConfFlag->zName, zSig); EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class."); - //MARKER(("Setting %s (field=%p) = %d\n", pConfFlag->zName, fieldId, pConfFlag->value)); assert(fieldId); switch(pConfFlag->jtype){ case JTYPE_INT: diff --git a/manifest b/manifest index 56dd2871d2..b7b5f63421 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improve\sC-side\sexception\shandling\sfrom\sJava-side\sUDF\scallbacks. -D 2023-08-23T00:17:28.362 +C Numerous\sminor\scleanups\sand\scode\sstyle\sconformance\simprovements. +D 2023-08-23T09:05:16.380 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 a38d7c5ad4ab1e209e2a9352ff06add1d9a0bc666351714bfc8858625330c16b F ext/jni/README.md ddcc6be0c0d65f1e2fd687de9f40d38c82630fd61f83cc9550773caa19dd8be1 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c 8e7dac68246edec8d85e34104d5c749c544ae10c55b1bbf6eeff116447da7f54 +F ext/jni/src/c/sqlite3-jni.c aba2bf845c512b835c795263c2c460faaa926967b497d7dbcf3cdbe98a2d0396 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 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd @@ -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 70d936953ba55cfed32efe7e3a9d4b71da9a7ffc8818b6540471e0bf311bc688 -R f4b3990a7d714ecf1a549380689ce9aa +P aebbc24afb339ed07b7cd767fbc0d25f3e9c3d9bb5ef3d1c10b04b605c7261bc +R 8d2509e7997f13cf4f5b727ee9382226 U stephan -Z b8494717453324d170d4924e0442cf8e +Z 2c18e9ca7bc91670a10ef4d3cc234c7a # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index d1c49b53b7..15b46f2538 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -aebbc24afb339ed07b7cd767fbc0d25f3e9c3d9bb5ef3d1c10b04b605c7261bc \ No newline at end of file +6c92d884920e4ace54913fc60ceef6e43a4351f45a4cb3c4a0ed3d29d544a31b \ No newline at end of file From 4e97ab42968863298de499151c3f073a4b749a36 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 23 Aug 2023 10:36:12 +0000 Subject: [PATCH 31/37] Bind a subset of sqlite3_config() to JNI: threading modes and sqllog. FossilOrigin-Name: fce8ecaf7f2e69a168978e6993e58c452c45f76c39da33f2869c9d947c16cab1 --- ext/jni/GNUmakefile | 2 + ext/jni/src/c/sqlite3-jni.c | 96 ++++++++++++++++++++++ ext/jni/src/c/sqlite3-jni.h | 16 ++++ ext/jni/src/org/sqlite/jni/SQLLog.java | 25 ++++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 25 ++++++ ext/jni/src/org/sqlite/jni/Tester1.java | 22 +++++ manifest | 21 ++--- manifest.uuid | 2 +- 8 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/SQLLog.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index f1a6c552e9..1d77fc5f74 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -68,6 +68,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\ ResultCode.java \ RollbackHook.java \ SQLFunction.java \ + SQLLog.java \ sqlite3_context.java \ sqlite3.java \ SQLite3Jni.java \ @@ -160,6 +161,7 @@ SQLITE_OPT = \ -DSQLITE_ENABLE_DBSTAT_VTAB \ -DSQLITE_ENABLE_BYTECODE_VTAB \ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ + -DSQLITE_ENABLE_SQLLOG \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_SHARED_CACHE \ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index e6a4b71c30..543f3be549 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -503,6 +503,11 @@ struct S3JniGlobalType { always have this set to the current JNIEnv object. Used only for sanity checking. */; } perDb; +#ifdef SQLITE_ENABLE_SQLLOG + struct { + S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */; + } hooks; +#endif /* ** Refs to global classes and methods. Obtained during static init ** and never released. @@ -2275,6 +2280,97 @@ JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ return rc; } +/* +** sqlite3_config(SQLITE_CONFIG_...) wrapper for a small subset of +** options. +*/ +JDECL(jint,1config__I)(JENV_CSELF, jint n){ + switch(n){ + case SQLITE_CONFIG_SINGLETHREAD: + case SQLITE_CONFIG_MULTITHREAD: + case SQLITE_CONFIG_SERIALIZED: + return sqlite3_config( n ); + default: + return SQLITE_MISUSE; + } +} + +#ifdef SQLITE_ENABLE_SQLLOG +/* C-to-Java SQLITE_CONFIG_SQLLOG wrapper. */ +static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int op){ + jobject jArg0 = 0; + jstring jArg1 = 0; + LocalJniGetEnv; + S3JniEnv * const jc = S3JniGlobal_env_cache(env); + S3JniDb * const ps = S3JniDb_for_db(env, 0, pDb); + S3JniHook * const hook = &SJG.hooks.sqllog; + + if( !ps || !hook->jObj ) return; + jArg0 = REF_L(ps->jDb); + switch(op){ + case 0: /* db opened */ + case 1: /* SQL executed */ + jArg1 = s3jni_utf8_to_jstring(jc, z, -1); + break; + case 2: /* db closed */ + break; + default: + (*env)->FatalError(env, "Unhandled 4th arg to SQLITE_CONFIG_SQLLOG."); + break; + } + (*env)->CallVoidMethod(env, hook->jObj, hook->midCallback, jArg0, jArg1, op); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("SQLITE_CONFIG_SQLLOG callback"); + EXCEPTION_CLEAR; + } + UNREF_L(jArg0); + UNREF_L(jArg1); +} +//! Requirement of SQLITE_CONFIG_SQLLOG. +void sqlite3_init_sqllog(void){ + sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 ); +} +#endif + +/* sqlite3_config(SQLITE_CONFIG_SQLLOG) wrapper. */ +JDECL(jint,1config__Lorg_sqlite_jni_SQLLog_2)(JENV_CSELF, jobject jLog){ +#ifdef SQLITE_ENABLE_SQLLOG + S3JniHook tmpHook; + S3JniHook * const hook = &tmpHook; + S3JniHook * const hookOld = & SJG.hooks.sqllog; + int rc = 0; + if( !jLog ){ + S3JniHook_unref(env, hookOld, 0); + return 0; + } + if( hookOld->jObj && (*env)->IsSameObject(env, jLog, hookOld->jObj) ){ + return 0; + } + hook->klazz = REF_G( (*env)->GetObjectClass(env, jLog) ); + hook->midCallback = (*env)->GetMethodID(env, hook->klazz, "xSqllog", + "(Lorg/sqlite/jni/sqlite3;" + "Ljava/lang/String;" + "I)V"); + if( !hook->midCallback ){ + EXCEPTION_WARN_IGNORE; + S3JniHook_unref(env, hook, 0); + return SQLITE_ERROR; + } + hook->jObj = REF_G(jLog); + rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 ); + if( rc ){ + S3JniHook_unref(env, hook, 0); + }else{ + S3JniHook_unref(env, hookOld, 0); + *hookOld = *hook; + } + return rc; +#else + MARKER(("Warning: built without SQLITE_ENABLE_SQLLOG.\n")); + return SQLITE_RANGE; +#endif +} + JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb) : 0; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index e189df6947..0a8736f2a7 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1083,6 +1083,22 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1used (JNIEnv *, jclass, jstring); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_config + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1config__I + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_config + * Signature: (Lorg/sqlite/jni/SQLLog;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1config__Lorg_sqlite_jni_SQLLog_2 + (JNIEnv *, jclass, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_create_collation diff --git a/ext/jni/src/org/sqlite/jni/SQLLog.java b/ext/jni/src/org/sqlite/jni/SQLLog.java new file mode 100644 index 0000000000..c1bc0aab61 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/SQLLog.java @@ -0,0 +1,25 @@ +/* +** 2023-08-23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A callback for use with sqlite3_config(SQLLog). +*/ +public interface SQLLog { + /** + Must function as described for sqlite3_config(SQLITE_CONFIG_SQLLOG) + callback, with the slight signature change. + */ + void xSqllog(sqlite3 db, String msg, int msgType ); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 22178faed6..e64ecf4914 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -454,6 +454,31 @@ public final class SQLite3Jni { @NotNull String optName ); + /* + ** Works like in the C API with the exception that it only supports + ** the following subset of configution flags: + ** + ** - SQLITE_CONFIG_SINGLETHREAD + ** - SQLITE_CONFIG_MULTITHREAD + ** - SQLITE_CONFIG_SERIALIZED + ** + ** Others may be added in the future. It returns SQLITE_MISUSE if + ** given an argument it does not handle. + */ + public static native int sqlite3_config(int op); + + /* + ** If the native library was built with SQLITE_ENABLE_SQLLOG defined + ** then this acts as a proxy for C's + ** sqlite3_config(SQLITE_ENABLE_SQLLOG,...). This sets or clears the + ** logger. If installation of a logger fails, any previous logger is + ** retained. + ** + ** If not built with SQLITE_ENABLE_SQLLOG defined, this returns + ** SQLITE_RANGE. + */ + public static native int sqlite3_config( @Nullable SQLLog logger ); + public static native int sqlite3_create_collation( @NotNull sqlite3 db, @NotNull String name, int eTextRep, @NotNull Collation col diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index c075a2d036..fc350814b4 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -1321,6 +1321,7 @@ public class Tester1 implements Runnable { boolean doSomethingForDev = false; Integer nRepeat = 1; boolean forceFail = false; + boolean sqlLog = false; for( int i = 0; i < args.length; ){ String arg = args[i++]; if(arg.startsWith("-")){ @@ -1338,6 +1339,8 @@ public class Tester1 implements Runnable { listRunTests = true; }else if(arg.equals("fail")){ forceFail = true; + }else if(arg.equals("sqllog")){ + sqlLog = true; }else if(arg.equals("naps")){ takeNaps = true; }else{ @@ -1366,8 +1369,27 @@ public class Tester1 implements Runnable { } } + if( sqlLog ){ + int rc = sqlite3_config( new SQLLog() { + @Override public void xSqllog(sqlite3 db, String msg, int op){ + switch(op){ + case 0: outln("Opening db: ",db); break; + case 1: outln(db,": ",msg); break; + case 2: outln("Closing db: ",db); break; + } + } + }); + affirm( 0==rc ); + } + final long timeStart = System.currentTimeMillis(); int nLoop = 0; + affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ), + "Could not switch to single-thread mode." ); + affirm( 0==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ), + "Could not switch to multithread mode." ); + affirm( 0==sqlite3_config( SQLITE_CONFIG_SERIALIZED ), + "Could not switch to serialized threading mode." ); outln("libversion_number: ", sqlite3_libversion_number(),"\n", sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); diff --git a/manifest b/manifest index b7b5f63421..ff7bb357eb 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Numerous\sminor\scleanups\sand\scode\sstyle\sconformance\simprovements. -D 2023-08-23T09:05:16.380 +C Bind\sa\ssubset\sof\ssqlite3_config()\sto\sJNI:\sthreading\smodes\sand\ssqllog. +D 2023-08-23T10:36:12.341 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,11 +232,11 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile a38d7c5ad4ab1e209e2a9352ff06add1d9a0bc666351714bfc8858625330c16b +F ext/jni/GNUmakefile 33abc2f4f4bbd5451d6be5e6f2e109c045cc326cd942d602a3908a0c7b3c6f49 F ext/jni/README.md ddcc6be0c0d65f1e2fd687de9f40d38c82630fd61f83cc9550773caa19dd8be1 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c aba2bf845c512b835c795263c2c460faaa926967b497d7dbcf3cdbe98a2d0396 -F ext/jni/src/c/sqlite3-jni.h 8b0ab1a3f0f92b75d4ff50db4a88b66a137cfb561268eb15bb3993ed174dbb74 +F ext/jni/src/c/sqlite3-jni.c 01c6cf041d1b9937a97c7700006a532d3b11fd4991931e31ffa7a777b97fba11 +F ext/jni/src/c/sqlite3-jni.h 44bcb4eb3517c089f8f24f1546ea66b350d0661a4b0fa148425c9a41cabf487d F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -255,8 +255,9 @@ 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 f697cf2a81c4119f2baf0682af689686f0466f1dd83dba00885f5603e693fe16 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2f36370cfdec01d309720392b2c3e4af6afce0b6ece8188b5c3ed688a5a1e63a -F ext/jni/src/org/sqlite/jni/Tester1.java 93bd76f2294fa2f592395c9d823adb38a9be3846bff00debeff02310f4e9e6be +F ext/jni/src/org/sqlite/jni/SQLLog.java c60610b35208416940822e834d61f08fbbe5d6e06b374b541b49e41fd56c9798 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2de5729a33cf2636160eb6893a4234c99669521a352bfffbf60410bd493ebece +F ext/jni/src/org/sqlite/jni/Tester1.java 4e17a30e9da15954ba71ef52beb5b347f312594a0facbaf86e1f29481c4dc65c F ext/jni/src/org/sqlite/jni/TesterFts5.java de095e3b701fba0c56d7b8b2993dc22bcbaa9de8f992904a93729ad729a91576 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2092,8 +2093,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 aebbc24afb339ed07b7cd767fbc0d25f3e9c3d9bb5ef3d1c10b04b605c7261bc -R 8d2509e7997f13cf4f5b727ee9382226 +P 6c92d884920e4ace54913fc60ceef6e43a4351f45a4cb3c4a0ed3d29d544a31b +R ebb24a95583279229c99fb88e45995e0 U stephan -Z 2c18e9ca7bc91670a10ef4d3cc234c7a +Z 0a740a88323f212cff509af7c6f7ae11 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 15b46f2538..23b760f68b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -6c92d884920e4ace54913fc60ceef6e43a4351f45a4cb3c4a0ed3d29d544a31b \ No newline at end of file +fce8ecaf7f2e69a168978e6993e58c452c45f76c39da33f2869c9d947c16cab1 \ No newline at end of file From bea9ed0f1f4d99b8671b5b4255ea57fab99f6280 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 23 Aug 2023 13:17:37 +0000 Subject: [PATCH 32/37] Bind sqlite3_preupdate_hook() and friends to JNI. FossilOrigin-Name: d0c425b5c1d3aac5ead18a501a3760b4506d68d373cb3be484247042cf2fa8d4 --- ext/jni/GNUmakefile | 1 + ext/jni/src/c/sqlite3-jni.c | 434 ++++++++++++------ ext/jni/src/c/sqlite3-jni.h | 48 ++ ext/jni/src/org/sqlite/jni/OutputPointer.java | 22 + ext/jni/src/org/sqlite/jni/PreUpdateHook.java | 29 ++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 65 ++- ext/jni/src/org/sqlite/jni/Tester1.java | 74 +++ manifest | 23 +- manifest.uuid | 2 +- 9 files changed, 545 insertions(+), 153 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/PreUpdateHook.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 1d77fc5f74..cc728003d7 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -161,6 +161,7 @@ SQLITE_OPT = \ -DSQLITE_ENABLE_DBSTAT_VTAB \ -DSQLITE_ENABLE_BYTECODE_VTAB \ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ + -DSQLITE_ENABLE_PREUPDATE_HOOK \ -DSQLITE_ENABLE_SQLLOG \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_DEPRECATED \ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 543f3be549..93c5d5f1d2 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -239,6 +239,7 @@ static const struct { const S3NphRef OutputPointer_ByteArray; const S3NphRef OutputPointer_sqlite3; const S3NphRef OutputPointer_sqlite3_stmt; + const S3NphRef OutputPointer_sqlite3_value; #ifdef SQLITE_ENABLE_FTS5 const S3NphRef Fts5Context; const S3NphRef Fts5ExtensionApi; @@ -248,22 +249,23 @@ static const struct { #endif } S3NphRefs = { #define NREF(INDEX, JAVANAME) { INDEX, "org/sqlite/jni/" JAVANAME } - NREF(0, "sqlite3"), - NREF(1, "sqlite3_stmt"), - NREF(2, "sqlite3_context"), - NREF(3, "sqlite3_value"), - NREF(4, "OutputPointer$Int32"), - NREF(5, "OutputPointer$Int64"), - NREF(6, "OutputPointer$String"), - NREF(7, "OutputPointer$ByteArray"), - NREF(8, "OutputPointer$sqlite3"), - NREF(9, "OutputPointer$sqlite3_stmt"), + NREF(0, "sqlite3"), + NREF(1, "sqlite3_stmt"), + NREF(2, "sqlite3_context"), + NREF(3, "sqlite3_value"), + NREF(4, "OutputPointer$Int32"), + NREF(5, "OutputPointer$Int64"), + NREF(6, "OutputPointer$String"), + NREF(7, "OutputPointer$ByteArray"), + NREF(8, "OutputPointer$sqlite3"), + NREF(9, "OutputPointer$sqlite3_stmt"), + NREF(10, "OutputPointer$sqlite3_value"), #ifdef SQLITE_ENABLE_FTS5 - NREF(10, "Fts5Context"), - NREF(11, "Fts5ExtensionApi"), - NREF(12, "fts5_api"), - NREF(13, "fts5_tokenizer"), - NREF(14, "Fts5Tokenizer") + NREF(11, "Fts5Context"), + NREF(12, "Fts5ExtensionApi"), + NREF(13, "fts5_api"), + NREF(14, "fts5_tokenizer"), + NREF(15, "Fts5Tokenizer") #endif #undef NREF }; @@ -394,15 +396,20 @@ struct S3JniDb { receive. */; char * zMainDbName /* Holds the string allocated on behalf of SQLITE_DBCONFIG_MAINDBNAME. */; - S3JniHook busyHandler; - S3JniHook collation; - S3JniHook collationNeeded; - S3JniHook commitHook; - S3JniHook progress; - S3JniHook rollbackHook; - S3JniHook trace; - S3JniHook updateHook; - S3JniHook authHook; + struct { + S3JniHook busyHandler; + S3JniHook collation; + S3JniHook collationNeeded; + S3JniHook commit; + S3JniHook progress; + S3JniHook rollback; + S3JniHook trace; + S3JniHook update; + S3JniHook auth; +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + S3JniHook preUpdate; +#endif + } hooks; #ifdef SQLITE_ENABLE_FTS5 jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */; #endif @@ -957,13 +964,16 @@ static void S3JniDb_set_aside(JNIEnv * env, S3JniDb * const s){ SJG.perDb.aUsed = s->pNext; } sqlite3_free( s->zMainDbName ); -#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY) +#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->hooks.MEMBER, XDESTROY) UNHOOK(trace, 0); UNHOOK(progress, 0); - UNHOOK(commitHook, 0); - UNHOOK(rollbackHook, 0); - UNHOOK(updateHook, 0); - UNHOOK(authHook, 0); + UNHOOK(commit, 0); + UNHOOK(rollback, 0); + UNHOOK(update, 0); + UNHOOK(auth, 0); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + UNHOOK(preUpdate, 0); +#endif UNHOOK(collation, 1); UNHOOK(collationNeeded, 1); UNHOOK(busyHandler, 1); @@ -1308,7 +1318,7 @@ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlon ** v. */ static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, - jobject jDb){ + jobject jDb){ jfieldID const setter = setupOutputPointer( env, &S3NphRefs.OutputPointer_sqlite3, "Lorg/sqlite/jni/sqlite3;", jOut ); @@ -1321,15 +1331,31 @@ static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, ** v. */ static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, - jobject jStmt){ + jobject jStmt){ jfieldID const setter = setupOutputPointer( env, &S3NphRefs.OutputPointer_sqlite3_stmt, - "Lorg/sqlite/jni/sqlite3_stmt;", jOut + "Lorg/sqlite/jni/sqlite3_stmt;", jOut ); (*env)->SetObjectField(env, jOut, setter, jStmt); EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value"); } +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** Sets the value property of the OutputPointer.sqlite3_value jOut object to +** v. +*/ +static void OutputPointer_set_sqlite3_value(JNIEnv * const env, jobject const jOut, + jobject jValue){ + jfieldID const setter = setupOutputPointer( + env, &S3NphRefs.OutputPointer_sqlite3_value, + "Lorg/sqlite/jni/sqlite3_value;", jOut + ); + (*env)->SetObjectField(env, jOut, setter, jValue); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_value.value"); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + #ifdef SQLITE_ENABLE_FTS5 #if 0 /* @@ -1391,7 +1417,8 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, } (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); - rc = (*env)->CallIntMethod(env, ps->collation.jObj, ps->collation.midCallback, + rc = (*env)->CallIntMethod(env, ps->hooks.collation.jObj, + ps->hooks.collation.midCallback, jbaLhs, jbaRhs); EXCEPTION_IGNORE; UNREF_L(jbaLhs); @@ -1402,7 +1429,7 @@ 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( s3jni_get_env(), &ps->collation, 1 ); + S3JniHook_unref( s3jni_get_env(), &ps->hooks.collation, 1 ); } /* @@ -1777,6 +1804,11 @@ WRAP_INT_DB(1error_1offset, sqlite3_error_offset) WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode) WRAP_MUTF8_VOID(1libversion, sqlite3_libversion) WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number) +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +WRAP_INT_DB(1preupdate_1blobwrite, sqlite3_preupdate_blobwrite) +WRAP_INT_DB(1preupdate_1count, sqlite3_preupdate_count) +WRAP_INT_DB(1preupdate_1depth, sqlite3_preupdate_depth) +#endif WRAP_INT_INT(1sleep, sqlite3_sleep) WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid) WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe) @@ -1969,10 +2001,10 @@ JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, static int s3jni_busy_handler(void* pState, int n){ S3JniDb * const ps = (S3JniDb *)pState; int rc = 0; - if( ps->busyHandler.jObj ){ + if( ps->hooks.busyHandler.jObj ){ LocalJniGetEnv; - rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, - ps->busyHandler.midCallback, (jint)n); + rc = (*env)->CallIntMethod(env, ps->hooks.busyHandler.jObj, + ps->hooks.busyHandler.midCallback, (jint)n); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW("sqlite3_busy_handler() callback"); rc = s3jni_db_exception(env, ps, SQLITE_ERROR, @@ -1987,7 +2019,7 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ int rc = 0; if(!ps) return (jint)SQLITE_NOMEM; if(jBusy){ - S3JniHook * const pHook = &ps->busyHandler; + S3JniHook * const pHook = &ps->hooks.busyHandler; if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){ /* Same object - this is a no-op. */ return 0; @@ -2004,7 +2036,7 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ return rc; } }else{ - S3JniHook_unref(env, &ps->busyHandler, 1); + S3JniHook_unref(env, &ps->hooks.busyHandler, 1); } return jBusy ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) @@ -2014,7 +2046,7 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); if( ps ){ - S3JniHook_unref(env, &ps->busyHandler, 1); + S3JniHook_unref(env, &ps->hooks.busyHandler, 1); return sqlite3_busy_timeout(ps->pDb, (int)ms); } return SQLITE_MISUSE; @@ -2092,8 +2124,8 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); EXCEPTION_CLEAR; }else{ - (*env)->CallVoidMethod(env, ps->collationNeeded.jObj, - ps->collationNeeded.midCallback, + (*env)->CallVoidMethod(env, ps->hooks.collationNeeded.jObj, + ps->hooks.collationNeeded.midCallback, ps->jDb, (jint)eTextRep, jName); IFTHREW{ s3jni_db_exception(env, ps, 0, "sqlite3_collation_needed() callback threw"); @@ -2107,7 +2139,7 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ jclass klazz; jobject pOld = 0; jmethodID xCallback; - S3JniHook * const pHook = &ps->collationNeeded; + S3JniHook * const pHook = &ps->hooks.collationNeeded; int rc; if( !ps ) return SQLITE_MISUSE; @@ -2192,10 +2224,10 @@ JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ LocalJniGetEnv; int rc = isCommit - ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, - ps->commitHook.midCallback) - : (int)((*env)->CallVoidMethod(env, ps->rollbackHook.jObj, - ps->rollbackHook.midCallback), 0); + ? (int)(*env)->CallIntMethod(env, ps->hooks.commit.jObj, + ps->hooks.commit.midCallback) + : (int)((*env)->CallVoidMethod(env, ps->hooks.rollback.jObj, + ps->hooks.rollback.midCallback), 0); IFTHREW{ EXCEPTION_CLEAR; rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw."); @@ -2211,13 +2243,14 @@ static void s3jni_rollback_hook_impl(void *pP){ (void)s3jni_commit_rollback_hook_impl(0, pP); } -static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, - jobject jHook){ +static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env, + jobject jDb, jobject jHook){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); jclass klazz; jobject pOld = 0; jmethodID xCallback; - S3JniHook * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; + S3JniHook * const pHook = + isCommit ? &ps->hooks.commit : &ps->hooks.rollback; if(!ps){ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); return 0; @@ -2367,7 +2400,7 @@ JDECL(jint,1config__Lorg_sqlite_jni_SQLLog_2)(JENV_CSELF, jobject jLog){ return rc; #else MARKER(("Warning: built without SQLITE_ENABLE_SQLLOG.\n")); - return SQLITE_RANGE; + return SQLITE_MISUSE; #endif } @@ -2380,13 +2413,13 @@ JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, jstring name, jint eTextRep, jobject oCollation){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); - jclass klazz; int rc; const char *zName; - S3JniHook * pHook; - if(!ps) return (jint)SQLITE_NOMEM; - pHook = &ps->collation; + jclass klazz; + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); + S3JniHook * const pHook = ps ? &ps->hooks.collation : 0; + + if( !pHook ) return SQLITE_MISUSE; klazz = (*env)->GetObjectClass(env, oCollation); pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare", "([B[B)I"); @@ -2841,12 +2874,201 @@ JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArra prepFlags, jOutStmt, outTail); } +/* +** Impl for C-to-Java of the callbacks for both sqlite3_update_hook() +** and sqlite3_preupdate_hook(). The differences are that for +** update_hook(): +** +** - pDb is NULL +** - iKey1 is the row ID +** - iKey2 is unused +*/ +static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId, + const char *zDb, const char *zTable, + sqlite3_int64 iKey1, sqlite3_int64 iKey2){ + S3JniDb * const ps = pState; + LocalJniGetEnv; + S3JniEnv * const jc = S3JniGlobal_env_cache(env); + jstring jDbName; + jstring jTable; + S3JniHook * pHook; + const int isPre = 0!=pDb; + + pHook = isPre ? +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + &ps->hooks.preUpdate +#else + 0 +#endif + : &ps->hooks.update; + + assert( pHook ); + jDbName = s3jni_utf8_to_jstring(jc, zDb, -1); + jTable = jDbName ? s3jni_utf8_to_jstring(jc, zTable, -1) : 0; + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + }else{ + assert( pHook->jObj ); + assert( pHook->midCallback ); + assert( ps->jDb ); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( isPre ) (*env)->CallVoidMethod(env, pHook->jObj, pHook->midCallback, + ps->jDb, (jint)opId, jDbName, jTable, + (jlong)iKey1, (jlong)iKey2); + else +#endif + (*env)->CallVoidMethod(env, pHook->jObj, pHook->midCallback, + (jint)opId, jDbName, jTable, (jlong)iKey1); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_(pre)update_hook() callback"); + s3jni_db_exception(env, ps, 0, + "sqlite3_(pre)update_hook() callback threw"); + } + } + UNREF_L(jDbName); + UNREF_L(jTable); +} + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +static void s3jni_preupdate_hook_impl(void * pState, sqlite3 *pDb, int opId, + const char *zDb, const char *zTable, + sqlite3_int64 iKey1, sqlite3_int64 iKey2){ + return s3jni_updatepre_hook_impl(pState, pDb, opId, zDb, zTable, + iKey1, iKey2); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, + const char *zTable, sqlite3_int64 nRowid){ + return s3jni_updatepre_hook_impl(pState, NULL, opId, zDb, zTable, nRowid, 0); +} + +#ifndef SQLITE_ENABLE_PREUPDATE_HOOK +/* We need no-op impls for preupdate_{count,depth,blobwrite}() */ +JDECL(int,1preupdate_1blobwrite)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; } +JDECL(int,1preupdate_1count)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; } +JDECL(int,1preupdate_1depth)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; } +#endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */ + +/* +** JNI wrapper for both sqlite3_update_hook() and +** sqlite3_preupdate_hook() (if isPre is true). +*/ +static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jobject jDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + S3JniHook * pHook = ps ? ( + isPre ? +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + &ps->hooks.preUpdate +#else + 0 +#endif + : &ps->hooks.update) : 0; + + if(!pHook){ + return 0; + } + pOld = pHook->jObj; + if( pOld && jHook && (*env)->IsSameObject(env, pOld, jHook) ){ + return pOld; + } + if( !jHook ){ + if( pOld ){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + memset(pHook, 0, sizeof(S3JniHook)); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( isPre ) sqlite3_preupdate_hook(ps->pDb, 0, 0); + else +#endif + sqlite3_update_hook(ps->pDb, 0, 0); + return pOld; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = isPre + ? (*env)->GetMethodID(env, klazz, "xPreUpdate", + "(Lorg/sqlite/jni/sqlite3;" + "I" + "Ljava/lang/String;" + "Ljava/lang/String;" + "JJ)V") + : (*env)->GetMethodID(env, klazz, "xUpdateHook", + "(ILjava/lang/String;Ljava/lang/String;J)V"); + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching callback on " + "(pre)update hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( isPre ) sqlite3_preupdate_hook(ps->pDb, s3jni_preupdate_hook_impl, ps); + else +#endif + sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps); + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + } + return pOld; +} + + +JDECL(jobject,1preupdate_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + return s3jni_updatepre_hook(env, 1, jDb, jHook); +#else + return NULL; +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +} + +/* Impl for sqlite3_preupdate_{new,old}(). */ +static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jobject jDb, + jint iCol, jobject jOut){ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3_value * pOut = 0; + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + int rc; + int (*fOrig)(sqlite3*,int,sqlite3_value**) = + isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old; + rc = fOrig(pDb, (int)iCol, &pOut); + if( 0==rc ){ + jobject pWrap = new_sqlite3_value_wrapper(env, pOut); + if( pWrap ){ + OutputPointer_set_sqlite3_value(env, jOut, pWrap); + UNREF_L(pWrap); + }else{ + rc = SQLITE_NOMEM; + } + } + return rc; +#else + return SQLITE_MISUSE; +#endif +} +JDECL(jint,1preupdate_1new)(JENV_CSELF, jobject jDb, jint iCol, jobject jOut){ + return s3jni_preupdate_newold(env, 1, jDb, iCol, jOut); +} +JDECL(jint,1preupdate_1old)(JENV_CSELF, jobject jDb, jint iCol, jobject jOut){ + return s3jni_preupdate_newold(env, 0, jDb, iCol, jOut); +} + + /* Central C-to-Java sqlite3_progress_handler() proxy. */ static int s3jni_progress_handler_impl(void *pP){ S3JniDb * const ps = (S3JniDb *)pP; LocalJniGetEnv; - int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj, - ps->progress.midCallback); + int rc = (int)(*env)->CallIntMethod(env, ps->hooks.progress.jObj, + ps->hooks.progress.midCallback); IFTHREW{ rc = s3jni_db_exception(env, ps, rc, "sqlite3_progress_handler() callback threw"); @@ -2860,8 +3082,8 @@ JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress jmethodID xCallback; if( n<1 || !jProgress ){ if(ps){ - UNREF_G(ps->progress.jObj); - memset(&ps->progress, 0, sizeof(ps->progress)); + UNREF_G(ps->hooks.progress.jObj); + memset(&ps->hooks.progress, 0, sizeof(ps->hooks.progress)); } sqlite3_progress_handler(ps->pDb, 0, 0, 0); return; @@ -2878,9 +3100,9 @@ JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress "Cannot not find matching xCallback() on " "ProgressHandler object."); }else{ - UNREF_G(ps->progress.jObj); - ps->progress.midCallback = xCallback; - ps->progress.jObj = REF_G(jProgress); + UNREF_G(ps->hooks.progress.jObj); + ps->hooks.progress.midCallback = xCallback; + ps->hooks.progress.jObj = REF_G(jProgress); sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps); } } @@ -3099,7 +3321,7 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, S3JniDb * const ps = pState; LocalJniGetEnv; S3JniEnv * const jc = S3JniGlobal_env_cache(env); - S3JniHook const * const pHook = &ps->authHook; + S3JniHook const * const pHook = &ps->hooks.auth; jstring const s0 = z0 ? s3jni_utf8_to_jstring(jc, z0, -1) : 0; jstring const s1 = z1 ? s3jni_utf8_to_jstring(jc, z1, -1) : 0; jstring const s2 = z2 ? s3jni_utf8_to_jstring(jc, z2, -1) : 0; @@ -3121,7 +3343,7 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); - S3JniHook * const pHook = ps ? &ps->authHook : 0; + S3JniHook * const pHook = ps ? &ps->hooks.auth : 0; if( !ps ) return SQLITE_MISUSE; else if( !jHook ){ @@ -3287,8 +3509,8 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ } } assert(jP); - rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj, - ps->trace.midCallback, + rc = (int)(*env)->CallIntMethod(env, ps->hooks.trace.jObj, + ps->hooks.trace.midCallback, (jint)traceflag, jP, jX); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback"); @@ -3303,96 +3525,28 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); jclass klazz; + if( !traceMask || !jTracer ){ if(ps){ - UNREF_G(ps->trace.jObj); - memset(&ps->trace, 0, sizeof(ps->trace)); + S3JniHook_unref(env, &ps->hooks.trace, 0); } return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0); } if(!ps) return SQLITE_NOMEM; klazz = (*env)->GetObjectClass(env, jTracer); - ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", + ps->hooks.trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "(ILjava/lang/Object;Ljava/lang/Object;)I"); IFTHREW { EXCEPTION_CLEAR; return s3jni_db_error(ps->pDb, SQLITE_ERROR, "Cannot not find matching xCallback() on Tracer object."); } - ps->trace.jObj = REF_G(jTracer); + ps->hooks.trace.jObj = REF_G(jTracer); return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps); } -static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, - const char *zTable, sqlite3_int64 nRowid){ - S3JniDb * const ps = pState; - LocalJniGetEnv; - S3JniEnv * const jc = S3JniGlobal_env_cache(env); - jstring jDbName; - jstring jTable; - jDbName = s3jni_utf8_to_jstring(jc, zDb, -1); - jTable = jDbName ? s3jni_utf8_to_jstring(jc, zTable, -1) : 0; - IFTHREW { - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); - }else{ - (*env)->CallVoidMethod(env, ps->updateHook.jObj, - ps->updateHook.midCallback, - (jint)opId, jDbName, jTable, (jlong)nRowid); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("sqlite3_update_hook() callback"); - s3jni_db_exception(env, ps, 0, - "sqlite3_update_hook() callback threw"); - } - } - UNREF_L(jDbName); - UNREF_L(jTable); -} - - JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); - jclass klazz; - jobject pOld = 0; - jmethodID xCallback; - S3JniHook * const pHook = &ps->updateHook; - if(!ps){ - s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); - return 0; - } - pOld = pHook->jObj; - if(pOld && jHook && - (*env)->IsSameObject(env, pOld, jHook)){ - return pOld; - } - if( !jHook ){ - if(pOld){ - jobject tmp = REF_L(pOld); - UNREF_G(pOld); - pOld = tmp; - } - memset(pHook, 0, sizeof(S3JniHook)); - sqlite3_update_hook(ps->pDb, 0, 0); - return pOld; - } - klazz = (*env)->GetObjectClass(env, jHook); - xCallback = (*env)->GetMethodID(env, klazz, "xUpdateHook", - "(ILjava/lang/String;Ljava/lang/String;J)V"); - IFTHREW { - EXCEPTION_CLEAR; - s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Cannot not find matching callback on " - "update hook object."); - }else{ - pHook->midCallback = xCallback; - pHook->jObj = REF_G(jHook); - sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps); - if(pOld){ - jobject tmp = REF_L(pOld); - UNREF_G(pOld); - pOld = tmp; - } - } - return pOld; + return s3jni_updatepre_hook(env, 0, jDb, jHook); } diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 0a8736f2a7..86fada9574 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1307,6 +1307,54 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2 JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3 (JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_blobwrite + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1blobwrite + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_count + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_depth + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1depth + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/PreUpdateHook;)Lorg/sqlite/jni/PreUpdateHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1hook + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_new + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1new + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_old + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1old + (JNIEnv *, jclass, jobject, jint, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_progress_handler diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index 416ad48e60..bf61656dd5 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -86,6 +86,28 @@ public final class OutputPointer { } } + /** + Output pointer for use with routines, such as sqlite3_prepupdate_new(), + which return a sqlite3_value handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ + public static final class sqlite3_value { + private org.sqlite.jni.sqlite3_value value; + //! Initializes with a null value. + public sqlite3_value(){value = null;} + //! Sets the current value to null. + public void clear(){value = null;} + //! Returns the current value. + public final org.sqlite.jni.sqlite3_value get(){return value;} + //! Equivalent to calling get() then clear(). + public final org.sqlite.jni.sqlite3_value take(){ + final org.sqlite.jni.sqlite3_value v = value; + value = null; + return v; + } + } + /** Output pointer for use with native routines which return integers via output pointers. diff --git a/ext/jni/src/org/sqlite/jni/PreUpdateHook.java b/ext/jni/src/org/sqlite/jni/PreUpdateHook.java new file mode 100644 index 0000000000..d5d82c72bc --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/PreUpdateHook.java @@ -0,0 +1,29 @@ +/* +** 2023-08-23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A callback for use with sqlite3_preupdate_hook(). +*/ +public interface PreUpdateHook { + /** + Must function as described for the sqlite3_preupdate_hook(). + callback, with the slight signature change. + + Must not throw. Any exceptions may emit debugging messages and + will be suppressed. + */ + void xPreUpdate(sqlite3 db, int op, String dbName, String dbTable, + long iKey1, long iKey2 ); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index e64ecf4914..c22d2fa60e 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -475,7 +475,7 @@ public final class SQLite3Jni { ** retained. ** ** If not built with SQLITE_ENABLE_SQLLOG defined, this returns - ** SQLITE_RANGE. + ** SQLITE_MISUSE. */ public static native int sqlite3_config( @Nullable SQLLog logger ); @@ -706,6 +706,69 @@ public final class SQLite3Jni { return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); } + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this + acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns + SQLITE_MISUSE with no side effects. + */ + public static native int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db); + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this + acts as a proxy for C's sqlite3_preupdate_count(), else it returns + SQLITE_MISUSE with no side effects. + */ + public static native int sqlite3_preupdate_count(@NotNull sqlite3 db); + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this + acts as a proxy for C's sqlite3_preupdate_depth(), else it returns + SQLITE_MISUSE with no side effects. + */ + public static native int sqlite3_preupdate_depth(@NotNull sqlite3 db); + + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this + acts as a proxy for C's sqlite3_preupdate_hook(), else it returns null + with no side effects. + */ + public static native PreUpdateHook sqlite3_preupdate_hook(@NotNull sqlite3 db, + @Nullable PreUpdateHook hook); + + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, + this acts as a proxy for C's sqlite3_preupdate_new(), else it + returns SQLITE_MISUSE with no side effects. + */ + public static native int sqlite3_preupdate_new(@NotNull sqlite3 db, int col, + @NotNull OutputPointer.sqlite3_value out); + + /** + Convenience wrapper for the 3-arg sqlite3_preupdate_new() which returns + null on error. + */ + public static sqlite3_value sqlite3_preupdate_new(@NotNull sqlite3 db, int col){ + final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value(); + sqlite3_preupdate_new(db, col, out); + return out.take(); + } + + /** + If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, + this acts as a proxy for C's sqlite3_preupdate_old(), else it + returns SQLITE_MISUSE with no side effects. + */ + public static native int sqlite3_preupdate_old(@NotNull sqlite3 db, int col, + @NotNull OutputPointer.sqlite3_value out); + + /** + Convenience wrapper for the 3-arg sqlite3_preupdate_old() which returns + null on error. + */ + public static sqlite3_value sqlite3_preupdate_old(@NotNull sqlite3 db, int col){ + final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value(); + sqlite3_preupdate_old(db, col, out); + return out.take(); + } + public static native void sqlite3_progress_handler( @NotNull sqlite3 db, int n, @Nullable ProgressHandler h ); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index fc350814b4..616e9fed22 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -997,6 +997,7 @@ public class Tester1 implements Runnable { final ValueHolder expectedOp = new ValueHolder<>(0); final UpdateHook theHook = new UpdateHook(){ @SuppressWarnings("unchecked") + @Override public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ ++counter.value; if( 0!=expectedOp.value ){ @@ -1040,6 +1041,79 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } + /** + This test is functionally identical to testUpdateHook(), only with a + different callback type. + */ + private synchronized void testPreUpdateHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder expectedOp = new ValueHolder<>(0); + final PreUpdateHook theHook = new PreUpdateHook(){ + @SuppressWarnings("unchecked") + @Override + public void xPreUpdate(sqlite3 db, int opId, String dbName, String dbTable, + long iKey1, long iKey2 ){ + ++counter.value; + switch( opId ){ + case SQLITE_UPDATE: + affirm( 0 < sqlite3_preupdate_count(db) ); + affirm( null != sqlite3_preupdate_new(db, 0) ); + affirm( null != sqlite3_preupdate_old(db, 0) ); + break; + case SQLITE_INSERT: + affirm( null != sqlite3_preupdate_new(db, 0) ); + break; + case SQLITE_DELETE: + affirm( null != sqlite3_preupdate_old(db, 0) ); + break; + default: + break; + } + if( 0!=expectedOp.value ){ + affirm( expectedOp.value == opId ); + } + } + }; + PreUpdateHook oldHook = sqlite3_preupdate_hook(db, theHook); + affirm( null == oldHook ); + expectedOp.value = SQLITE_INSERT; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 3 == counter.value ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='d' where a='c';"); + affirm( 4 == counter.value ); + oldHook = sqlite3_preupdate_hook(db, theHook); + affirm( theHook == oldHook ); + expectedOp.value = SQLITE_DELETE; + execSql(db, "DELETE FROM t where a='d'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_preupdate_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "update t set a='e' where a='b';"); + affirm( 5 == counter.value ); + oldHook = sqlite3_preupdate_hook(db, null); + affirm( null == oldHook ); + + final PreUpdateHook newHook = new PreUpdateHook(){ + @Override + public void xPreUpdate(sqlite3 db, int opId, String dbName, + String tableName, long iKey1, long iKey2){ + } + }; + oldHook = sqlite3_preupdate_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "update t set a='h' where a='a'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_preupdate_hook(db, theHook); + affirm( newHook == oldHook ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='i' where a='h'"); + affirm( 6 == counter.value ); + + sqlite3_close_v2(db); + } + private void testRollbackHook(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); diff --git a/manifest b/manifest index ff7bb357eb..ca1266d086 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\sa\ssubset\sof\ssqlite3_config()\sto\sJNI:\sthreading\smodes\sand\ssqllog. -D 2023-08-23T10:36:12.341 +C Bind\ssqlite3_preupdate_hook()\sand\sfriends\sto\sJNI. +D 2023-08-23T13:17:37.782 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,11 +232,11 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 33abc2f4f4bbd5451d6be5e6f2e109c045cc326cd942d602a3908a0c7b3c6f49 +F ext/jni/GNUmakefile 14b7c3abd1ae8693203b08b0e06bb359f8924ad2243f15953e9c6e456ae317b5 F ext/jni/README.md ddcc6be0c0d65f1e2fd687de9f40d38c82630fd61f83cc9550773caa19dd8be1 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c 01c6cf041d1b9937a97c7700006a532d3b11fd4991931e31ffa7a777b97fba11 -F ext/jni/src/c/sqlite3-jni.h 44bcb4eb3517c089f8f24f1546ea66b350d0661a4b0fa148425c9a41cabf487d +F ext/jni/src/c/sqlite3-jni.c 852c4812c9a3663d871cb334eaa60eb6fc22d67da47d4ff3868fdbfd6ebedb3a +F ext/jni/src/c/sqlite3-jni.h c5cb0348efe4e5f3d125a240e2437e8475de14a586c2f859e2acdcde4116244d F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -250,14 +250,15 @@ F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7 F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 8110d4cfb20884e8ed241de7420c615b040a9f9c441d9cff06f34833399244a8 -F ext/jni/src/org/sqlite/jni/OutputPointer.java 464ea85c3eba673a7b575545f69fcd8aeb398477a26d155d88cee3e2459e7802 +F ext/jni/src/org/sqlite/jni/OutputPointer.java bb09fee5ad51d10e58075de000f8c1a3622a6c4b6a390ef134b6add1bfb32ca1 +F ext/jni/src/org/sqlite/jni/PreUpdateHook.java dec00a706b58c67989f0ff56c4f0a703821d25b45c62dd7fed1b462049f15c26 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc 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 f697cf2a81c4119f2baf0682af689686f0466f1dd83dba00885f5603e693fe16 F ext/jni/src/org/sqlite/jni/SQLLog.java c60610b35208416940822e834d61f08fbbe5d6e06b374b541b49e41fd56c9798 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2de5729a33cf2636160eb6893a4234c99669521a352bfffbf60410bd493ebece -F ext/jni/src/org/sqlite/jni/Tester1.java 4e17a30e9da15954ba71ef52beb5b347f312594a0facbaf86e1f29481c4dc65c +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java e99e073e3779d00e23842858276efac93c8b523193b77ff12469d12a0b6182ca +F ext/jni/src/org/sqlite/jni/Tester1.java 05ae085ed040bcc10b51cd12076a4151eda478f9773dc00a85d0cddd3dcc01f7 F ext/jni/src/org/sqlite/jni/TesterFts5.java de095e3b701fba0c56d7b8b2993dc22bcbaa9de8f992904a93729ad729a91576 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2093,8 +2094,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 6c92d884920e4ace54913fc60ceef6e43a4351f45a4cb3c4a0ed3d29d544a31b -R ebb24a95583279229c99fb88e45995e0 +P fce8ecaf7f2e69a168978e6993e58c452c45f76c39da33f2869c9d947c16cab1 +R 28457b0903a4397220c04d68facc73da U stephan -Z 0a740a88323f212cff509af7c6f7ae11 +Z d81ff8935be35c831285c9d98a32b81f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 23b760f68b..9a861544e3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fce8ecaf7f2e69a168978e6993e58c452c45f76c39da33f2869c9d947c16cab1 \ No newline at end of file +d0c425b5c1d3aac5ead18a501a3760b4506d68d373cb3be484247042cf2fa8d4 \ No newline at end of file From 495046ef88ed1ff05e414b0ede46ef2149bd696e Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 23 Aug 2023 13:36:27 +0000 Subject: [PATCH 33/37] Add a note to the JNI README explaining why the Java API has callback names like xFunc() and xPreUpdate(). FossilOrigin-Name: 415447a310f6a7d06b4aa9ef51f110cf8e2ef9545c69cb5983c367c50fe641d2 --- ext/jni/README.md | 11 +++++++++++ manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ext/jni/README.md b/ext/jni/README.md index 5e100c4f07..90067556d2 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -110,6 +110,17 @@ to throw, in which cases they get translated to C-level result codes and/or messages. +Awkward Callback Names +------------------------------------------------------------------------ + +In places where the Java interface uses callbacks (see below), those +callbacks often have what might fairly be labeled as awkward names, +e.g. `sqlScalarFunction.xFunc()` and `preupdateHook.xPreUpdate()`. +Those names were chosen because they match the corresponding arguments +in the C-level API docs. If they were renamed to be more Java-esque, +documentation transparency would suffer. + + Unwieldy Constructs are Re-mapped ------------------------------------------------------------------------ diff --git a/manifest b/manifest index ca1266d086..58f3c72d7e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_preupdate_hook()\sand\sfriends\sto\sJNI. -D 2023-08-23T13:17:37.782 +C Add\sa\snote\sto\sthe\sJNI\sREADME\sexplaining\swhy\sthe\sJava\sAPI\shas\scallback\snames\slike\sxFunc()\sand\sxPreUpdate(). +D 2023-08-23T13:36:27.524 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -233,7 +233,7 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 14b7c3abd1ae8693203b08b0e06bb359f8924ad2243f15953e9c6e456ae317b5 -F ext/jni/README.md ddcc6be0c0d65f1e2fd687de9f40d38c82630fd61f83cc9550773caa19dd8be1 +F ext/jni/README.md 1693e865d366f5ebaa756732ea0d4b786515caf3cfbcd4dcb8758274373913b0 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 F ext/jni/src/c/sqlite3-jni.c 852c4812c9a3663d871cb334eaa60eb6fc22d67da47d4ff3868fdbfd6ebedb3a F ext/jni/src/c/sqlite3-jni.h c5cb0348efe4e5f3d125a240e2437e8475de14a586c2f859e2acdcde4116244d @@ -2094,8 +2094,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 fce8ecaf7f2e69a168978e6993e58c452c45f76c39da33f2869c9d947c16cab1 -R 28457b0903a4397220c04d68facc73da +P d0c425b5c1d3aac5ead18a501a3760b4506d68d373cb3be484247042cf2fa8d4 +R 2838b81803960048740e7919bfbbb667 U stephan -Z d81ff8935be35c831285c9d98a32b81f +Z 6887128552106c38adfd2773e845a05f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 9a861544e3..69eb67759c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d0c425b5c1d3aac5ead18a501a3760b4506d68d373cb3be484247042cf2fa8d4 \ No newline at end of file +415447a310f6a7d06b4aa9ef51f110cf8e2ef9545c69cb5983c367c50fe641d2 \ No newline at end of file From 1c3bf8a3e15fc6dcdfdb508f67c8e747b2621014 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 23 Aug 2023 17:15:48 +0000 Subject: [PATCH 34/37] Remove unnecessary jclass-type struct members. FossilOrigin-Name: d67255f7251cc5d1d27d77d4c84ff216e2da71202db989718189a6b4beff1cd0 --- ext/jni/src/c/sqlite3-jni.c | 117 ++++++++++++++++++------------------ manifest | 12 ++-- manifest.uuid | 2 +- 3 files changed, 65 insertions(+), 66 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 93c5d5f1d2..494e64a3f6 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -356,7 +356,7 @@ struct S3JniNphClass { ** 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 */; volatile jfieldID fidAggCtx /* sqlite3_context::aggregateContext. Used only @@ -369,12 +369,6 @@ struct S3JniHook{ jobject jObj /* global ref to Java instance */; jmethodID midCallback /* callback method. Signature depends on ** 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. */; }; /* @@ -533,7 +527,6 @@ struct S3JniGlobalType { 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; @@ -860,6 +853,7 @@ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ char * zMsg; jclass const klazz = (*env)->GetObjectClass(env, jx); mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;"); + UNREF_L(klazz); IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; @@ -905,20 +899,18 @@ static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, } /* -** Extracts the (void xDestroy()) method from the given jclass and -** applies it to jobj. If jObj is NULL, this is a no-op. If klazz is -** NULL then it's derived from jobj. The lack of an xDestroy() method -** is silently ignored and any exceptions thrown by xDestroy() trigger -** a warning to stdout or stderr and then the exception is suppressed. +** Extracts the (void xDestroy()) method from jObj and applies it to +** jObj. If jObj is NULL, this is a no-op. The lack of an xDestroy() +** method is silently ignored and any exceptions thrown by xDestroy() +** trigger a warning to stdout or stderr and then the exception is +** suppressed. */ -static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ +static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj){ if(jObj){ - jmethodID method; - if(!klazz){ - klazz = (*env)->GetObjectClass(env, jObj); - assert(klazz); - } - method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); + jclass const klazz = (*env)->GetObjectClass(env, jObj); + jmethodID method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); + + UNREF_L(klazz); if(method){ s3jni_incr( &SJG.metrics.nDestroy ); (*env)->CallVoidMethod(env, jObj, method); @@ -927,6 +919,7 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ EXCEPTION_CLEAR; } }else{ + /* Non-fatal. */ EXCEPTION_CLEAR; } } @@ -934,17 +927,16 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ /* ** Removes any Java references from s and clears its state. If -** doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's +** doXDestroy is true and s->jObj is not NULL, s->jObj's ** s is passed to s3jni_call_xDestroy() before any references are ** cleared. It is legal to call this when the object has no Java ** references. */ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDestroy){ - if(doXDestroy && s->klazz && s->jObj){ - s3jni_call_xDestroy(env, s->jObj, s->klazz); + if(doXDestroy && s->jObj){ + s3jni_call_xDestroy(env, s->jObj); } UNREF_G(s->jObj); - UNREF_G(s->klazz); memset(s, 0, sizeof(*s)); } @@ -1198,16 +1190,12 @@ static void S3JniAutoExtension_clear(JNIEnv * const env, static int S3JniAutoExtension_init(JNIEnv *const env, S3JniAutoExtension * const ax, jobject const jAutoExt){ - jclass klazz; - klazz = (*env)->GetObjectClass(env, jAutoExt); - if(!klazz){ - S3JniAutoExtension_clear(env, ax); - return SQLITE_ERROR; - } + jclass const klazz = (*env)->GetObjectClass(env, jAutoExt); + ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", "(Lorg/sqlite/jni/sqlite3;)I"); - EXCEPTION_WARN_IGNORE; UNREF_L(klazz); + EXCEPTION_WARN_IGNORE; if(!ax->midFunc){ MARKER(("Error getting xEntryPoint(sqlite3) from AutoExtension object.")); S3JniAutoExtension_clear(env, ax); @@ -1245,8 +1233,7 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, if(!pNC->fidAggCtx){ MUTEX_NPH_ENTER; if(!pNC->fidAggCtx){ - pNC->fidAggCtx = (*env)->GetFieldID(env, pNC->klazz, - "aggregateContext", "J"); + pNC->fidAggCtx = (*env)->GetFieldID(env, pNC->klazz, "aggregateContext", "J"); EXCEPTION_IS_FATAL("Cannot get sqlite3_contex.aggregateContext member."); } MUTEX_NPH_LEAVE; @@ -1540,7 +1527,6 @@ typedef void (*udf_xFinal_f)(sqlite3_context*); typedef struct S3JniUdf S3JniUdf; struct S3JniUdf { jobject jObj /* SQLFunction instance */; - jclass klazz /* jObj's class */; char * zFuncName /* Only for error reporting and debug logging */; enum UDFType type; /** Method IDs for the various UDF methods. */ @@ -1558,11 +1544,12 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ "(Lorg/sqlite/jni/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V"; const char * zFV = /* signature for xFinal, xValue */ "(Lorg/sqlite/jni/sqlite3_context;)V"; + jclass const klazz = (*env)->GetObjectClass(env, jObj); + memset(s, 0, sizeof(S3JniUdf)); s->jObj = REF_G(jObj); - s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); #define FGET(FuncName,FuncType,Field) \ - s->Field = (*env)->GetMethodID(env, s->klazz, FuncName, FuncType); \ + s->Field = (*env)->GetMethodID(env, klazz, FuncName, FuncType); \ if(!s->Field) (*env)->ExceptionClear(env) FGET("xFunc", zFSI, jmidxFunc); FGET("xStep", zFSI, jmidxStep); @@ -1570,6 +1557,7 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ FGET("xValue", zFV, jmidxValue); FGET("xInverse", zFSI, jmidxInverse); #undef FGET + UNREF_L(klazz); if(s->jmidxFunc) s->type = UDF_SCALAR; else if(s->jmidxStep && s->jmidxFinal){ s->type = s->jmidxValue ? UDF_WINDOW : UDF_AGGREGATE; @@ -1584,9 +1572,8 @@ static void S3JniUdf_free(S3JniUdf * s){ LocalJniGetEnv; if(env){ //MARKER(("UDF cleanup: %s\n", s->zFuncName)); - s3jni_call_xDestroy(env, s->jObj, s->klazz); + s3jni_call_xDestroy(env, s->jObj); UNREF_G(s->jObj); - UNREF_G(s->klazz); } sqlite3_free(s->zFuncName); sqlite3_free(s); @@ -2024,10 +2011,12 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ /* Same object - this is a no-op. */ return 0; } + jclass klazz; S3JniHook_unref(env, pHook, 1); pHook->jObj = REF_G(jBusy); - pHook->klazz = REF_G((*env)->GetObjectClass(env, jBusy)); - pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, "xCallback", "(I)I"); + klazz = (*env)->GetObjectClass(env, jBusy); + pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "(I)I"); + UNREF_L(klazz); IFTHREW { S3JniHook_unref(env, pHook, 0); rc = SQLITE_ERROR; @@ -2157,6 +2146,7 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ klazz = (*env)->GetObjectClass(env, jHook); xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded", "(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I"); + UNREF_L(klazz); IFTHREW { rc = s3jni_db_exception(env, ps, SQLITE_MISUSE, "Cannot not find matching callback on " @@ -2275,6 +2265,7 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env, xCallback = (*env)->GetMethodID(env, klazz, isCommit ? "xCommitHook" : "xRollbackHook", isCommit ? "()I" : "()V"); + UNREF_L(klazz); IFTHREW { EXCEPTION_REPORT; EXCEPTION_CLEAR; @@ -2371,6 +2362,7 @@ JDECL(jint,1config__Lorg_sqlite_jni_SQLLog_2)(JENV_CSELF, jobject jLog){ S3JniHook tmpHook; S3JniHook * const hook = &tmpHook; S3JniHook * const hookOld = & SJG.hooks.sqllog; + jclass klazz; int rc = 0; if( !jLog ){ S3JniHook_unref(env, hookOld, 0); @@ -2379,11 +2371,12 @@ JDECL(jint,1config__Lorg_sqlite_jni_SQLLog_2)(JENV_CSELF, jobject jLog){ if( hookOld->jObj && (*env)->IsSameObject(env, jLog, hookOld->jObj) ){ return 0; } - hook->klazz = REF_G( (*env)->GetObjectClass(env, jLog) ); - hook->midCallback = (*env)->GetMethodID(env, hook->klazz, "xSqllog", + klazz = (*env)->GetObjectClass(env, jLog); + hook->midCallback = (*env)->GetMethodID(env, klazz, "xSqllog", "(Lorg/sqlite/jni/sqlite3;" "Ljava/lang/String;" "I)V"); + UNREF_L(klazz); if( !hook->midCallback ){ EXCEPTION_WARN_IGNORE; S3JniHook_unref(env, hook, 0); @@ -2423,8 +2416,9 @@ JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, klazz = (*env)->GetObjectClass(env, oCollation); pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare", "([B[B)I"); + UNREF_L(klazz); IFTHREW{ - EXCEPTION_REPORT; + UNREF_L(klazz); return s3jni_db_error(ps->pDb, SQLITE_ERROR, "Could not get xCompare() method for object."); } @@ -2435,7 +2429,6 @@ JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, JSTR_RELEASE(name, zName); if( 0==rc ){ pHook->jObj = REF_G(oCollation); - pHook->klazz = REF_G(klazz); }else{ S3JniHook_unref(env, pHook, 1); } @@ -3000,6 +2993,7 @@ static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jobject jDb, jobjec "JJ)V") : (*env)->GetMethodID(env, klazz, "xUpdateHook", "(ILjava/lang/String;Ljava/lang/String;J)V"); + UNREF_L(klazz); IFTHREW { EXCEPTION_CLEAR; s3jni_db_error(ps->pDb, SQLITE_ERROR, @@ -3094,6 +3088,7 @@ JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress } klazz = (*env)->GetObjectClass(env, jProgress); xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); + UNREF_L(klazz); IFTHREW { EXCEPTION_CLEAR; s3jni_db_error(ps->pDb, SQLITE_ERROR, @@ -3351,6 +3346,7 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 ); }else{ int rc = 0; + jclass klazz; if( pHook->jObj ){ if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){ /* Same object - this is a no-op. */ @@ -3359,8 +3355,8 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ S3JniHook_unref(env, pHook, 0); } pHook->jObj = REF_G(jHook); - pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook)); - pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, + klazz = (*env)->GetObjectClass(env, jHook); + pHook->midCallback = (*env)->GetMethodID(env, klazz, "xAuth", "(I" "Ljava/lang/String;" @@ -3368,6 +3364,7 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ "Ljava/lang/String;" "Ljava/lang/String;" ")I"); + UNREF_L(klazz); IFTHREW { S3JniHook_unref(env, pHook, 0); return s3jni_db_error(ps->pDb, SQLITE_ERROR, @@ -3536,6 +3533,7 @@ JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ klazz = (*env)->GetObjectClass(env, jTracer); ps->hooks.trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "(ILjava/lang/Object;Ljava/lang/Object;)I"); + UNREF_L(klazz); IFTHREW { EXCEPTION_CLEAR; return s3jni_db_error(ps->pDb, SQLITE_ERROR, @@ -3723,7 +3721,6 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ */ typedef struct { jobject jObj /* functor instance */; - jclass klazz /* jObj's class */; jobject jUserData /* 2nd arg to JNI binding of xCreateFunction(), ostensibly the 3rd arg to the lib-level xCreateFunction(), except @@ -3737,9 +3734,8 @@ static void Fts5JniAux_free(Fts5JniAux * const s){ LocalJniGetEnv; if(env){ /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/ - s3jni_call_xDestroy(env, s->jObj, s->klazz); + s3jni_call_xDestroy(env, s->jObj); UNREF_G(s->jObj); - UNREF_G(s->klazz); UNREF_G(s->jUserData); } sqlite3_free(s->zFuncName); @@ -3753,14 +3749,16 @@ static void Fts5JniAux_xDestroy(void *p){ static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ Fts5JniAux * s = sqlite3_malloc(sizeof(Fts5JniAux)); if(s){ + jclass klazz; memset(s, 0, sizeof(Fts5JniAux)); s->jObj = REF_G(jObj); - s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); - s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", + klazz = (*env)->GetObjectClass(env, jObj); + s->jmid = (*env)->GetMethodID(env, klazz, "xFunction", "(Lorg/sqlite/jni/Fts5ExtensionApi;" "Lorg/sqlite/jni/Fts5Context;" "Lorg/sqlite/jni/sqlite3_context;" "[Lorg/sqlite/jni/sqlite3_value;)V"); + UNREF_L(klazz); IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; @@ -3966,7 +3964,7 @@ static void S3JniFts5AuxData_xDestroy(void *x){ S3JniFts5AuxData * const p = x; if(p->jObj){ LocalJniGetEnv; - s3jni_call_xDestroy(env, p->jObj, 0); + s3jni_call_xDestroy(env, p->jObj); UNREF_G(p->jObj); } sqlite3_free(x); @@ -4023,7 +4021,7 @@ static void s3jni_phraseIter_NToJ(JNIEnv *const env, Fts5PhraseIter const * const pSrc, jobject jIter){ S3JniGlobalType * const g = &S3JniGlobal; - assert(g->fts5.jPhraseIter.klazz); + assert(g->fts5.jPhraseIter.fidA); (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidA, (jlong)pSrc->a); EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field."); (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB, (jlong)pSrc->b); @@ -4034,7 +4032,7 @@ static void s3jni_phraseIter_NToJ(JNIEnv *const env, static void s3jni_phraseIter_JToN(JNIEnv *const env, jobject jIter, Fts5PhraseIter * const pDest){ S3JniGlobalType * const g = &S3JniGlobal; - assert(g->fts5.jPhraseIter.klazz); + assert(g->fts5.jPhraseIter.fidA); pDest->a = (const unsigned char *)(*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA); EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); @@ -4078,7 +4076,6 @@ JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, Fts5ExtDecl; Fts5PhraseIter iter; int iCol = 0, iOff = 0; - 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); @@ -4091,7 +4088,6 @@ JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter, Fts5ExtDecl; Fts5PhraseIter iter; int iCol = 0; - 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); @@ -4151,6 +4147,7 @@ JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, s.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "(Lorg.sqlite.jni.Fts5ExtensionApi;" "Lorg.sqlite.jni.Fts5Context;)I"); + UNREF_L(klazz); EXCEPTION_IS_FATAL("Could not extract xQueryPhraseCallback.xCallback method."); return (jint)fext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s, s3jni_xQueryPhrase); @@ -4179,7 +4176,7 @@ JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ if(jAux){ /* Emulate how xSetAuxdata() behaves when it cannot alloc ** its auxdata wrapper. */ - s3jni_call_xDestroy(env, jAux, 0); + s3jni_call_xDestroy(env, jAux); } return SQLITE_NOMEM; } @@ -4237,6 +4234,7 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, S3NphRef const *pRef, s.jFcx = jFcx; s.fext = fext; s.midCallback = (*env)->GetMethodID(env, klazz, "xToken", "(I[BII)I"); + UNREF_L(klazz); IFTHREW { EXCEPTION_REPORT; EXCEPTION_CLEAR; @@ -4606,16 +4604,17 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ SJG.g.oCharsetUtf8 = REF_G((*env)->GetStaticObjectField(env, klazz, fUtf8)); EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); + UNREF_L(klazz); } #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"); + SJG.fts5.jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "b", "J"); EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); + UNREF_L(klazz); #endif SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); diff --git a/manifest b/manifest index 58f3c72d7e..f6449b99b7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\snote\sto\sthe\sJNI\sREADME\sexplaining\swhy\sthe\sJava\sAPI\shas\scallback\snames\slike\sxFunc()\sand\sxPreUpdate(). -D 2023-08-23T13:36:27.524 +C Remove\sunnecessary\sjclass-type\sstruct\smembers. +D 2023-08-23T17:15:48.658 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 14b7c3abd1ae8693203b08b0e06bb359f8924ad2243f15953e9c6e456ae317b5 F ext/jni/README.md 1693e865d366f5ebaa756732ea0d4b786515caf3cfbcd4dcb8758274373913b0 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c 852c4812c9a3663d871cb334eaa60eb6fc22d67da47d4ff3868fdbfd6ebedb3a +F ext/jni/src/c/sqlite3-jni.c adb773d104abe72e93364f21c52d455f361692f3cd7dd6d9fdab6110b8b4f3ee F ext/jni/src/c/sqlite3-jni.h c5cb0348efe4e5f3d125a240e2437e8475de14a586c2f859e2acdcde4116244d F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd @@ -2094,8 +2094,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 d0c425b5c1d3aac5ead18a501a3760b4506d68d373cb3be484247042cf2fa8d4 -R 2838b81803960048740e7919bfbbb667 +P 415447a310f6a7d06b4aa9ef51f110cf8e2ef9545c69cb5983c367c50fe641d2 +R fddefcd0dbe857a799e7e6b983a3459a U stephan -Z 6887128552106c38adfd2773e845a05f +Z b441a1d2bae36389c447acd3134d6be2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 69eb67759c..3df5deea50 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -415447a310f6a7d06b4aa9ef51f110cf8e2ef9545c69cb5983c367c50fe641d2 \ No newline at end of file +d67255f7251cc5d1d27d77d4c84ff216e2da71202db989718189a6b4beff1cd0 \ No newline at end of file From 8cafdfa916d31818e4713c2e5aac1d3d105f57fa Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 23 Aug 2023 17:52:51 +0000 Subject: [PATCH 35/37] JNI cleanups regarding building with certain features disabled. FossilOrigin-Name: a9e6d5158b8a4a6b8554a5f8f0a35785ee450d42ea877275dc27085e89716c18 --- ext/jni/src/c/sqlite3-jni.c | 26 ++++++------------ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 5 ---- ext/jni/src/org/sqlite/jni/Tester1.java | 31 ++++++++++++++-------- manifest | 16 +++++------ manifest.uuid | 2 +- 5 files changed, 37 insertions(+), 43 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 494e64a3f6..debb221d6a 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -61,9 +61,6 @@ #ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC # define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 #endif -#ifndef SQLITE_ENABLE_PREUPDATE_HOOK -# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ -#endif #ifndef SQLITE_ENABLE_RTREE # define SQLITE_ENABLE_RTREE 1 #endif @@ -235,12 +232,12 @@ static const struct { const S3NphRef sqlite3_value; const S3NphRef OutputPointer_Int32; const S3NphRef OutputPointer_Int64; - const S3NphRef OutputPointer_String; - const S3NphRef OutputPointer_ByteArray; const S3NphRef OutputPointer_sqlite3; const S3NphRef OutputPointer_sqlite3_stmt; const S3NphRef OutputPointer_sqlite3_value; #ifdef SQLITE_ENABLE_FTS5 + const S3NphRef OutputPointer_String; + const S3NphRef OutputPointer_ByteArray; const S3NphRef Fts5Context; const S3NphRef Fts5ExtensionApi; const S3NphRef fts5_api; @@ -255,12 +252,12 @@ static const struct { NREF(3, "sqlite3_value"), NREF(4, "OutputPointer$Int32"), NREF(5, "OutputPointer$Int64"), - NREF(6, "OutputPointer$String"), - NREF(7, "OutputPointer$ByteArray"), - NREF(8, "OutputPointer$sqlite3"), - NREF(9, "OutputPointer$sqlite3_stmt"), - NREF(10, "OutputPointer$sqlite3_value"), + NREF(6, "OutputPointer$sqlite3"), + NREF(7, "OutputPointer$sqlite3_stmt"), + NREF(8, "OutputPointer$sqlite3_value"), #ifdef SQLITE_ENABLE_FTS5 + NREF(9, "OutputPointer$String"), + NREF(10, "OutputPointer$ByteArray"), NREF(11, "Fts5Context"), NREF(12, "Fts5ExtensionApi"), NREF(13, "fts5_api"), @@ -4525,13 +4522,6 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ int value; } ConfigFlagEntry; const ConfigFlagEntry aLimits[] = { - {"SQLITE_ENABLE_FTS5", JTYPE_BOOL, -#ifdef SQLITE_ENABLE_FTS5 - 1 -#else - 0 -#endif - }, {"SQLITE_MAX_ALLOCATION_SIZE", JTYPE_INT, SQLITE_MAX_ALLOCATION_SIZE}, {"SQLITE_LIMIT_LENGTH", JTYPE_INT, SQLITE_LIMIT_LENGTH}, {"SQLITE_MAX_LENGTH", JTYPE_INT, SQLITE_MAX_LENGTH}, @@ -4597,7 +4587,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ { /* StandardCharsets.UTF_8 */ jfieldID fUtf8; klazz = (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); - EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class."); + EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets class."); fUtf8 = (*env)->GetStaticFieldID(env, klazz, "UTF_8", "Ljava/nio/charset/Charset;"); EXCEPTION_IS_FATAL("Error getting StandardCharsets.UTF_8 field."); diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index c22d2fa60e..29ed0e5602 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -1251,11 +1251,6 @@ public final class SQLite3Jni { public static final String SQLITE_VERSION = sqlite3_libversion(); public static final String SQLITE_SOURCE_ID = sqlite3_sourceid(); - //! Feature flags which are initialized at lib startup. Necessarily - // non-final so that lib init can fill out the proper values, - // but modifying them from client code has no effect. - public static boolean SQLITE_ENABLE_FTS5 = false; - // access public static final int SQLITE_ACCESS_EXISTS = 0; public static final int SQLITE_ACCESS_READWRITE = 1; diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 616e9fed22..c655ae7ffd 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -1046,6 +1046,10 @@ public class Tester1 implements Runnable { different callback type. */ private synchronized void testPreUpdateHook(){ + if( !sqlite3_compileoption_used("ENABLE_PREUPDATE_HOOK") ){ + //outln("Skipping testPreUpdateHook(): no pre-update hook support."); + return; + } final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); final ValueHolder expectedOp = new ValueHolder<>(0); @@ -1153,8 +1157,8 @@ public class Tester1 implements Runnable { */ @SuppressWarnings("unchecked") private void testFts5() throws Exception { - if( !SQLITE_ENABLE_FTS5 ){ - outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests."); + if( !sqlite3_compileoption_used("ENABLE_FTS5") ){ + //outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests."); return; } Exception err = null; @@ -1444,16 +1448,21 @@ public class Tester1 implements Runnable { } if( sqlLog ){ - int rc = sqlite3_config( new SQLLog() { - @Override public void xSqllog(sqlite3 db, String msg, int op){ - switch(op){ - case 0: outln("Opening db: ",db); break; - case 1: outln(db,": ",msg); break; - case 2: outln("Closing db: ",db); break; + if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){ + int rc = sqlite3_config( new SQLLog() { + @Override public void xSqllog(sqlite3 db, String msg, int op){ + switch(op){ + case 0: outln("Opening db: ",db); break; + case 1: outln(db,": ",msg); break; + case 2: outln("Closing db: ",db); break; + } } - } - }); - affirm( 0==rc ); + }); + affirm( 0==rc ); + }else{ + outln("WARNING: -sqllog is not active because library was built ", + "without SQLITE_ENABLE_SQLLOG."); + } } final long timeStart = System.currentTimeMillis(); diff --git a/manifest b/manifest index f6449b99b7..f1c1761dd4 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\sunnecessary\sjclass-type\sstruct\smembers. -D 2023-08-23T17:15:48.658 +C JNI\scleanups\sregarding\sbuilding\swith\scertain\sfeatures\sdisabled. +D 2023-08-23T17:52:51.175 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 14b7c3abd1ae8693203b08b0e06bb359f8924ad2243f15953e9c6e456ae317b5 F ext/jni/README.md 1693e865d366f5ebaa756732ea0d4b786515caf3cfbcd4dcb8758274373913b0 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c adb773d104abe72e93364f21c52d455f361692f3cd7dd6d9fdab6110b8b4f3ee +F ext/jni/src/c/sqlite3-jni.c 0ca96134d7fb3f313a7a49487f68a8d7a6d7545470c84532aa1ce63d2cdc432e F ext/jni/src/c/sqlite3-jni.h c5cb0348efe4e5f3d125a240e2437e8475de14a586c2f859e2acdcde4116244d F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd @@ -257,8 +257,8 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java f697cf2a81c4119f2baf0682af689686f0466f1dd83dba00885f5603e693fe16 F ext/jni/src/org/sqlite/jni/SQLLog.java c60610b35208416940822e834d61f08fbbe5d6e06b374b541b49e41fd56c9798 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java e99e073e3779d00e23842858276efac93c8b523193b77ff12469d12a0b6182ca -F ext/jni/src/org/sqlite/jni/Tester1.java 05ae085ed040bcc10b51cd12076a4151eda478f9773dc00a85d0cddd3dcc01f7 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java f64554457fa30a048ef99374bfac3c4b986e3528353dce3086a98a858e3fe000 +F ext/jni/src/org/sqlite/jni/Tester1.java 69ea63a5b235f94f914dff6fe3ecd103ee0a8023b8737db071b46c0c75375e26 F ext/jni/src/org/sqlite/jni/TesterFts5.java de095e3b701fba0c56d7b8b2993dc22bcbaa9de8f992904a93729ad729a91576 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2094,8 +2094,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 415447a310f6a7d06b4aa9ef51f110cf8e2ef9545c69cb5983c367c50fe641d2 -R fddefcd0dbe857a799e7e6b983a3459a +P d67255f7251cc5d1d27d77d4c84ff216e2da71202db989718189a6b4beff1cd0 +R b911867dcaf18c3e131e156c82d306fe U stephan -Z b441a1d2bae36389c447acd3134d6be2 +Z bdef86836c9254e732e1b6d744febf17 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3df5deea50..5245bf9cfe 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d67255f7251cc5d1d27d77d4c84ff216e2da71202db989718189a6b4beff1cd0 \ No newline at end of file +a9e6d5158b8a4a6b8554a5f8f0a35785ee450d42ea877275dc27085e89716c18 \ No newline at end of file From bfdc7ab5a7c988e1b1d4b6361eeffbb5cd173eee Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 24 Aug 2023 11:57:51 +0000 Subject: [PATCH 36/37] Add more JNI docs, tests, and a handful of Java-side overloads. FossilOrigin-Name: d19a431facbde6a6b960664674753ee85d2c051a76109ce7db0b079c65fbdea0 --- ext/jni/GNUmakefile | 19 +- ext/jni/README.md | 120 ++++++++++--- ext/jni/jar-dist.make | 6 +- ext/jni/src/c/sqlite3-jni.c | 48 ++--- ext/jni/src/c/sqlite3-jni.h | 8 + ext/jni/src/org/sqlite/jni/AutoExtension.java | 12 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 165 +++++++++++++++--- ext/jni/src/org/sqlite/jni/Tester1.java | 104 ++++++++--- ext/jni/src/org/sqlite/jni/TesterFts5.java | 21 ++- manifest | 28 +-- manifest.uuid | 2 +- 11 files changed, 395 insertions(+), 138 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index cc728003d7..6b32c40a82 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -223,19 +223,18 @@ $(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE) $(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h) $(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE) $(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \ - $(sqlite3-jni.c) -shared -o $@ -lpthread + $(sqlite3-jni.c) -shared -o $@ all: $(sqlite3-jni.dll) .PHONY: test -test.flags ?= -v +test.flags ?= +test.main.flags = -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.Tester1 test: $(SQLite3Jni.class) $(sqlite3-jni.dll) - $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ - $(java.flags) -cp $(classpath) \ - org.sqlite.jni.Tester1 $(test.flags) -test-mt: $(SQLite3Jni.class) $(sqlite3-jni.dll) - $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ - $(java.flags) -cp $(classpath) \ - org.sqlite.jni.Tester1 -t 7 -r 50 -shuffle $(test.flags) + $(bin.java) $(test.main.flags) $(test.flags) + @echo "Again in multi-threaded mode:" + $(bin.java) $(test.main.flags) -t 5 -r 20 -shuffle $(test.flags) tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) tester.flags ?= # --verbose @@ -271,7 +270,7 @@ endif tester-ext: tester-local tester: tester-ext -tests: test test-mt tester +tests: test tester package.jar.in := $(abspath $(dir.src)/jar.in) CLEAN_FILES += $(package.jar.in) $(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) diff --git a/ext/jni/README.md b/ext/jni/README.md index 90067556d2..6e5e07cc03 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -16,7 +16,7 @@ Technical support is available in the forum: > **FOREWARNING:** this subproject is very much in development and subject to any number of changes. Please do not rely on any information about its API until this disclaimer is removed. The JNI - bindgins released with version 3.43 are a "tech preview" and 3.44 + bindings released with version 3.43 are a "tech preview" and 3.44 will be "final," at which point strong backward compatibility guarantees will apply. @@ -43,29 +43,34 @@ Non-goals: - Creation of high-level OO wrapper APIs. Clients are free to create them off of the C-style API. + Hello World ----------------------------------------------------------------------- ```java import org.sqlite.jni.*; -import static org.sqlite.jni.SQLite3Jni; +import static SQLite3Jni.*; + ... -OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); -int rc = sqlite3_open(":memory:", out); -final sqlite3 db = out.take(); -if( 0 != rc ){ - if( null != db ){ - System.out.print("Error opening db: "+sqlite3_errmsg(db)); - sqlite3_close(db); - }else{ - System.out.print("Error opening db: rc="+rc); + +final sqlite3 db = sqlite3_open(":memory:"); +try { + final int rc = sqlite3_errcode(db); + if( 0 != rc ){ + if( null != db ){ + System.out.print("Error opening db: "+sqlite3_errmsg(db)); + }else{ + System.out.print("Error opening db: rc="+rc); + } + ... handle error ... } - ... handle error ... + // ... else use the db ... +}finally{ + // ALWAYS close databases using sqlite3_close() or sqlite3_close_v2() + // when done with them. All of their active statement handles must + // first have been passed to sqlite3_finalize(). + sqlite3_close_v2(db); } - -... use db ... - -sqlite3_close_v2(db); ``` Building @@ -86,28 +91,90 @@ $ make test $ make clean ``` -The jar distribution can be created with `make jar`. +The jar distribution can be created with `make jar`, but note that it +does not contain the binary DLL file. A different DLL is needed for +each target platform. + One-to-One(-ish) Mapping to C ======================================================================== This JNI binding aims to provide as close to a 1-to-1 experience with -the C API as cross-language semantics allow. Changes are necessarily -made where cross-language semantics do not allow a 1-to-1, and -judiciously made where a 1-to-1 mapping would be unduly cumbersome to -use in Java. +the C API as cross-language semantics allow. Interface changes are +necessarily made where cross-language semantics do not allow a 1-to-1, +and judiciously made where a 1-to-1 mapping would be unduly cumbersome +to use in Java. In all cases, this binding makes every effort to +provide semantics compatible with the C API documentation even if the +interface to those semantics is slightly different. Any cases which +deviate from those semantics (either removing or adding semantics) are +clearly documented. -Golden Rule: _Never_ Throw from Callbacks (Unless...) +Where it makes sense to do so for usability, Java-side overloads are +provided which accept or return data in alternative forms or provide +sensible default argument values. In all such cases they are thin +proxies around the corresponding C APIs and do not introduce new +semantics. + +In some very few cases, Java-specific capabilities have been added in +new APIs, all of which have "_java" somewhere in their names. +Examples include: + +- `sqlite3_result_java_object()` +- `sqlite3_column_java_object()` +- `sqlite3_column_java_casted()` +- `sqlite3_value_java_object()` +- `sqlite3_value_java_casted()` + +which, as one might surmise, collectively enable the passing of +arbitrary Java objects from user-defined SQL functions through to the +caller. + + +Golden Rule: Garbage Collection Cannot Free SQLite Resources ------------------------------------------------------------------------ +It is important that all databases and prepared statement handles get +cleaned up by client code. A database cannot be closed if it has open +statement handles. `sqlite3_close()` fails if the db cannot be closed +whereas `sqlite3_close_v2()` recognizes that case and marks the db as +a "zombie," pending finalization when the library detects that all +pending statements have been closed. Be aware that Java garbage +collection _cannot_ close a database or finalize a prepared statement. +Those things require explicit API calls. + + +Golden Rule #2: _Never_ Throw from Callbacks (Unless...) +------------------------------------------------------------------------ + +All routines in this API, barring explicitly documented exceptions, +retain C-like semantics. For example, they are not permitted to throw +or propagate exceptions and must return error information (if any) via +result codes or `null`. The only cases where the C-style APIs may +throw is through client-side misuse, e.g. passing in a null where it +shouldn't be used. The APIs clearly mark function parameters which +should not be null, but does not actively defend itself against such +misuse. Some C-style APIs explicitly accept `null` as a no-op for +usability's sake, and some of the JNI APIs deliberately return an +error code, instead of segfaulting, when passed a `null`. + Client-defined callbacks _must never throw exceptions_ unless _very explicitly documented_ as being throw-safe. Exceptions are generally reserved for higher-level bindings which are constructed to specifically deal with them and ensure that they do not leak C-level -resources. In some cases, callback handlers (see below) are permitted -to throw, in which cases they get translated to C-level result codes -and/or messages. +resources. In some cases, callback handlers are permitted to throw, in +which cases they get translated to C-level result codes and/or +messages. If a callback which is not permitted to throw throws, its +exception may trigger debug output but will otherwise be suppressed. + +The reason some callbacks are permitted to throw and others not is +because all such callbacks act as proxies for C function callback +interfaces and some of those interfaces have no error-reporting +mechanism. Those which are capable of propagating errors back through +the library convert exceptions from callbacks into corresponding +C-level error information. Those which cannot propagate errors +necessarily suppress any exceptions in order to maintain the C-style +semantics of the APIs. Awkward Callback Names @@ -246,4 +313,5 @@ in-flux nature of this API. Various APIs which accept callbacks, e.g. `sqlite3_trace_v2()` and `sqlite3_update_hook()`, use interfaces similar to those shown above. - +Despite the changes in signature, the JNI layer makes every effort to +provide the same semantics as the C API documentation suggests. diff --git a/ext/jni/jar-dist.make b/ext/jni/jar-dist.make index ac1a768b8c..7596c99f3f 100644 --- a/ext/jni/jar-dist.make +++ b/ext/jni/jar-dist.make @@ -46,11 +46,13 @@ $(sqlite3-jni.dll): echo "*** to configure it for your system. ***"; \ echo "************************************************************************" $(CC) $(CFLAGS) $(SQLITE_OPT) \ - src/sqlite3-jni.c -lpthread -shared -o $@ + src/sqlite3-jni.c -shared -o $@ @echo "Now try running it with: make test" +test.flags = -Djava.library.path=. sqlite3-jni-*.jar test: $(sqlite3-jni.dll) - java -jar -Djava.library.path=. sqlite3-jni-*.jar + java -jar $(test.flags) + java -jar $(test.flags) -t 7 -r 10 -shuffle clean: -rm -f $(sqlite3-jni.dll) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index debb221d6a..03ba8be57a 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1429,7 +1429,7 @@ typedef struct { } ResultJavaVal; /* For use with sqlite3_result/value_pointer() */ -#define ResultJavaValuePtrStr "ResultJavaVal" +#define ResultJavaValuePtrStr "org.sqlite.jni.ResultJavaVal" /* ** Allocate a new ResultJavaVal and assign it a new global ref of @@ -1915,15 +1915,10 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){ JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ - int rc; - if(!baData){ - rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx); - }else{ - jbyte * const pBuf = JBA_TOC(baData); - rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, - SQLITE_TRANSIENT); - JBA_RELEASE(baData,pBuf); - } + jbyte * const pBuf = baData ? JBA_TOC(baData) : 0; + int const rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, + pBuf, (int)nMax, SQLITE_TRANSIENT); + JBA_RELEASE(baData,pBuf); return (jint)rc; } @@ -1960,15 +1955,21 @@ JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ - if(baData){ - jbyte * const pBuf = JBA_TOC(baData); - int rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, - (int)nMax, SQLITE_TRANSIENT); - JBA_RELEASE(baData, pBuf); - return (jint)rc; - }else{ - return sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); - } + jbyte * const pBuf = baData ? JBA_TOC(baData) : 0; + int const rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, + (const char *)pBuf, + (int)nMax, SQLITE_TRANSIENT); + JBA_RELEASE(baData, pBuf); + return (jint)rc; +} + +JDECL(jint,1bind_1text16)(JENV_CSELF, jobject jpStmt, + jint ndx, jbyteArray baData, jint nMax){ + jbyte * const pBuf = baData ? JBA_TOC(baData) : 0; + int const rc = sqlite3_bind_text16(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, + pBuf, (int)nMax, SQLITE_TRANSIENT); + JBA_RELEASE(baData, pBuf); + return (jint)rc; } JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt, @@ -3595,18 +3596,19 @@ JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){ } static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){ - int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal)); + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const nLen = sqlite3_value_bytes16(sv); jbyteArray jba; const jbyte * pBytes; switch(mode){ case SQLITE_UTF16: - pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal)); + pBytes = sqlite3_value_text16(sv); break; case SQLITE_UTF16LE: - pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal)); + pBytes = sqlite3_value_text16le(sv); break; case SQLITE_UTF16BE: - pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal)); + pBytes = sqlite3_value_text16be(sv); break; default: assert(!"not possible"); diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 86fada9574..8052a6027e 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -843,6 +843,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1 JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text16 + (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_bind_zeroblob diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java index fcad273d3d..443345fde4 100644 --- a/ext/jni/src/org/sqlite/jni/AutoExtension.java +++ b/ext/jni/src/org/sqlite/jni/AutoExtension.java @@ -14,24 +14,24 @@ package org.sqlite.jni; /** - A callback for use with sqlite3_auto_extension(). + A callback for use with the sqlite3_auto_extension() family of + APIs. */ public interface AutoExtension { /** Must function as described for a sqlite3_auto_extension() - callback, with the caveat that the signature is more limited. + callback, with the caveat that the signature is shorter. - As an exception (as it were) to the callbacks-must-not-throw - rule, AutoExtensions may throw and the exception's error message + AutoExtensions may throw and the exception's error message will be set as the db's error string. - Hints for implementations: + Tips 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 + - If this routine is stateful, it may be useful to make the overridden method synchronized. - Results are undefined if db is closed by an auto-extension. diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 29ed0e5602..ef67890918 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -154,32 +154,35 @@ public final class SQLite3Jni { Functions almost as documented for the C API, with these exceptions: - - The callback interface is more limited because of - cross-language differences. Specifically, auto-extensions do - not have access to the sqlite3_api object which native - auto-extensions do. + - The callback interface is is shorter because of cross-language + differences. Specifically, 3rd argument to the C auto-extension + callback interface is unnecessary here. - - If the list of auto-extensions is manipulated from an - auto-extension, it is undefined which, if any, auto-extensions - will subsequently execute for the current database (it depends - on multiple factors). + + The C API docs do not specifically say so, if the list of + auto-extensions is manipulated from an auto-extension, it is + undefined which, if any, auto-extensions will subsequently + execute for the current database. See the AutoExtension class docs for more information. */ public static native int sqlite3_auto_extension(@NotNull AutoExtension callback); + /** + Results are undefined if data is not null and n<0 || n>=data.length. + */ + public static native int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n + ); + public static int sqlite3_bind_blob( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data ){ - return (null == data) + return (null==data) ? sqlite3_bind_null(stmt, ndx) : sqlite3_bind_blob(stmt, ndx, data, data.length); } - private static native int sqlite3_bind_blob( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n - ); - public static native int sqlite3_bind_double( @NotNull sqlite3_stmt stmt, int ndx, double v ); @@ -200,13 +203,10 @@ public final class SQLite3Jni { @NotNull sqlite3_stmt stmt ); - /** - 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. + Requires that paramName be a NUL-terminated UTF-8 string. */ - private static native int sqlite3_bind_parameter_index( + public static native int sqlite3_bind_parameter_index( @NotNull sqlite3_stmt stmt, byte[] paramName ); @@ -218,14 +218,22 @@ public final class SQLite3Jni { } /** - 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. + Works like the C-level sqlite3_bind_text() but assumes + SQLITE_TRANSIENT for the final C API parameter. + + Results are undefined if data is not null and + maxBytes>=data.length. If maxBytes is negative then results are + undefined if data is not null and does not contain a NUL byte. */ private static native int sqlite3_bind_text( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes ); + /** + Converts data, if not null, to a UTF-8-encoded byte array and + binds it as such, returning the result of the C-level + sqlite3_bind_null() or sqlite3_bind_text(). + */ public static int sqlite3_bind_text( @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data ){ @@ -234,6 +242,9 @@ public final class SQLite3Jni { return sqlite3_bind_text(stmt, ndx, utf8, utf8.length); } + /** + Requires that data be null or in UTF-8 encoding. + */ public static int sqlite3_bind_text( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data ){ @@ -242,6 +253,41 @@ public final class SQLite3Jni { : sqlite3_bind_text(stmt, ndx, data, data.length); } + /** + Identical to the sqlite3_bind_text() overload with the same + signature but requires that its input be encoded in UTF-16 in + platform byte order. + */ + private static native int sqlite3_bind_text16( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes + ); + + /** + Converts its string argument to UTF-16 and binds it as such, returning + the result of the C-side function of the same name. The 3rd argument + may be null. + */ + public static int sqlite3_bind_text16( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data + ){ + if(null == data) return sqlite3_bind_null(stmt, ndx); + final byte[] bytes = data.getBytes(StandardCharsets.UTF_16); + return sqlite3_bind_text16(stmt, ndx, bytes, bytes.length); + } + + /** + Requires that data be null or in UTF-16 encoding in platform byte + order. Returns the result of the C-level sqlite3_bind_null() or + sqlite3_bind_text(). + */ + public static int sqlite3_bind_text16( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data + ){ + return (null == data) + ? sqlite3_bind_null(stmt, ndx) + : sqlite3_bind_text16(stmt, ndx, data, data.length); + } + public static native int sqlite3_bind_zeroblob( @NotNull sqlite3_stmt stmt, int ndx, int n ); @@ -253,8 +299,7 @@ public final class SQLite3Jni { /** As for the C-level function of the same name, with a BusyHandler instance in place of a callback function. Pass it a null handler - to clear the busy handler. Calling this multiple times with the - same object is a no-op on the second and subsequent calls. + to clear the busy handler. */ public static native int sqlite3_busy_handler( @NotNull sqlite3 db, @Nullable BusyHandler handler @@ -595,15 +640,41 @@ public final class SQLite3Jni { @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb ); + /** + Convenience overload which returns its db handle directly. The returned + object might not have been successfully opened: use sqlite3_errcode() to + check whether it is in an error state. + + Ownership of the returned value is passed to the caller, who must eventually + pass it to sqlite3_close() or sqlite3_close_v2(). + */ + public static sqlite3 sqlite3_open(@Nullable String filename){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + sqlite3_open(filename, out); + return out.take(); + }; + public static native int sqlite3_open_v2( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, int flags, @Nullable String zVfs ); + /** + Has the same semantics as the sqlite3-returning sqlite3_open() + but uses sqlite3_open_v2() instead of sqlite3_open(). + */ + public static sqlite3 sqlite3_open_v2(@Nullable String filename, int flags, + @Nullable String zVfs){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + sqlite3_open_v2(filename, out, flags, zVfs); + return out.take(); + }; + /** The sqlite3_prepare() family of functions require slightly - different signatures than their native counterparts, but - overloading allows us to install several convenience forms. + different signatures than their native counterparts, but (A) they + retain functionally equivalent semantics and (B) overloading + allows us to install several convenience forms. All of them which take their SQL in the form of a byte[] require that it be in UTF-8 encoding unless explicitly noted otherwise. @@ -648,6 +719,26 @@ public final class SQLite3Jni { return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); } + /** + Convenience overload which returns its statement handle directly, + or null on error or when reading only whitespace or + comments. sqlite3_errcode() can be used to determine whether + there was an error or the input was empty. Ownership of the + returned object is passed to the caller, who must eventually pass + it to sqlite3_finalize(). + */ + public static sqlite3_stmt sqlite3_prepare( + @NotNull sqlite3 db, @NotNull String sql + ){ + final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); + sqlite3_prepare(db, sql, out); + return out.take(); + } + + /** + See sqlite3_prepare() for details about the slight API differences + from the C API. + */ private static native int sqlite3_prepare_v2( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, @NotNull OutputPointer.sqlite3_stmt outStmt, @@ -677,6 +768,18 @@ public final class SQLite3Jni { return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); } + /** + Works identically to the sqlite3_stmt-returning sqlite3_prepare() + but uses sqlite3_prepare_v2(). + */ + public static sqlite3_stmt sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull String sql + ){ + final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); + sqlite3_prepare_v2(db, sql, out); + return out.take(); + } + private static native int sqlite3_prepare_v3( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt, @@ -706,6 +809,18 @@ public final class SQLite3Jni { return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); } + /** + Works identically to the sqlite3_stmt-returning sqlite3_prepare() + but uses sqlite3_prepare_v3(). + */ + public static sqlite3_stmt sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull String sql, int prepFlags + ){ + final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); + sqlite3_prepare_v3(db, sql, prepFlags, out); + return out.take(); + } + /** If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index c655ae7ffd..bafc73bf68 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -21,6 +21,16 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +/** + An annotation for Tester1 tests which we do not want to run in + reflection-driven test mode because either they are not suitable + for multi-threaded threaded mode or we have to control their execution + order. +*/ +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) +@interface ManualTest{} + public class Tester1 implements Runnable { //! True when running in multi-threaded mode. private static boolean mtMode = false; @@ -30,6 +40,10 @@ public class Tester1 implements Runnable { private static boolean shuffle = false; //! True to dump the list of to-run tests to stdout. private static boolean listRunTests = false; + //! True to squelch all out() and outln() output. + private static boolean quietMode = false; + //! Total number of runTests() calls. + private static int nTestRuns = 0; //! List of test*() methods to run. private static List testMethods = null; //! List of exceptions collected by run() @@ -48,27 +62,37 @@ public class Tester1 implements Runnable { static final Metrics metrics = new Metrics(); public synchronized static void outln(){ - System.out.println(""); + if( !quietMode ){ + System.out.println(""); + } } public synchronized static void outln(Object val){ - System.out.print(Thread.currentThread().getName()+": "); - System.out.println(val); + if( !quietMode ){ + System.out.print(Thread.currentThread().getName()+": "); + System.out.println(val); + } } public synchronized static void out(Object val){ - System.out.print(val); + if( !quietMode ){ + System.out.print(val); + } } @SuppressWarnings("unchecked") public synchronized static void out(Object... vals){ - System.out.print(Thread.currentThread().getName()+": "); - for(Object v : vals) out(v); + if( !quietMode ){ + System.out.print(Thread.currentThread().getName()+": "); + for(Object v : vals) out(v); + } } @SuppressWarnings("unchecked") public synchronized static void outln(Object... vals){ - out(vals); out("\n"); + if( !quietMode ){ + out(vals); out("\n"); + } } static volatile int affirmCount = 0; @@ -85,6 +109,7 @@ public class Tester1 implements Runnable { affirm(v, "Affirmation failed."); } + @ManualTest /* because testing this for threading is pointless */ private void test1(){ affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); affirm(SQLITE_MAX_LENGTH > 0); @@ -280,6 +305,16 @@ public class Tester1 implements Runnable { affirm(0 != stmt.getNativePointer()); sqlite3_finalize(stmt); affirm(0 == stmt.getNativePointer() ); + + affirm( 0==sqlite3_errcode(db) ); + stmt = sqlite3_prepare(db, "intentional error"); + affirm( null==stmt ); + affirm( 0!=sqlite3_errcode(db) ); + affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") ); + sqlite3_finalize(stmt); + stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only"); + affirm( null==stmt ); + affirm( 0==sqlite3_errcode(db) ); sqlite3_close_v2(db); } @@ -383,8 +418,11 @@ public class Tester1 implements Runnable { sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); String[] list1 = { "hell🤩", "w😃rld", "!" }; int rc; + int n = 0; for( String e : list1 ){ - rc = sqlite3_bind_text(stmt, 1, e); + rc = (0==n) + ? sqlite3_bind_text(stmt, 1, e) + : sqlite3_bind_text16(stmt, 1, e); affirm(0 == rc); rc = sqlite3_step(stmt); affirm(SQLITE_DONE==rc); @@ -393,7 +431,7 @@ public class Tester1 implements Runnable { sqlite3_finalize(stmt); stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); StringBuilder sbuf = new StringBuilder(); - int n = 0; + n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ String txt = sqlite3_column_text16(stmt, 0); //outln("txt = "+txt); @@ -521,6 +559,7 @@ public class Tester1 implements Runnable { affirm(xDestroyCalled.value); } + @ManualTest /* because threading is meaningless here */ private void testToUtf8(){ /** https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html @@ -873,6 +912,7 @@ public class Tester1 implements Runnable { affirm( 7 == counter.value ); } + @ManualTest /* because threads inherently break this test */ private void testBusy(){ final String dbName = "_busy-handler.db"; final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); @@ -1156,6 +1196,8 @@ public class Tester1 implements Runnable { it throws. */ @SuppressWarnings("unchecked") + @ManualTest /* because the Fts5 parts are not yet known to be + thread-safe */ private void testFts5() throws Exception { if( !sqlite3_compileoption_used("ENABLE_FTS5") ){ //outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests."); @@ -1206,6 +1248,8 @@ public class Tester1 implements Runnable { sqlite3_close(db); } + @ManualTest/* because multiple threads legitimately make these + results unpredictable */ private synchronized void testAutoExtension(){ final ValueHolder val = new ValueHolder<>(0); final ValueHolder toss = new ValueHolder<>(null); @@ -1296,6 +1340,7 @@ public class Tester1 implements Runnable { affirm( 8 == val.value ); } + @ManualTest /* because we only want to run this test manually */ private void testSleep(){ out("Sleeping briefly... "); sqlite3_sleep(600); @@ -1308,6 +1353,7 @@ public class Tester1 implements Runnable { } } + @ManualTest /* because we only want to run this test on demand */ private void testFail(){ affirm( false, "Intentional failure." ); } @@ -1355,6 +1401,9 @@ public class Tester1 implements Runnable { testFts5(); } } + synchronized( this.getClass() ){ + ++nTestRuns; + } } public void run() { @@ -1375,6 +1424,8 @@ public class Tester1 implements Runnable { CLI flags: + -q|-quiet: disables most test output. + -t|-thread N: runs the tests in N threads concurrently. Default=1. @@ -1400,6 +1451,7 @@ public class Tester1 implements Runnable { Integer nRepeat = 1; boolean forceFail = false; boolean sqlLog = false; + boolean squelchTestOutput = false; for( int i = 0; i < args.length; ){ String arg = args[i++]; if(arg.startsWith("-")){ @@ -1421,6 +1473,8 @@ public class Tester1 implements Runnable { sqlLog = true; }else if(arg.equals("naps")){ takeNaps = true; + }else if(arg.equals("q") || arg.equals("quiet")){ + squelchTestOutput = true; }else{ throw new IllegalArgumentException("Unhandled flag:"+arg); } @@ -1430,19 +1484,16 @@ public class Tester1 implements Runnable { { // Build list of tests to run from the methods named test*(). testMethods = new ArrayList<>(); - final List excludes = new ArrayList<>(); - // Tests we want to control the order of: - if( !forceFail ) excludes.add("testFail"); - excludes.add("test1"); - excludes.add("testAutoExtension"); - excludes.add("testBusy"); - excludes.add("testFts5"); - excludes.add("testSleep"); - excludes.add("testToUtf8"); - for(java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){ + for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){ final String name = m.getName(); - if( name.startsWith("test") && excludes.indexOf(name)<0 ){ - testMethods.add(m); + if( name.equals("testFail") ){ + if( forceFail ){ + testMethods.add(m); + } + }else if( !m.isAnnotationPresent( ManualTest.class ) ){ + if( name.startsWith("test") ){ + testMethods.add(m); + } } } } @@ -1465,6 +1516,11 @@ public class Tester1 implements Runnable { } } + quietMode = squelchTestOutput; + outln("If you just saw warning messages regarding CallStaticObjectMethod, ", + "you are very likely seeing the side effects of a known openjdk8 ", + "bug. It is unsightly but does not affect the library."); + final long timeStart = System.currentTimeMillis(); int nLoop = 0; affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ), @@ -1476,7 +1532,7 @@ public class Tester1 implements Runnable { outln("libversion_number: ", sqlite3_libversion_number(),"\n", sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); - outln("Running ",nRepeat," loop(s) over ",nThread," thread(s)."); + outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each."); if( takeNaps ) outln("Napping between tests is enabled."); for( int n = 0; n < nRepeat; ++n ){ if( nThread==null || nThread<=1 ){ @@ -1500,6 +1556,7 @@ public class Tester1 implements Runnable { Thread.currentThread().interrupt(); } if( !listErrors.isEmpty() ){ + quietMode = false; outln("TEST ERRORS:"); Exception err = null; for( Exception e : listErrors ){ @@ -1510,9 +1567,10 @@ public class Tester1 implements Runnable { } } outln(); + quietMode = false; final long timeEnd = System.currentTimeMillis(); - outln("Tests done. Metrics:"); + outln("Tests done. Metrics across ",nTestRuns," total iteration(s):"); outln("\tAssertions checked: ",affirmCount); outln("\tDatabases opened: ",metrics.dbOpen); if( doSomethingForDev ){ diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index 3856573169..feb6d6303d 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -72,13 +72,18 @@ public class TesterFts5 { affirm( xDestroyCalled.value ); } - public TesterFts5(){ - final long timeStart = System.currentTimeMillis(); - final int oldAffirmCount = Tester1.affirmCount; - test1(); - final int affirmCount = Tester1.affirmCount - oldAffirmCount; - final long timeEnd = System.currentTimeMillis(); - outln("FTS5 Tests done. Assertions checked = ",affirmCount, - ", Total time = ",(timeEnd - timeStart),"ms"); + public TesterFts5(boolean verbose){ + if(verbose){ + final long timeStart = System.currentTimeMillis(); + final int oldAffirmCount = Tester1.affirmCount; + test1(); + final int affirmCount = Tester1.affirmCount - oldAffirmCount; + final long timeEnd = System.currentTimeMillis(); + outln("FTS5 Tests done. Assertions checked = ",affirmCount, + ", Total time = ",(timeEnd - timeStart),"ms"); + }else{ + test1(); + } } + public TesterFts5(){ this(false); } } diff --git a/manifest b/manifest index f1c1761dd4..2da52989e3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C JNI\scleanups\sregarding\sbuilding\swith\scertain\sfeatures\sdisabled. -D 2023-08-23T17:52:51.175 +C Add\smore\sJNI\sdocs,\stests,\sand\sa\shandful\sof\sJava-side\soverloads. +D 2023-08-24T11:57:51.863 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,13 +232,13 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 14b7c3abd1ae8693203b08b0e06bb359f8924ad2243f15953e9c6e456ae317b5 -F ext/jni/README.md 1693e865d366f5ebaa756732ea0d4b786515caf3cfbcd4dcb8758274373913b0 -F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3 -F ext/jni/src/c/sqlite3-jni.c 0ca96134d7fb3f313a7a49487f68a8d7a6d7545470c84532aa1ce63d2cdc432e -F ext/jni/src/c/sqlite3-jni.h c5cb0348efe4e5f3d125a240e2437e8475de14a586c2f859e2acdcde4116244d +F ext/jni/GNUmakefile 0a823c56f081294e7797dae303380ac989ebaa801bba970968342b7358f07aed +F ext/jni/README.md 64bf1da0d562d051207ca1c5cfa52e8b7a69120533cc034a3da7670ef920cbef +F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa +F ext/jni/src/c/sqlite3-jni.c e1e3cde4d08925282b5bc949f9ed8f613a6a2c6f60d0c697e79d59fb49f9fe4b +F ext/jni/src/c/sqlite3-jni.h cc24d6742b29a52338ffd3b47caf923facb8ae77f9c2fc9c2de82673bf339ea2 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 -F ext/jni/src/org/sqlite/jni/AutoExtension.java 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd +F ext/jni/src/org/sqlite/jni/AutoExtension.java bcc1849b2fccbe5e2d7ac9e9ac7f8d05a6d7088a8fedbaad90e39569745a61e6 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 @@ -257,9 +257,9 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java f697cf2a81c4119f2baf0682af689686f0466f1dd83dba00885f5603e693fe16 F ext/jni/src/org/sqlite/jni/SQLLog.java c60610b35208416940822e834d61f08fbbe5d6e06b374b541b49e41fd56c9798 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java f64554457fa30a048ef99374bfac3c4b986e3528353dce3086a98a858e3fe000 -F ext/jni/src/org/sqlite/jni/Tester1.java 69ea63a5b235f94f914dff6fe3ecd103ee0a8023b8737db071b46c0c75375e26 -F ext/jni/src/org/sqlite/jni/TesterFts5.java de095e3b701fba0c56d7b8b2993dc22bcbaa9de8f992904a93729ad729a91576 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 956063c854c4f662183c41c65d0ab48b5e2127824b8053eeb05b9fc40f0d09e3 +F ext/jni/src/org/sqlite/jni/Tester1.java b5a4bb2a969df053d5c138887f04039a79b36170372a2efdf5dfbd6ac90db4c9 +F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2094,8 +2094,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 d67255f7251cc5d1d27d77d4c84ff216e2da71202db989718189a6b4beff1cd0 -R b911867dcaf18c3e131e156c82d306fe +P a9e6d5158b8a4a6b8554a5f8f0a35785ee450d42ea877275dc27085e89716c18 +R cb79df5ad40cc86377d98f5a1329b589 U stephan -Z bdef86836c9254e732e1b6d744febf17 +Z 39e8fbdcbc2ebaa58ed84b96f0374c06 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5245bf9cfe..180d574846 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a9e6d5158b8a4a6b8554a5f8f0a35785ee450d42ea877275dc27085e89716c18 \ No newline at end of file +d19a431facbde6a6b960664674753ee85d2c051a76109ce7db0b079c65fbdea0 \ No newline at end of file From e7a46858371a7146fce62f96bb1dbae94cf9b4f3 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 24 Aug 2023 14:31:36 +0000 Subject: [PATCH 37/37] Correct JNI layer's misuse of an sqlite3-internal error-reporting API (no mutex held). Style cleanups. Eliminate lookups of per-thread state by approximately 85% across the test suite. FossilOrigin-Name: 1f46ba8d3bc61af771c1e33d09ad25f0da4fc4f915f7a9f6223ebfd99526d81d --- ext/jni/src/c/sqlite3-jni.c | 172 +++++++++++------------- ext/jni/src/org/sqlite/jni/Tester1.java | 3 +- manifest | 14 +- manifest.uuid | 2 +- 4 files changed, 92 insertions(+), 99 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 03ba8be57a..7c4aa7c28b 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -303,7 +303,7 @@ static const struct { /** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ #define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \ - return s3jni_utf8_to_jstring(S3JniGlobal_env_cache(env), \ + return s3jni_utf8_to_jstring(env, \ CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx), \ -1); \ } @@ -324,10 +324,10 @@ static const struct { } /* Helpers for jstring and jbyteArray. */ -#define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL) -#define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR) -#define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) -#define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) +#define s3jni_jstring_to_mutf8(ARG) (*env)->GetStringUTFChars(env, ARG, NULL) +#define s3jni_mutf8_release(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR) +#define s3jni_jbytearray_bytes(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) +#define s3jni_jbytearray_release(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) enum { /* @@ -423,9 +423,7 @@ struct S3JniEnv { ** ** - 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. + ** object. ** ** - Call sqlite3_open(), which triggers the auto-extension ** handler. That handler uses pdbOpening to connect the native @@ -631,7 +629,7 @@ static void s3jni_incr( volatile unsigned int * const p ){ SJG.perDb.locker = 0; \ sqlite3_mutex_leave( SJG.perDb.mutex ) -#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) +#define s3jni_oom_check(VAR) if(!(VAR)) s3jni_oom(env) static inline void s3jni_oom(JNIEnv * const env){ (*env)->FatalError(env, "Out of memory.") /* does not return */; } @@ -706,7 +704,9 @@ static int s3jni_db_error(sqlite3* const db, int err_code, sqlite3Error(db, err_code); }else{ const int nMsg = sqlite3Strlen30(zMsg); + sqlite3_mutex_enter(sqlite3_db_mutex(db)); sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); } } return err_code; @@ -753,10 +753,9 @@ static JNIEnv * s3jni_get_env(void){ ** standard UTF-8 to a Java string, but JNI offers only algorithms for ** working with MUTF-8, not UTF-8. */ -static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc, +static jstring s3jni_utf8_to_jstring(JNIEnv * const env, const char * const z, int n){ jstring rv = NULL; - 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 @@ -790,9 +789,8 @@ static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc, ** The returned memory is allocated from sqlite3_malloc() and ** ownership is transferred to the caller. */ -static char * s3jni_jstring_to_utf8(S3JniEnv * const jc, +static char * s3jni_jstring_to_utf8(JNIEnv * const env, jstring jstr, int *nLen){ - LocalJniGetEnv; jbyteArray jba; jsize nBa; char *rv; @@ -844,7 +842,6 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, ** System.out.println(e.getMessage()); // Hi */ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ - S3JniEnv * const jc = S3JniGlobal_env_cache(env); jmethodID mid; jstring msg; char * zMsg; @@ -862,7 +859,7 @@ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ EXCEPTION_CLEAR; return 0; } - zMsg = s3jni_jstring_to_utf8(jc, msg, 0); + zMsg = s3jni_jstring_to_utf8(env, msg, 0); UNREF_L(msg); return zMsg; } @@ -1484,7 +1481,7 @@ static jobject new_NativePointerHolder_object(JNIEnv * const env, S3NphRef const } rv = (*env)->NewObject(env, pNC->klazz, pNC->midCtor); EXCEPTION_IS_FATAL("No-arg constructor threw."); - OOM_CHECK(rv); + s3jni_oom_check(rv); if(rv) NativePointerHolder_set(env, rv, pNative, pRef); return rv; } @@ -1807,6 +1804,16 @@ 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) +#undef WRAP_INT64_DB +#undef WRAP_INT_DB +#undef WRAP_INT_INT +#undef WRAP_INT_STMT +#undef WRAP_INT_STMT_INT +#undef WRAP_INT_SVALUE +#undef WRAP_INT_VOID +#undef WRAP_MUTF8_VOID +#undef WRAP_STR_STMT_INT + /* Central auto-extension handler. */ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr, const struct sqlite3_api_routines *ignored){ @@ -1915,10 +1922,10 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){ JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ - jbyte * const pBuf = baData ? JBA_TOC(baData) : 0; + jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0; int const rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, SQLITE_TRANSIENT); - JBA_RELEASE(baData,pBuf); + s3jni_jbytearray_release(baData,pBuf); return (jint)rc; } @@ -1944,31 +1951,31 @@ JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt, JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){ int rc = 0; - jbyte * const pBuf = JBA_TOC(jName); + jbyte * const pBuf = s3jni_jbytearray_bytes(jName); if(pBuf){ rc = sqlite3_bind_parameter_index(PtrGet_sqlite3_stmt(jpStmt), (const char *)pBuf); - JBA_RELEASE(jName, pBuf); + s3jni_jbytearray_release(jName, pBuf); } return rc; } JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ - jbyte * const pBuf = baData ? JBA_TOC(baData) : 0; + jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0; int const rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, (int)nMax, SQLITE_TRANSIENT); - JBA_RELEASE(baData, pBuf); + s3jni_jbytearray_release(baData, pBuf); return (jint)rc; } JDECL(jint,1bind_1text16)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ - jbyte * const pBuf = baData ? JBA_TOC(baData) : 0; + jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0; int const rc = sqlite3_bind_text16(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, SQLITE_TRANSIENT); - JBA_RELEASE(baData, pBuf); + s3jni_jbytearray_release(baData, pBuf); return (jint)rc; } @@ -2295,10 +2302,10 @@ JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){ } JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ - const char *zUtf8 = JSTR_TOC(name); + const char *zUtf8 = s3jni_jstring_to_mutf8(name); const jboolean rc = 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE; - JSTR_RELEASE(name, zUtf8); + s3jni_mutf8_release(name, zUtf8); return rc; } @@ -2323,7 +2330,6 @@ static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int jobject jArg0 = 0; jstring jArg1 = 0; LocalJniGetEnv; - S3JniEnv * const jc = S3JniGlobal_env_cache(env); S3JniDb * const ps = S3JniDb_for_db(env, 0, pDb); S3JniHook * const hook = &SJG.hooks.sqllog; @@ -2332,7 +2338,7 @@ static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int switch(op){ case 0: /* db opened */ case 1: /* SQL executed */ - jArg1 = s3jni_utf8_to_jstring(jc, z, -1); + jArg1 = s3jni_utf8_to_jstring(env, z, -1); break; case 2: /* db closed */ break; @@ -2420,11 +2426,11 @@ JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, return s3jni_db_error(ps->pDb, SQLITE_ERROR, "Could not get xCompare() method for object."); } - zName = JSTR_TOC(name); + zName = s3jni_jstring_to_mutf8(name); rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep, ps, CollationState_xCompare, CollationState_xDestroy); - JSTR_RELEASE(name, zName); + s3jni_mutf8_release(name, zName); if( 0==rc ){ pHook->jObj = REF_G(oCollation); }else{ @@ -2438,7 +2444,7 @@ JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, S3JniUdf * s = 0; int rc; sqlite3 * const pDb = PtrGet_sqlite3(jDb); - const char * zFuncName = 0; + char * zFuncName = 0; if( !encodingTypeIsValid(eTextRep) ){ return s3jni_db_error(pDb, SQLITE_FORMAT, @@ -2452,7 +2458,7 @@ JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, S3JniUdf_free(s); goto error_cleanup; } - zFuncName = JSTR_TOC(jFuncName); + zFuncName = s3jni_jstring_to_utf8(env,jFuncName,0); if(!zFuncName){ rc = SQLITE_NOMEM; S3JniUdf_free(s); @@ -2476,14 +2482,8 @@ JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s, xFunc, xStep, xFinal, S3JniUdf_finalizer); } - if( 0==rc ){ - s->zFuncName = sqlite3_mprintf("%s", zFuncName) - /* OOM here is non-fatal. Ignore it. Handling it would require - ** re-calling the appropriate create_function() func with 0 - ** for all xAbc args so that s would be finalized. */; - } error_cleanup: - JSTR_RELEASE(jFuncName, zFuncName); + sqlite3_free(zFuncName); /* on sqlite3_create_function() error, s will be destroyed via ** create_function(), so we're not leaking s. */ return (jint)rc; @@ -2499,7 +2499,7 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( switch( (ps && jStr) ? op : 0 ){ case SQLITE_DBCONFIG_MAINDBNAME: - zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0); + zStr = s3jni_jstring_to_utf8(env, jStr, 0); if( zStr ){ rc = sqlite3_db_config(ps->pDb, (int)op, zStr); if( rc ){ @@ -2577,7 +2577,6 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0); - S3JniEnv * const jc = ps ? S3JniGlobal_env_cache(env) : 0; char *zDbName; jstring jRv = 0; int nStr = 0; @@ -2585,12 +2584,12 @@ JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ if( !ps || !jDbName ){ return 0; } - zDbName = s3jni_jstring_to_utf8(jc, jDbName, &nStr); + zDbName = s3jni_jstring_to_utf8(env, jDbName, &nStr); if( zDbName ){ char const * zRv = sqlite3_db_filename(ps->pDb, zDbName); sqlite3_free(zDbName); if( zRv ){ - jRv = s3jni_utf8_to_jstring(jc, zRv, -1); + jRv = s3jni_utf8_to_jstring(env, zRv, -1); } } return jRv; @@ -2617,8 +2616,7 @@ JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){ JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); - S3JniEnv * const jc = pDb ? S3JniGlobal_env_cache(env) : 0; - return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0; + return pDb ? s3jni_utf8_to_jstring(env, sqlite3_errmsg(pDb), -1) : 0; } JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){ @@ -2631,11 +2629,10 @@ JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); jstring rv = 0; if( pStmt ){ - S3JniEnv * const jc = S3JniGlobal_env_cache(env); char * zSql = sqlite3_expanded_sql(pStmt); - OOM_CHECK(zSql); + s3jni_oom_check(zSql); if( zSql ){ - rv = s3jni_utf8_to_jstring(jc, zSql, -1); + rv = s3jni_utf8_to_jstring(env, zSql, -1); sqlite3_free(zSql); } } @@ -2692,7 +2689,7 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, rc = SQLITE_NOMEM; goto end; } - *zDbName = jDbName ? s3jni_jstring_to_utf8(*jc, jDbName, 0) : 0; + *zDbName = jDbName ? s3jni_jstring_to_utf8(env, jDbName, 0) : 0; if(jDbName && !*zDbName){ rc = SQLITE_NOMEM; goto end; @@ -2771,7 +2768,7 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, char *zVfs = 0; int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps); if( 0==rc && strVfs ){ - zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); + zVfs = s3jni_jstring_to_utf8(env, strVfs, 0); if( !zVfs ){ rc = SQLITE_NOMEM; } @@ -2794,7 +2791,7 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass sqlite3_stmt * pStmt = 0; jobject jStmt = 0; const char * zTail = 0; - jbyte * const pBuf = JBA_TOC(baSql); + jbyte * const pBuf = s3jni_jbytearray_bytes(baSql); int rc = SQLITE_ERROR; assert(prepVersion==1 || prepVersion==2 || prepVersion==3); if( !pBuf ){ @@ -2821,7 +2818,7 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass assert(0 && "Invalid prepare() version"); } end: - JBA_RELEASE(baSql,pBuf); + s3jni_jbytearray_release(baSql,pBuf); if( 0==rc ){ if( 0!=outTail ){ /* Noting that pBuf is deallocated now but its address is all we need for @@ -2879,7 +2876,6 @@ static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId, sqlite3_int64 iKey1, sqlite3_int64 iKey2){ S3JniDb * const ps = pState; LocalJniGetEnv; - S3JniEnv * const jc = S3JniGlobal_env_cache(env); jstring jDbName; jstring jTable; S3JniHook * pHook; @@ -2894,8 +2890,8 @@ static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId, : &ps->hooks.update; assert( pHook ); - jDbName = s3jni_utf8_to_jstring(jc, zDb, -1); - jTable = jDbName ? s3jni_utf8_to_jstring(jc, zTable, -1) : 0; + jDbName = s3jni_utf8_to_jstring(env, zDb, -1); + jTable = jDbName ? s3jni_utf8_to_jstring(env, zTable, -1) : 0; IFTHREW { EXCEPTION_CLEAR; s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); @@ -3131,7 +3127,7 @@ static void result_blob_text(int asBlob, int as64, JNIEnv * const env, sqlite3_context *pCx, jbyteArray jBa, jlong nMax){ if(jBa){ - jbyte * const pBuf = JBA_TOC(jBa); + jbyte * const pBuf = s3jni_jbytearray_bytes(jBa); jsize nBa = (*env)->GetArrayLength(env, jBa); if( nMax>=0 && nBa>(jsize)nMax ){ nBa = (jsize)nMax; @@ -3196,7 +3192,7 @@ static void result_blob_text(int asBlob, int as64, break; } } - JBA_RELEASE(jBa, pBuf); + s3jni_jbytearray_release(jBa, pBuf); } }else{ sqlite3_result_null(pCx); @@ -3219,7 +3215,7 @@ JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, int eTextRep){ const char * zUnspecified = "Unspecified error."; jsize const baLen = (*env)->GetArrayLength(env, baMsg); - jbyte * const pjBuf = baMsg ? JBA_TOC(baMsg) : NULL; + jbyte * const pjBuf = baMsg ? s3jni_jbytearray_bytes(baMsg) : NULL; switch(pjBuf ? eTextRep : SQLITE_UTF8){ case SQLITE_UTF8: { const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified; @@ -3238,7 +3234,7 @@ JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, "to sqlite3_result_error().", -1); break; } - JBA_RELEASE(baMsg,pjBuf); + s3jni_jbytearray_release(baMsg,pjBuf); } JDECL(void,1result_1error_1code)(JENV_CSELF, jobject jpCx, jint v){ @@ -3313,12 +3309,11 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, const char*z2,const char*z3){ S3JniDb * const ps = pState; LocalJniGetEnv; - S3JniEnv * const jc = S3JniGlobal_env_cache(env); S3JniHook const * const pHook = &ps->hooks.auth; - jstring const s0 = z0 ? s3jni_utf8_to_jstring(jc, z0, -1) : 0; - jstring const s1 = z1 ? s3jni_utf8_to_jstring(jc, z1, -1) : 0; - jstring const s2 = z2 ? s3jni_utf8_to_jstring(jc, z2, -1) : 0; - jstring const s3 = z3 ? s3jni_utf8_to_jstring(jc, z3, -1) : 0; + jstring const s0 = z0 ? s3jni_utf8_to_jstring(env, z0, -1) : 0; + jstring const s1 = z1 ? s3jni_utf8_to_jstring(env, z1, -1) : 0; + jstring const s2 = z2 ? s3jni_utf8_to_jstring(env, z2, -1) : 0; + jstring const s3 = z3 ? s3jni_utf8_to_jstring(env, z3, -1) : 0; int rc; assert( pHook->jObj ); @@ -3405,18 +3400,18 @@ JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh static int s3jni_strlike_glob(int isLike, JNIEnv *const env, jbyteArray baG, jbyteArray baT, jint escLike){ int rc = 0; - jbyte * const pG = JBA_TOC(baG); - jbyte * const pT = pG ? JBA_TOC(baT) : 0; + jbyte * const pG = s3jni_jbytearray_bytes(baG); + jbyte * const pT = pG ? s3jni_jbytearray_bytes(baT) : 0; - OOM_CHECK(pT); + s3jni_oom_check(pT); /* Note that we're relying on the byte arrays having been NUL-terminated on the Java side. */ rc = isLike ? sqlite3_strlike((const char *)pG, (const char *)pT, (unsigned int)escLike) : sqlite3_strglob((const char *)pG, (const char *)pT); - JBA_RELEASE(baG, pG); - JBA_RELEASE(baT, pT); + s3jni_jbytearray_release(baG, pG); + s3jni_jbytearray_release(baT, pT); return rc; } @@ -3445,10 +3440,9 @@ JDECL(jstring,1sql)(JENV_CSELF, jobject jpStmt){ jstring rv = 0; if( pStmt ){ const char * zSql = 0; - S3JniEnv * const jc = S3JniGlobal_env_cache(env); zSql = sqlite3_sql(pStmt); - rv = s3jni_utf8_to_jstring(jc, zSql, -1); - OOM_CHECK(rv); + rv = s3jni_utf8_to_jstring(env, zSql, -1); + s3jni_oom_check(rv); } return rv; } @@ -3468,12 +3462,11 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ 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 */; - S3JniEnv * const jc = S3JniGlobal_env_cache(env); int rc; int createStmt = 0; switch(traceflag){ case SQLITE_TRACE_STMT: - jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1); + jX = s3jni_utf8_to_jstring(env, (const char *)pX, -1); if(!jX) return SQLITE_NOMEM; /*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/ createStmt = 1; @@ -3858,8 +3851,7 @@ JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, &pz, &pn); if( 0==rc ){ - S3JniEnv * const jc = S3JniGlobal_env_cache(env); - jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0; + jstring jstr = pz ? s3jni_utf8_to_jstring(env, pz, pn) : 0; if( pz ){ if( jstr ){ OutputPointer_set_String(env, jOut, jstr); @@ -3929,7 +3921,7 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, Fts5JniAux * pAux; assert(pApi); - zName = JSTR_TOC(jName); + zName = s3jni_jstring_to_mutf8(jName); if(!zName) return SQLITE_NOMEM; pAux = Fts5JniAux_alloc(env, jFunc); if( pAux ){ @@ -3944,7 +3936,7 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, pAux->zFuncName = sqlite3_mprintf("%s", zName) /* OOM here is non-fatal. Ignore it. */; } - JSTR_RELEASE(jName, zName); + s3jni_mutf8_release(jName, zName); return (jint)rc; } @@ -4222,7 +4214,7 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, S3NphRef const *pRef, S3JniEnv * const jc = S3JniGlobal_env_cache(env); struct s3jni_xQueryPhraseState s; int rc = 0; - jbyte * const pText = jCallback ? JBA_TOC(jbaText) : 0; + jbyte * const pText = jCallback ? s3jni_jbytearray_bytes(jbaText) : 0; jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0; jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; @@ -4237,7 +4229,7 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, S3NphRef const *pRef, IFTHREW { EXCEPTION_REPORT; EXCEPTION_CLEAR; - JBA_RELEASE(jbaText, pText); + s3jni_jbytearray_release(jbaText, pText); return SQLITE_ERROR; } s.tok.jba = REF_L(jbaText); @@ -4259,7 +4251,7 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, S3NphRef const *pRef, assert( s.tok.zPrev ); UNREF_L(s.tok.jba); } - JBA_RELEASE(jbaText, pText); + s3jni_jbytearray_release(jbaText, pText); return (jint)rc; } @@ -4457,15 +4449,15 @@ Java_org_sqlite_jni_tester_SQLTester_strglob( JENV_CSELF, jbyteArray baG, jbyteArray baT ){ int rc = 0; - jbyte * const pG = JBA_TOC(baG); - jbyte * const pT = pG ? JBA_TOC(baT) : 0; + jbyte * const pG = s3jni_jbytearray_bytes(baG); + jbyte * const pT = pG ? s3jni_jbytearray_bytes(baT) : 0; - OOM_CHECK(pT); + s3jni_oom_check(pT); /* Note that we're relying on the byte arrays having been NUL-terminated on the Java side. */ rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT); - JBA_RELEASE(baG, pG); - JBA_RELEASE(baT, pT); + s3jni_jbytearray_release(baG, pG); + s3jni_jbytearray_release(baT, pT); return rc; } @@ -4610,15 +4602,15 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ #endif SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - OOM_CHECK( SJG.envCache.mutex ); + s3jni_oom_check( SJG.envCache.mutex ); SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - OOM_CHECK( SJG.perDb.mutex ); + s3jni_oom_check( SJG.perDb.mutex ); SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - OOM_CHECK( SJG.autoExt.mutex ); + s3jni_oom_check( SJG.autoExt.mutex ); #if S3JNI_METRICS_MUTEX SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - OOM_CHECK( SJG.metrics.mutex ); + s3jni_oom_check( SJG.metrics.mutex ); #endif sqlite3_shutdown() diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index bafc73bf68..cdbf860666 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -680,7 +680,8 @@ public class Tester1 implements Runnable { sqlite3_finalize(stmt); affirm( 0 != rc ); affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 ); - + rc = sqlite3_create_function(db, "mysca", 1, -1, funcSc); + affirm( SQLITE_FORMAT==rc, "invalid encoding value." ); sqlite3_close_v2(db); } diff --git a/manifest b/manifest index 2da52989e3..681eca27b3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\smore\sJNI\sdocs,\stests,\sand\sa\shandful\sof\sJava-side\soverloads. -D 2023-08-24T11:57:51.863 +C Correct\sJNI\slayer's\smisuse\sof\san\ssqlite3-internal\serror-reporting\sAPI\s(no\smutex\sheld).\sStyle\scleanups.\sEliminate\slookups\sof\sper-thread\sstate\sby\sapproximately\s85%\sacross\sthe\stest\ssuite. +D 2023-08-24T14:31:36.028 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 0a823c56f081294e7797dae303380ac989ebaa801bba970968342b7358f07aed F ext/jni/README.md 64bf1da0d562d051207ca1c5cfa52e8b7a69120533cc034a3da7670ef920cbef F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa -F ext/jni/src/c/sqlite3-jni.c e1e3cde4d08925282b5bc949f9ed8f613a6a2c6f60d0c697e79d59fb49f9fe4b +F ext/jni/src/c/sqlite3-jni.c d7d6d420f2a13d55828cee19ba17a37c4244532dafbc5822582d7fd52ae2aaf0 F ext/jni/src/c/sqlite3-jni.h cc24d6742b29a52338ffd3b47caf923facb8ae77f9c2fc9c2de82673bf339ea2 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java bcc1849b2fccbe5e2d7ac9e9ac7f8d05a6d7088a8fedbaad90e39569745a61e6 @@ -258,7 +258,7 @@ F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f F ext/jni/src/org/sqlite/jni/SQLFunction.java f697cf2a81c4119f2baf0682af689686f0466f1dd83dba00885f5603e693fe16 F ext/jni/src/org/sqlite/jni/SQLLog.java c60610b35208416940822e834d61f08fbbe5d6e06b374b541b49e41fd56c9798 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 956063c854c4f662183c41c65d0ab48b5e2127824b8053eeb05b9fc40f0d09e3 -F ext/jni/src/org/sqlite/jni/Tester1.java b5a4bb2a969df053d5c138887f04039a79b36170372a2efdf5dfbd6ac90db4c9 +F ext/jni/src/org/sqlite/jni/Tester1.java 9b6ec0ae299a56822e82e7dc2cf7ef1031ae87bcb595065bef84b7edac7114f5 F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2094,8 +2094,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 a9e6d5158b8a4a6b8554a5f8f0a35785ee450d42ea877275dc27085e89716c18 -R cb79df5ad40cc86377d98f5a1329b589 +P d19a431facbde6a6b960664674753ee85d2c051a76109ce7db0b079c65fbdea0 +R a3460d6a3bf6ffdc6985db3e6bc764fc U stephan -Z 39e8fbdcbc2ebaa58ed84b96f0374c06 +Z 4bd864e1476ea863a52783451fa010a4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 180d574846..bc1f81d857 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d19a431facbde6a6b960664674753ee85d2c051a76109ce7db0b079c65fbdea0 \ No newline at end of file +1f46ba8d3bc61af771c1e33d09ad25f0da4fc4f915f7a9f6223ebfd99526d81d \ No newline at end of file