Remove a superfluous level of indirection in the JNI internals.

FossilOrigin-Name: 8dca6f7660c15eacbda20da1c66c9ef1de36864f78750658226b1a7baf22b726
This commit is contained in:
stephan 2023-09-02 10:18:10 +00:00
parent 63f4a9c4db
commit 70b58fc119
4 changed files with 104 additions and 115 deletions

View File

@ -294,43 +294,66 @@ static inline void s3jni_unref_local(JNIEnv * const env, jobject const v){
#define S3JniUnrefLocal(VAR) s3jni_unref_local(env, (VAR))
/*
** Lookup key type for use with s3jni_nph().
** Lookup key type for use with s3jni_nphop() and a cache of a
** frequently-needed Java-side class reference and one or two Java
** class member IDs.
*/
typedef struct S3JniNphRef S3JniNphRef;
struct S3JniNphRef {
typedef struct S3JniNphOp S3JniNphOp;
struct S3JniNphOp {
const int index /* index into S3JniGlobal.nph[] */;
const char * const zName /* Full Java name of the class */;
const char * const zMember /* Name of member property */;
const char * const zTypeSig /* JNI type signature of zMember */;
/*
** klazz is a global ref to the class represented by pRef.
**
** According to:
**
** https://developer.ibm.com/articles/j-jni/
**
** > ... the IDs returned for a given class don't change for the
** lifetime of the JVM process. But the call to get the field or
** method can require significant work in the JVM, because fields
** and methods might have been inherited from superclasses, making
** the JVM walk up the class hierarchy to find them. Because the
** IDs are the same for a given class, 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.
*/
jclass klazz;
volatile jfieldID fidValue /* NativePointerHolder.nativePointer or
** OutputPointer.T.value */;
volatile jmethodID midCtor /* klazz's no-arg constructor. Used by
** NativePointerHolder_new(). */;
};
/*
** Cache keys for each concrete NativePointerHolder subclasses and
** OutputPointer.T types. The members are to be used with s3jni_nph()
** OutputPointer.T types. The members are to be used with s3jni_nphop()
** and friends, and each one's member->index corresponds to its index
** in the S3JniGlobal.nph[] array.
*/
static const struct {
const S3JniNphRef sqlite3;
const S3JniNphRef sqlite3_stmt;
const S3JniNphRef sqlite3_context;
const S3JniNphRef sqlite3_value;
const S3JniNphRef OutputPointer_Bool;
const S3JniNphRef OutputPointer_Int32;
const S3JniNphRef OutputPointer_Int64;
const S3JniNphRef OutputPointer_sqlite3;
const S3JniNphRef OutputPointer_sqlite3_stmt;
const S3JniNphRef OutputPointer_sqlite3_value;
const S3JniNphRef OutputPointer_String;
const S3JniNphOp sqlite3;
const S3JniNphOp sqlite3_stmt;
const S3JniNphOp sqlite3_context;
const S3JniNphOp sqlite3_value;
const S3JniNphOp OutputPointer_Bool;
const S3JniNphOp OutputPointer_Int32;
const S3JniNphOp OutputPointer_Int64;
const S3JniNphOp OutputPointer_sqlite3;
const S3JniNphOp OutputPointer_sqlite3_stmt;
const S3JniNphOp OutputPointer_sqlite3_value;
const S3JniNphOp OutputPointer_String;
#ifdef SQLITE_ENABLE_FTS5
const S3JniNphRef OutputPointer_ByteArray;
const S3JniNphRef Fts5Context;
const S3JniNphRef Fts5ExtensionApi;
const S3JniNphRef fts5_api;
const S3JniNphRef fts5_tokenizer;
const S3JniNphRef Fts5Tokenizer;
const S3JniNphOp OutputPointer_ByteArray;
const S3JniNphOp Fts5Context;
const S3JniNphOp Fts5ExtensionApi;
const S3JniNphOp fts5_api;
const S3JniNphOp fts5_tokenizer;
const S3JniNphOp Fts5Tokenizer;
#endif
} S3JniNphRefs = {
} S3JniNphOps = {
#define MkRef(INDEX, KLAZZ, MEMBER, SIG) \
{ INDEX, "org/sqlite/jni/" KLAZZ, MEMBER, SIG }
/* NativePointerHolder ref */
@ -364,51 +387,17 @@ static const struct {
#undef RefO
};
#define S3JniNph(T) &S3JniNphRefs.T
#define S3JniNph(T) &S3JniNphOps.T
enum {
/*
** Size of the NativePointerHolder cache. Need enough space for
** (only) the library's NativePointerHolder and OutputPointer types,
** a fixed count known at build-time. This value needs to be
** exactly the number of S3JniNphRef entries in the S3JniNphRefs
** exactly the number of S3JniNphOp entries in the S3JniNphOps
** object.
*/
S3Jni_NphCache_size = sizeof(S3JniNphRefs) / sizeof(S3JniNphRef)
};
/*
** Cache entry for NativePointerHolder subclasses and OutputPointer
** types. The pRef and klazz fields are set up the first time the
** entry is fetched using s3jni_nph(). The other fields are
** populated as needed by the routines which use them.
*/
typedef struct S3JniNphClass S3JniNphClass;
struct S3JniNphClass {
volatile const S3JniNphRef * pRef /* Entry from S3JniNphRefs */;
/*
** klazz is a global ref to the class represented by pRef.
**
** According to:
**
** https://developer.ibm.com/articles/j-jni/
**
** > ... the IDs returned for a given class don't change for the
** lifetime of the JVM process. But the call to get the field or
** method can require significant work in the JVM, because fields
** and methods might have been inherited from superclasses, making
** the JVM walk up the class hierarchy to find them. Because the
** IDs are the same for a given class, 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.
*/
jclass klazz;
volatile jmethodID midCtor /* klazz's no-arg constructor. Used by
** NativePointerHolder_new(). */;
volatile jfieldID fidValue /* NativePointerHolder.nativePointer or
** OutputPointer.T.value */;
volatile jfieldID fidAggCtx /* sqlite3_context.aggregateContext, used only
** by the sqlite3_context binding. */;
S3Jni_NphCache_size = sizeof(S3JniNphOps) / sizeof(S3JniNphOp)
};
/*
@ -420,7 +409,9 @@ struct S3JniHook{
jmethodID midCallback /* callback method. Signature depends on
** jObj's type */;
/* We lookup the jObj.xDestroy() method as-needed for contexts which
** have custom finalizers. */
** support custom finalizers. Fundamentally we can support them for
** any Java type, but we only want to expose support for them where
** the C API does. */
jobject jExtra /* Global ref to a per-hook-type value */;
int doXDestroy /* If true then S3JniHook_unref() will call
jObj->xDestroy() if it's available. */;
@ -590,10 +581,10 @@ struct S3JniGlobalType {
** NativePointerHolder subclasses and OutputPointer.T types.
*/
struct {
S3JniNphClass list[S3Jni_NphCache_size];
S3JniNphOp list[S3Jni_NphCache_size];
sqlite3_mutex * mutex; /* mutex for this->list */
void const * locker; /* sanity-checking-only context object
for this->mutex */
volatile void const * locker; /* sanity-checking-only context object
for this->mutex */
} nph;
/*
** Cache of per-thread state.
@ -602,8 +593,9 @@ struct S3JniGlobalType {
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. */;
volatile void const * locker /* env mutex is held on this
object's behalf. Used only for
sanity checking. */;
} envCache;
/*
** Per-db state. This can move into the core library once we can tie
@ -612,8 +604,10 @@ struct S3JniGlobalType {
struct {
S3JniDb * aFree /* Linked list of free instances */;
sqlite3_mutex * mutex /* mutex for aHead and aFree */;
void const * locker /* perDb mutex is held on this object's
behalf. Used only for sanity checking. */;
volatile void const * locker
/* perDb mutex is held on this object's behalf. Used only for
sanity checking. Note that the mutex is at the class level, not
instance level. */;
} perDb;
struct {
S3JniUdf * aFree /* Head of the free-item list. Guarded by global
@ -646,8 +640,9 @@ struct S3JniGlobalType {
int nExt /* number of active entries in aExt, all in the
first nExt'th array elements. */;
sqlite3_mutex * mutex /* mutex for manipulation/traversal of aExt */;
const void * locker /* object on whose behalf the mutex is held.
Only for sanity checking in debug builds. */;
volatile const void * locker /* object on whose behalf the mutex
is held. Only for sanity checking
in debug builds. */;
} autoExt;
#ifdef SQLITE_ENABLE_FTS5
struct {
@ -1316,23 +1311,20 @@ static int S3JniEnv_uncache(JNIEnv * const env){
** This simple cache catches >99% of searches in the current
** (2023-07-31) tests.
*/
static S3JniNphClass * s3jni__nph(JNIEnv * const env, S3JniNphRef const* pRef){
S3JniNphClass * const pNC = &SJG.nph.list[pRef->index];
static S3JniNphOp * s3jni__nphop(JNIEnv * const env, S3JniNphOp const* pRef){
S3JniNphOp * const pNC = &SJG.nph.list[pRef->index];
assert( (void*)pRef>=(void*)&S3JniNphRefs && (void*)pRef<(void*)(&S3JniNphRefs + 1)
assert( (void*)pRef>=(void*)&S3JniNphOps && (void*)pRef<(void*)(&S3JniNphOps + 1)
&& "pRef is out of range" );
assert( pRef->index>=0
&& (pRef->index < (sizeof(S3JniNphRefs) / sizeof(S3JniNphRef)))
&& (pRef->index < (sizeof(S3JniNphOps) / sizeof(S3JniNphOp)))
&& "pRef->index is out of range" );
if( !pNC->pRef ){
if( !pNC->klazz ){
S3JniNph_mutex_enter;
if( !pNC->pRef ){
if( !pNC->klazz ){
jclass const klazz = (*env)->FindClass(env, pRef->zName);
S3JniExceptionIsFatal("FindClass() unexpectedly threw");
pNC->klazz = S3JniRefGlobal(klazz);
pNC->pRef = pRef
/* Must come last to avoid a race condition where pNC->klass
can be NULL after this function returns. */;
}
S3JniNph_mutex_leave;
}
@ -1340,11 +1332,11 @@ static S3JniNphClass * s3jni__nph(JNIEnv * const env, S3JniNphRef const* pRef){
return pNC;
}
#define s3jni_nph(PRef) s3jni__nph(env, PRef)
#define s3jni_nphop(PRef) s3jni__nphop(env, PRef)
/*
** Common code for accessor functions for NativePointerHolder and
** OutputPointer types. pRef must be a pointer from S3JniNphRefs. jOut
** OutputPointer types. pRef must be a pointer from S3JniNphOps. jOut
** must be an instance of that class (Java's type safety takes care of
** that requirement). If necessary, this fetches the jfieldID for
** jOut's pRef->zMember, which must be of the type represented by the
@ -1354,8 +1346,8 @@ static S3JniNphClass * s3jni__nph(JNIEnv * const env, S3JniNphRef const* pRef){
**
** Property lookups are cached on a per-pRef basis.
*/
static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphRef const* pRef){
S3JniNphClass * const pNC = s3jni_nph(pRef);
static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphOp const* pRef){
S3JniNphOp * const pNC = s3jni_nphop(pRef);
if( !pNC->fidValue ){
S3JniNph_mutex_enter;
@ -1363,7 +1355,7 @@ static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphRef const* pRef){
pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz,
pRef->zMember, pRef->zTypeSig);
S3JniExceptionIsFatal("Code maintenance required: missing "
"required S3JniNphClass::fidValue.");
"required S3JniNphOp::fidValue.");
}
S3JniNph_mutex_leave;
}
@ -1376,7 +1368,7 @@ static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphRef const* pRef){
** zClassName must be a static string so we can use its address
** as a cache key.
*/
static void NativePointerHolder__set(JNIEnv * const env, S3JniNphRef const* pRef,
static void NativePointerHolder__set(JNIEnv * const env, S3JniNphOp const* pRef,
jobject ppOut, const void * p){
assert( ppOut );
(*env)->SetLongField(env, ppOut, s3jni_nphop_field(env, pRef), (jlong)p);
@ -1392,7 +1384,7 @@ static void NativePointerHolder__set(JNIEnv * const env, S3JniNphRef const* pRef
** no-op if pObj is NULL.
*/
static void * NativePointerHolder__get(JNIEnv * env, jobject pObj,
S3JniNphRef const* pRef){
S3JniNphOp const* pRef){
void * rv = 0;
if( pObj ){
rv = (void*)(*env)->GetLongField(env, pObj, s3jni_nphop_field(env, pRef));
@ -1570,7 +1562,7 @@ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut,
** Object type.
*/
static void OutputPointer_set_obj(JNIEnv * const env,
S3JniNphRef const * const pRef,
S3JniNphOp const * const pRef,
jobject const jOut,
jobject v){
(*env)->SetObjectField(env, jOut, s3jni_nphop_field(env, pRef), v);
@ -1666,14 +1658,14 @@ static void ResultJavaValue_finalizer(void *v){
** if Java fails to allocate, but the JNI docs are not entirely clear
** on that detail.
**
** Always use a static pointer from the S3JniNphRefs struct for the
** Always use a static pointer from the S3JniNphOps struct for the
** 2nd argument.
*/
static jobject NativePointerHolder_new(JNIEnv * const env,
S3JniNphRef const * pRef,
S3JniNphOp const * pRef,
const void * pNative){
jobject rv = 0;
S3JniNphClass * const pNC = s3jni_nph(pRef);
S3JniNphOp * const pNC = s3jni_nphop(pRef);
if( !pNC->midCtor ){
S3JniNph_mutex_enter;
if( !pNC->midCtor ){
@ -1826,7 +1818,7 @@ static int udf_args(JNIEnv *env,
*jArgv = 0;
if( !jcx ) goto error_oom;
ja = (*env)->NewObjectArray(
env, argc, s3jni_nph(S3JniNph(sqlite3_value))->klazz,
env, argc, s3jni_nphop(S3JniNph(sqlite3_value))->klazz,
NULL);
s3jni_oom_check( ja );
if( !ja ) goto error_oom;
@ -2113,12 +2105,12 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
if( 0==SJG.autoExt.nExt ) return 0;
env = s3jni_env();
jc = S3JniEnv_get();
S3JniAutoExt_mutex_enter;
S3JniDb_mutex_enter;
ps = jc->pdbOpening ? jc->pdbOpening : S3JniDb_from_c(pDb);
if( !ps ){
*pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in "
"auto-extension runner.");
S3JniAutoExt_mutex_leave;
S3JniDb_mutex_leave;
return SQLITE_ERROR;
}
assert( ps->jDb );
@ -2128,7 +2120,7 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
ps, 0/* we'll re-set this after open()
completes. */);
if( rc ){
S3JniAutoExt_mutex_leave;
S3JniDb_mutex_leave;
return rc;
}
}
@ -2136,7 +2128,7 @@ static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
assert( ps == jc->pdbOpening );
jc->pdbOpening = 0;
}
S3JniAutoExt_mutex_leave;
S3JniDb_mutex_leave;
NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, pDb)
/* As of here, the Java/C connection is complete except for the
(temporary) lack of finalizer for the ps object. */;
@ -4442,10 +4434,12 @@ JniDecl(void,1jni_1internal_1details)(JniArgsEnvClass){
SO(S3JniEnv);
SO(S3JniHook);
SO(S3JniDb);
SO(S3JniNphRefs);
SO(S3JniNphOps);
printf("\t(^^^ %u NativePointerHolder/OutputPointer.T types)\n",
(unsigned)S3Jni_NphCache_size);
SO(S3JniGlobal);
SO(S3JniGlobal.nph);
SO(S3JniGlobal.metrics);
SO(S3JniAutoExtension);
SO(S3JniUdf);
#undef SO
@ -4457,7 +4451,7 @@ JniDecl(void,1jni_1internal_1details)(JniArgsEnvClass){
printf("Mutex entry:"
"\n\tglobal = %u"
"\n\tenv = %u"
"\n\tnph = %u for S3JniNphClass init"
"\n\tnph = %u for S3JniNphOp init"
"\n\tperDb = %u"
"\n\tautoExt list = %u"
"\n\tS3JniUdf = %u (free-list)"
@ -5034,7 +5028,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(JniArgsEnvObj, S3JniNphRef const *pRef,
static jint s3jni_fts5_xTokenize(JniArgsEnvObj, S3JniNphOp const *pRef,
jint tokFlags, jobject jFcx,
jbyteArray jbaText, jobject jCallback){
Fts5ExtDecl;

View File

@ -122,7 +122,7 @@ public class Tester1 implements Runnable {
affirm(v, "Affirmation failed.");
}
@ManualTest /* because testing this for threading is pointless */
@SingleThreadOnly /* because it's thread-agnostic */
private void test1(){
affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
}
@ -610,7 +610,7 @@ public class Tester1 implements Runnable {
affirm( 1 == xDestroyCalled.value );
}
@ManualTest /* because threading is meaningless here */
@SingleThreadOnly /* because it's thread-agnostic */
private void testToUtf8(){
/**
https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
@ -978,8 +978,8 @@ public class Tester1 implements Runnable {
affirm( 7 == counter.value );
}
@ManualTest /* because threads inherently break this test */
private void testBusy(){
@SingleThreadOnly /* because threads inherently break this test */
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();
@ -1482,7 +1482,7 @@ public class Tester1 implements Runnable {
}
@ManualTest /* we really only want to run this test manually. */
@ManualTest /* we really only want to run this test manually */
private void testSleep(){
out("Sleeping briefly... ");
sqlite3_sleep(600);
@ -1525,8 +1525,6 @@ public class Tester1 implements Runnable {
outln();
}
}
testToUtf8();
test1();
for(java.lang.reflect.Method m : mlist){
nap();
try{
@ -1536,9 +1534,6 @@ public class Tester1 implements Runnable {
throw e;
}
}
if( !fromThread ){
testBusy();
}
synchronized( this.getClass() ){
++nTestRuns;
}

View File

@ -1,5 +1,5 @@
C Further\sJNI\scleanups.
D 2023-09-02T08:51:14.022
C Remove\sa\ssuperfluous\slevel\sof\sindirection\sin\sthe\sJNI\sinternals.
D 2023-09-02T10:18:10.477
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -237,7 +237,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3
F ext/jni/GNUmakefile dc6e78f9717470d262b4b3ec17c337834295f9df81717c1539da84106324fd1e
F ext/jni/README.md 1332b1fa27918bd5d9ca2d0d4f3ac3a6ab86b9e3699dc5bfe32904a027f3d2a9
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
F ext/jni/src/c/sqlite3-jni.c d98030bb7f502b42990eeb0f2bd103af0b1f1312ba82892949c5ced85262e036
F ext/jni/src/c/sqlite3-jni.c 1ce08538c568ec47ccf9e88e8b6d0e5b8a874520238d809adbfc93feb9f54d27
F ext/jni/src/c/sqlite3-jni.h c22f0189254abe26fad3ba132b484785b19a1aa96d34d30d7d8c5ffe6a9b25d1
F ext/jni/src/org/sqlite/jni/AbstractCollationCallback.java 95e88ba04f4aac51ffec65693e878e234088b2f21b387f4e4285c8b72b33e436
F ext/jni/src/org/sqlite/jni/AggregateFunction.java 7312486bc65fecdb91753c0a4515799194e031f45edbe16a6373cea18f404dc4
@ -264,7 +264,7 @@ F ext/jni/src/org/sqlite/jni/SQLFunction.java 544a875d33fd160467d82e2397ac33157b
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 98f8d62492e2d6693336dd42c12267ea7f21eefe219aa85b8dd399bd6b0732bd
F ext/jni/src/org/sqlite/jni/ScalarFunction.java 6d387bb499fbe3bc13c53315335233dbf6a0c711e8fa7c521683219b041c614c
F ext/jni/src/org/sqlite/jni/TableColumnMetadata.java 54511b4297fa28dcb3f49b24035e34ced10e3fd44fd0e458e784f4d6b0096dab
F ext/jni/src/org/sqlite/jni/Tester1.java 93e89c4f72065bf28625cf435c47ed2bd3d937fe5c2431e6e7c3407f09f3f9bc
F ext/jni/src/org/sqlite/jni/Tester1.java e62b0e855ef19a703dc53c1eb8395ff49f5dc4c9471627f5ba71b9b831a18b37
F ext/jni/src/org/sqlite/jni/TesterFts5.java 2b2d6f3cc9f508358c103b774aee296c0f3d8c2f387d6abae9b8b9055f62c800
F ext/jni/src/org/sqlite/jni/TraceV2Callback.java beb0b064c1a5f8bfe585a324ed39a4e33edbe379a3fc60f1401661620d3ca7c0
F ext/jni/src/org/sqlite/jni/UpdateHookCallback.java 8376f4a931f2d5612b295c003c9515ba933ee76d8f95610e89c339727376e36c
@ -2115,8 +2115,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 f9c1f9cad1ad22f689a4afa13d59bf9969ddaed6624cbc16cb1bf5d1fd0c8a5b
R 215167a378a1f1105562740ffd0f1842
P 30e38173c3ece0c9f8e7a9710f46cb5e8e8ef101c04531318a7adb070242f5dd
R 5b531d67a390c107642befa040c21413
U stephan
Z 15dc34b47fe3ed21ed7cc58408f87312
Z 11b0834dd4cedde9630a4a43d0bbc08f
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
30e38173c3ece0c9f8e7a9710f46cb5e8e8ef101c04531318a7adb070242f5dd
8dca6f7660c15eacbda20da1c66c9ef1de36864f78750658226b1a7baf22b726