Skip to content

Commit 95d4133

Browse files
committed
Add DualState.SingleDeleteGlobalRefP
The caching of a Lookup object at a PostgreSQL call site (in flinfo->fn_extra) will call for a flavor of DualState that can delete a JNI global reference when the call site's memory context is reset or deleted. This seems to be the first DualState flavor whose nativeStateReleased method actually does anything, which requires a little refinement of some only-when-assertions-enabled checking that had never had to deal with that before.
1 parent 7af5e94 commit 95d4133

File tree

2 files changed

+142
-33
lines changed

2 files changed

+142
-33
lines changed

pljava-so/src/main/c/DualState.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "org_postgresql_pljava_internal_DualState_SingleSPIfreetuptable.h"
2121
#include "org_postgresql_pljava_internal_DualState_SingleSPIcursorClose.h"
2222
#include "org_postgresql_pljava_internal_DualState_BBHeapFreeTuple.h"
23+
#include "org_postgresql_pljava_internal_DualState_SingleDeleteGlobalRefP.h"
2324
#include "pljava/DualState.h"
2425

2526
#include "pljava/Exception.h"
@@ -156,6 +157,16 @@ void pljava_DualState_initialize(void)
156157
{ 0, 0, 0 }
157158
};
158159

160+
JNINativeMethod deleteGlobalRefPMethods[] =
161+
{
162+
{
163+
"_deleteGlobalRefP",
164+
"(J)V",
165+
Java_org_postgresql_pljava_internal_DualState_00024SingleDeleteGlobalRefP__1deleteGlobalRefP
166+
},
167+
{ 0, 0, 0 }
168+
};
169+
159170
s_DualState_class = (jclass)JNI_newGlobalRef(PgObject_getJavaClass(
160171
"org/postgresql/pljava/internal/DualState"));
161172
s_DualState_cleanEnqueuedInstances = PgObject_getStaticJavaMethod(
@@ -206,6 +217,11 @@ void pljava_DualState_initialize(void)
206217
PgObject_registerNatives2(clazz, bbHeapFreeTupleMethods);
207218
JNI_deleteLocalRef(clazz);
208219

220+
clazz = (jclass)PgObject_getJavaClass(
221+
"org/postgresql/pljava/internal/DualState$SingleDeleteGlobalRefP");
222+
PgObject_registerNatives2(clazz, deleteGlobalRefPMethods);
223+
JNI_deleteLocalRef(clazz);
224+
209225
/*
210226
* Call initialize() methods of known classes built upon DualState.
211227
*/
@@ -418,3 +434,23 @@ Java_org_postgresql_pljava_internal_DualState_00024BBHeapFreeTuple__1heapFreeTup
418434
heap_freetuple(tup);
419435
END_NATIVE
420436
}
437+
438+
439+
440+
/*
441+
* Class: org_postgresql_pljava_internal_DualState_SingleDeleteGlobalRefP
442+
* Method: _deleteGlobalRefP
443+
* Signature: (J)V
444+
*/
445+
JNIEXPORT void JNICALL
446+
Java_org_postgresql_pljava_internal_DualState_00024SingleDeleteGlobalRefP__1deleteGlobalRefP(
447+
JNIEnv* env, jobject _this, jlong refp)
448+
{
449+
Ptr2Long p2l;
450+
jobject ref;
451+
p2l.longVal = refp;
452+
ref = *(jobject *)p2l.ptrVal;
453+
*(jobject *)p2l.ptrVal = NULL;
454+
/* no call into PostgreSQL here, just one simple JNI operation */
455+
(*env)->DeleteGlobalRef(env, ref);
456+
}

pljava/src/main/java/org/postgresql/pljava/internal/DualState.java

Lines changed: 106 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -528,27 +528,42 @@ private boolean hasPin(DualState<?> s)
528528
/** Thread local record of when the PG thread is invoking callbacks. */
529529
private static final CleanupTracker s_inCleanup = new CleanupTracker();
530530

531+
/** Flag for cleanup entry/exit from the Java side */
532+
private static final int CLEAN_JAVA = 1;
533+
534+
/** Flag for cleanup entry/exit from the native side */
535+
private static final int CLEAN_NATIVE = 2;
536+
531537
/** Thread local boolean with pairing enter/exit operations. */
532-
static final class CleanupTracker extends ThreadLocal<Boolean>
538+
static final class CleanupTracker extends ThreadLocal<Integer>
533539
{
534-
boolean enter()
540+
boolean enter(int how)
535541
{
536542
assert Backend.threadMayEnterPG() : m("inCleanup.enter thread");
537-
assert ! inCleanup() : m("inCleanup.enter re-entered");
538-
set(Boolean.TRUE);
543+
int got = get();
544+
assert (got&3) != how : m("inCleanup.enter re-entered same how");
545+
assert got < 4 : m("inCleanup.enter too many entries");
546+
set((got << 2) | how);
539547
return true;
540548
}
541549

542-
boolean exit()
550+
boolean exit(int how)
543551
{
544-
assert inCleanup() : m("inCleanup.exit mispaired");
545-
set(Boolean.FALSE);
552+
int got = get();
553+
assert (got&3) == how : m("inCleanup.exit mispaired");
554+
set(got >>> 2);
546555
return true;
547556
}
548557

549558
boolean inCleanup()
550559
{
551-
return Boolean.TRUE == get();
560+
return 0 != get();
561+
}
562+
563+
@Override
564+
protected Integer initialValue()
565+
{
566+
return 0;
552567
}
553568
}
554569

@@ -1757,35 +1772,44 @@ private static void lifespanRelease(Lifespan lifespan)
17571772

17581773
DualState t = head.m_next;
17591774
head.m_prev = head.m_next = head;
1760-
for ( DualState s = t ; s != head ; s = t )
1775+
1776+
assert s_inCleanup.enter(CLEAN_NATIVE); //no-op when assertions disabled
1777+
try
17611778
{
1762-
t = s.m_next;
1763-
s.m_prev = s.m_next = null;
1764-
++ total;
1765-
/*
1766-
* This lock() is part of DualState's contract with clients.
1767-
* They are responsible for pinning the state instance
1768-
* whenever they need the wrapped native state (which is verified
1769-
* to still be valid at that time) and for the duration of whatever
1770-
* operation needs access to that state. Taking this lock here
1771-
* ensures the native state is blocked from vanishing while it is
1772-
* actively in use.
1773-
*/
1774-
int state = s.lock(false);
1775-
try
1779+
for ( DualState s = t ; s != head ; s = t )
17761780
{
1777-
if ( z(NATIVE_RELEASED & state) )
1781+
t = s.m_next;
1782+
s.m_prev = s.m_next = null;
1783+
++ total;
1784+
/*
1785+
* This lock() is part of DualState's contract with clients.
1786+
* They are responsible for pinning the state instance whenever
1787+
* they need the wrapped native state (which is verified to
1788+
* still be valid at that time) and for the duration of whatever
1789+
* operation needs access to that state. Taking this lock here
1790+
* ensures the native state is blocked from vanishing while it
1791+
* is actively in use.
1792+
*/
1793+
int state = s.lock(false);
1794+
try
17781795
{
1779-
++ release;
1780-
s.nativeStateReleased(
1781-
z(JAVA_RELEASED & state) && null != s.referent());
1796+
if ( z(NATIVE_RELEASED & state) )
1797+
{
1798+
++ release;
1799+
s.nativeStateReleased(
1800+
z(JAVA_RELEASED & state) && null != s.referent());
1801+
}
1802+
}
1803+
finally
1804+
{
1805+
s.unlock(state, true);//true->ensure NATIVE_RELEASED is set.
17821806
}
1783-
}
1784-
finally
1785-
{
1786-
s.unlock(state, true); // true -> ensure NATIVE_RELEASED is set.
17871807
}
17881808
}
1809+
finally
1810+
{
1811+
assert s_inCleanup.exit(CLEAN_NATIVE);
1812+
}
17891813

17901814
s_stats.lifespanPoll(release, total);
17911815
}
@@ -1807,7 +1831,7 @@ private static void cleanEnqueuedInstances()
18071831
int nDeferred = s_deferredReleased.size();
18081832
boolean isDeferred;
18091833

1810-
assert s_inCleanup.enter(); // no-op when assertions disabled
1834+
assert s_inCleanup.enter(CLEAN_JAVA); // no-op when assertions disabled
18111835
try
18121836
{
18131837
for ( ;; )
@@ -1848,7 +1872,7 @@ else if ( z(NATIVE_RELEASED & state) )
18481872
}
18491873
finally
18501874
{
1851-
assert s_inCleanup.exit();
1875+
assert s_inCleanup.exit(CLEAN_JAVA);
18521876
}
18531877

18541878
s_stats.referenceQueueDrain(total - release, release, total, reDefer);
@@ -2363,6 +2387,55 @@ protected void javaStateUnreachable(boolean nativeStateLive)
23632387
private native void _heapFreeTuple(ByteBuffer tuple);
23642388
}
23652389

2390+
/**
2391+
* A {@code DualState} subclass whose only native resource releasing action
2392+
* needed is a JNI {@code DeleteGlobalRef} of a single pointer.
2393+
*/
2394+
public static abstract class SingleDeleteGlobalRefP<T>
2395+
extends SingleGuardedLong<T>
2396+
{
2397+
protected SingleDeleteGlobalRefP(
2398+
T referent, Lifespan span, long dgrTarget)
2399+
{
2400+
super(referent, span, dgrTarget);
2401+
}
2402+
2403+
@Override
2404+
public String formatString()
2405+
{
2406+
return "%s DeleteGlobalRef(%x)";
2407+
}
2408+
2409+
/**
2410+
* When the Java state is released (it won't normally go unreachable,
2411+
* because of the global ref), a JNI {@code DeleteGlobalRef} call
2412+
* is made so the instance can be collected without having to wait
2413+
* for release of its containing context.
2414+
*/
2415+
@Override
2416+
protected void javaStateReleased(boolean nativeStateLive)
2417+
{
2418+
assert Backend.threadMayEnterPG();
2419+
if ( nativeStateLive )
2420+
_deleteGlobalRefP(guardedLong());
2421+
}
2422+
2423+
/**
2424+
* When the native state is released, a JNI {@code DeleteGlobalRef} call
2425+
* is made so the global ref stored in that to-be-released memory isn't
2426+
* leaked (left permanently live).
2427+
*/
2428+
@Override
2429+
protected void nativeStateReleased(boolean javaStateLive)
2430+
{
2431+
assert Backend.threadMayEnterPG();
2432+
if ( javaStateLive )
2433+
_deleteGlobalRefP(guardedLong());
2434+
}
2435+
2436+
private native void _deleteGlobalRefP(long pointer);
2437+
}
2438+
23662439
/**
23672440
* Bean exposing some {@code DualState} allocation and lifecycle statistics
23682441
* for viewing in a JMX management client.

0 commit comments

Comments
 (0)