- Direct Known Subclasses:
DualState.ListHead
,DualState.SingleGuardedBB
,DualState.SingleGuardedLong
,DualState.SingleMemContextDelete
A DualState
object connects some state that exists in the JVM
as well as some native/PostgreSQL resources. It will 'belong' to some Java
object that holds a strong reference to it, and this state object is,
in turn, a WeakReference
to that object. Java state may be held in
that object (if it needs only to be freed by the garbage collector when
unreachable), or in this object if it needs some more specific cleanup.
Native state will be referred to by this object.
These interesting events are possible in the life cycle of a
DualState
object:
- It is explicitly closed by the Java code using it. It, and any associated native state, should be released.
- It is found unreachable by the Java garbage collector. Again, any associated state should be released.
- Its associated native state is released or invalidated (such as by exit of a corresponding context). If the object is still reachable from Java, it must throw an exception for any future attempted access to its native state.
A subclass overrides the javaStateReleased
,
javaStateUnreachable
, or
nativeStateReleased
methods, respectively,
to add behavior for those life cycle events.
A subclass calls releaseFromJava
to signal an event
of the first kind. Events of the second kind are, naturally, detected by the
Java garbage collector. To detect events of the third kind, a lifespan
must be associated with the instance.
A parameter to the DualState
constructor is a Lifespan
. A
nativeStateReleased
event occurs when the corresponding
Lifespan
is released in PostgreSQL. PostgreSQL ResourceOwner
and MemoryContext
are two types of object that can serve
as lifespans. A PL/Java Invocation
object may also be used, to mark
the lifespans of function arguments and other data expected to live at least
for the duration of a function call. The lifespan argument can be null,
for an object allocated in an immortal context and managed only by its
javaStateReleased
or javaStateUnreachable
methods.
Java code may execute in multiple threads, but PostgreSQL is not
multi-threaded; at any given time, there is no more than one thread that may
safely make JNI calls into PostgreSQL routines (for that thread,
Backend.threadMayEnterPG()
returns true). Depending on the setting of
the pljava.java_thread_pg_entry
PostgreSQL configuration variable,
that may be the same one thread for the duration of a session, or it may be
possible for one thread to relinquish that status and another thread to take
it: for the pljava.java_thread_pg_entry
setting allow
, the
status is represented by holding the object monitor on
Backend.THREADLOCK
, and Backend.threadMayEnterPG()
returns
true for whatever thread holds it. Under that setting, there can be moments
when Backend.threadMayEnterPG()
is not true for any thread, if one
has released the monitor and no other thread has yet acquired it. For brevity
in what follows, "the PG thread" will be used to mean whatever thread, at a
given moment, would observe Backend.threadMayEnterPG()
to return
true.
Some methods of DualState
and subclasses may be called from any Java
thread, while some must be called from the PG thread. The life-cycle
callbacks, javaStateReleased
, javaStateUnreachable
, and
nativeStateReleased
, are called by the implementation, and always
on the PG thread.
The Java Memory Model imposes strict conditions for updates to memory state
made in one thread to be visible to other threads. Methods that are known to
be called only on the PG thread can sidestep those complexities, at least
to the extent that they manipulate only data structures not accessed in other
threads. This is true even under the pljava.java_thread_pg_entry
setting allow
, where "the PG thread" may not always be the same
thread. Because a Java synchronization event is involved whenever
"the PG thread" changes, unbroken visibility is assured, just as it would be
in one unchanging thread, so one can say "the PG thread" for convenience and
without loss of generality.
For the nativeStateReleased
lifecycle event, rules for memory
visibility are not enough; a mechanism for mutual exclusion is needed. The
callback is made on the PG thread from PostgreSQL code that is in the process
of invalidating the native state, and will do so once the callback returns.
If any other Java thread is actively referring to that native state, there is
no choice but to block the PG thread making the callback until such other
threads are no longer relying on the native state.
To that end, the pin
and unpin
methods are
provided, and must be used to surround any block of code that accesses the
native state:
pin(); try { ... code that dereferences or relies on a valid native state ... } finally { unpin(); }
Pins are lightweight, nonexclusive (any number of threads may simultaneously
pin the same DualState
instance), and reentrant (a single thread may
obtain and release nested pins on the same instance). The code protected by a
pin is ideally a short sequence representing a simple operation (reading a
value, or refilling a small buffer with data) on the native state. The chief
purpose of holding a pin is to hold off the possible invalidation of the
native state until the pin is released.
If either the native state or the Java state has been released already (by
the lifespan callback or an explicit call to releaseFromJava
,
respectively), pin()
will detect that and throw the appropriate
exception. Otherwise, the state is safe to make use of until unpin
.
A subclass can customize the messages or SQLSTATE
codes for the
exceptions pin()
may throw, by overriding one or more of
identifierForMessage
,
invalidMessage
,
releasedMessage
,
invalidSqlState
, or
releasedSqlState
.
Code that holds a pin may safely act on components of the native state from
any thread, so long as the actions do not include native calls to PostgreSQL
routines (directly or transitively). Access to the native memory through a
direct byte buffer would be a permitted example, or even calls to JNI methods
to retrieve fields from C struct
s or chase pointers through a data
structure, as long as only thread-safe routines from the C runtime are called
and no routines of PostgreSQL itself, and as long as the memory or structure
being accessed is known to be safe from modification by PostgreSQL while the
pin is held. In the future, PL/Java may one day have an annotation that can
be used to mark native methods that satisfy these limits; at present, there
has been no effort to segregate them into those that do and those that don't.
Native methods that may (under any circumstances!) invoke PG routines must
be invoked on the PG thread.
The exclusive counterparts to pin
and unpin
are
lock
and unlock
, which are not
expected to be used as widely. The chief use of lock
/unlock
is around the call to nativeStateReleased
when handling a lifespan
callback from PostgreSQL. They can be used in subclasses to surround
modifications to the state, as needed. A lock
will block until all
earlier-acquired pins are released; subsequent pins block until the lock is
released. Only the PG thread may use lock
/unlock
. An
upgrade
argument to lock
allows the lock to be acquired
when the PG thread already holds a pin; it should be specified
only when inspection of the code identifies a nearby enclosing pin and
confirms that the planned locked actions will not break the pinning code's
assumptions. Pins can be freely acquired by the PG thread while it holds a
lock; the coding convention's strict nesting assures they will all be
released before the lock is.
In an explicit call to releaseFromJava
, which may be made from any
thread, the instance is immediately, atomically, flagged as released. No
subsequent pin will succeed. Pins already held are unaffected, so there must
be no changes made to the state, at the time releaseFromJava
is
called, that could confuse any code that already holds a pin and is relying
on the state. Such changes must be made in the javaStateReleased
callback, which will execute only after release of the last pin, if any, and
always on the PG thread. If the last pin is released by a thread other than
the PG thread, the callback does not execute immediately, but via a queue
that is polled from the PG thread at convenient points.
Instances whose referents are found unreachable by Java's garbage collector
are placed on the same queue, so their javaStateUnreachable
callbacks
will be executed on the PG thread when the queue is polled. The callbacks
should clean up any lingering native state.
As the callbacks are executed on the PG thread, any native calls they may need to make into PostgreSQL are allowed without extra ceremony.
There are different abstract subclasses of DualState
that wrap
different sorts of PostgreSQL native state, and encapsulate what needs to be
done when such state is released from the Java or native side. More such
subclasses can be added as needed.
A client class of DualState
will typically contain a static nested
class that further extends one of these abstract subclasses, and the client
instance will hold a strong reference to an instance of that
DualState
subclass constructed at the same time.
This class uses some private data structures, to track created instances through their life cycles, that are not synchronized or thread-safe. The design rests on the following requirements:
- The structures are only traversed or modified during:
- Instance construction
- Reference queue processing (instances found unreachable by Java's
garbage collector, or enqueued following
releaseFromJava
) - Exit of a lifespan's scope
- There is only one PG thread, or only one at a time.
- Construction of any
DualState
instance is to take place only on the PG thread. - Reference queue processing takes place only at chosen points where a thread enters or exits native code, on the PG thread.
- Lifespan callbacks originate in native code, on the PG thread.
-
Nested Class Summary
Modifier and TypeClassDescriptionstatic class
ADualState
subclass whose only native resource releasing action needed isheap_freetuple
of the address of a direct byte buffer.static class
An otherwise nonfunctional DualState subclass whose instances only serve as list headers in per-lifespan lists of instances.static interface
A subinterface ofAutoCloseable
whoseclose
method throws no checked exceptions.static class
ADualState
subclass whose only native resource releasing action needed is a JNIDeleteGlobalRef
of a single pointer.static class
ADualState
subclass whose only native resource releasing action needed isFreeErrorData
of a single pointer.static class
ADualState
subclass whose only native resource releasing action needed isFreeTupleDesc
of a single pointer.static class
ADualState
subclass serving only to guard access to a single nonnullByteBuffer
value.static class
ADualState
subclass serving only to guard access to a single nonzerolong
value (typically a native pointer).static class
ADualState
subclass whose only native resource releasing action needed isheap_freetuple
of a single pointer.static class
ADualState
subclass whose only native resource releasing action needed isMemoryContextDelete
of a single context.static class
ADualState
subclass whose only native resource releasing action needed ispfree
of a single pointer.static class
ADualState
subclass whose only native resource releasing action needed isSPI_cursor_close
of a single pointer.static class
ADualState
subclass whose only native resource releasing action needed isSPI_freeplan
of a single pointer.static class
ADualState
subclass whose only native resource releasing action needed isSPI_freetuptable
of a single pointer. -
Constructor Summary
-
Method Summary
Modifier and TypeMethodDescriptionprotected final void
Specialized version oflock
for use by code implementing anadopt
operation (in which complete control of an object is handed back to PostgreSQL and it is dissociated from Java).protected final void
Specialized version ofunlock
for use by code implementing anadopt
operation (in which complete control of an object is handed back to PostgreSQL and it is dissociated from Java).final void
clear()
ThrowsUnsupportedOperationException
;releaseFromJava
must be used rather than calling this method directly.final boolean
enqueue()
ThrowsUnsupportedOperationException
;releaseFromJava
must be used rather than calling this method directly.final T
get()
ThrowsUnsupportedOperationException
; client code should already hold a reference.protected String
Return a string identifying this object in a way useful within an exception message for use of this state after native release or Java release.protected String
Return a string for an exception message reporting the use of this object after the native state has been released.protected String
Return the SQLSTATE appropriate for an attempt to use this object after its native state has been released.protected void
javaStateReleased
(boolean nativeStateLive) Called after client code has calledreleaseFromJava
, always on a thread for whichBackend.threadMayEnterPG()
is true, and after any pins held on the state have been released.protected void
javaStateUnreachable
(boolean nativeStateLive) Method that will be called when the Java garbage collector has determined the referent object is no longer strongly reachable.protected final int
lock
(boolean upgrade) Take an exclusive lock in preparation to mutate the state.static <T> T
m
(T detail) Return the argument; convenient breakpoint target for failed assertions.protected void
nativeStateReleased
(boolean javaStateLive) Method that will be called when the associatedLifespan
is released, indicating that the native portion of the state is no longer valid.final void
pin()
Obtain a pin on this state, throwing an appropriate exception if it is not still valid, blocking if necessary until release of a lock.final DualState.Pinned
pinned()
Obtains a pin on this state, returning anAutoCloseable
instance that can be used in atry
-with resources statement to ensure it is unpinned.final boolean
Whether the current thread has pinned this object, for use in assertions.final DualState.Pinned
Obtains a pin on this state, returning anAutoCloseable
instance that can be used in atry
-with resources statement to ensure it is unpinned.final boolean
Obtain a pin on this state, if it is still valid, blocking if necessary until release of a lock.protected final T
referent()
Used internally to obtain this object's referent.protected String
Return a string for an exception message reporting the use of this object after the Java state has been released.protected String
Return the SQLSTATE appropriate for an attempt to use this object after its Java state has been released.protected final void
What Java code will call to explicitly release this instance (in the implementation ofclose
, for example).toString()
Produce a string describing this state object in a way useful for debugging, with such information as the associatedLifespan
and whether the state is fresh or stale.Produce a string with such details of this object as might be useful for debugging, starting with an abbreviated form of the class name of the supplied object.final <E extends Throwable>
voidRuns r with this state pinned, unless the state has already been released, completing normally without running r in that case.protected final void
unlock
(int s) Callsunlock(s, false)
.protected final void
unlock
(int s, boolean isNativeRelease) Release a lock, optionally setting theNATIVE_RELEASED
flag atomically in the process.final void
unpin()
Release a pin.Methods inherited from class java.lang.ref.Reference
clone, isEnqueued, reachabilityFence, refersTo
-
Constructor Details
-
DualState
Construct aDualState
instance with a reference to the Java object whose state it represents.Subclass constructors must accept a cookie parameter from the native caller, and pass it along to superclass constructors. That allows some confidence that constructor parameters representing native values are for real, and also that the construction is taking place on a thread holding the native lock, keeping the concurrency story simple.
- Parameters:
referent
- The Java object whose state this instance represents.lifespan
-Lifespan
whose release callback will indicate that this object's native state is no longer valid. If null, it indicates that the state is held in long-lived native memory (such as JavaMemoryContext), and can only be released viajavaStateUnreachable
orjavaStateReleased
.
-
-
Method Details
-
m
public static <T> T m(T detail) Return the argument; convenient breakpoint target for failed assertions. -
nativeStateReleased
protected void nativeStateReleased(boolean javaStateLive) Method that will be called when the associatedLifespan
is released, indicating that the native portion of the state is no longer valid. The implementing class should clean up whatever is appropriate to that event.This object's exclusive
lock()
will always be held when this method is called during lifespan release. The class whose state this is must usepin()
, followed byunpin()
in afinally
block, around every (ideally short) block of code that could refer to the native state.This default implementation does nothing.
- Parameters:
javaStateLive
- true is passed if the instance's "Java state" is still considered live, that is,releaseFromJava
has not been called, and the garbage collector has not determined the referent to be unreachable.
-
javaStateUnreachable
protected void javaStateUnreachable(boolean nativeStateLive) Method that will be called when the Java garbage collector has determined the referent object is no longer strongly reachable. This default implementation does nothing; a subclass should override it to do any cleanup, or release of native resources, that may be required.If the
nativeStateLive
parameter is false, this method must avoid any action (such as freeing) it would otherwise take on the associated native state; if it does not, double-free crashes can result.It is not necessary for this method to remove the instance from the live-instances data structures; that will have been done just before this method is called.
- Parameters:
nativeStateLive
- true is passed if the instance's "native state" is still considered live, that is, no lifespan callback has been invoked to stamp it invalid (nor has it been "adopted").
-
javaStateReleased
protected void javaStateReleased(boolean nativeStateLive) Called after client code has calledreleaseFromJava
, always on a thread for whichBackend.threadMayEnterPG()
is true, and after any pins held on the state have been released.This should not be called directly. When Java code has called
releaseFromJava
, the state will be changed to 'released' immediately, though without actually disturbing any state that might be referenced by threads with existing pins. This method will be called at some later time, always on a thread able to enter PG, and with no other threads having the native state pinned, so this is the place for any actual release of native state that may be needed.If the
nativeStateLive
parameter is false, this method must avoid any action (such as freeing) it would otherwise take on the associated native state; if it does not, double-free crashes can result.This default implementation calls
javaStateUnreachable
, which, in typical cases, will have the same cleanup to do.- Parameters:
nativeStateLive
- true is passed if the instance's "native state" is still considered live, that is, no lifespan callback has been invoked to stamp it invalid (nor has it been "adopted").
-
releaseFromJava
protected final void releaseFromJava()What Java code will call to explicitly release this instance (in the implementation ofclose
, for example).The state is immediately marked 'released' to prevent future use, while a call to
javaStateReleased
will be deferred until after any pins currently held on the state have been released. -
enqueue
public final boolean enqueue()ThrowsUnsupportedOperationException
;releaseFromJava
must be used rather than calling this method directly. -
clear
public final void clear()ThrowsUnsupportedOperationException
;releaseFromJava
must be used rather than calling this method directly. -
get
ThrowsUnsupportedOperationException
; client code should already hold a reference. -
referent
Used internally to obtain this object's referent. -
pin
Obtain a pin on this state, throwing an appropriate exception if it is not still valid, blocking if necessary until release of a lock.Pins are re-entrant; a thread may obtain more than one on the same object, in strictly nested fashion. Only the outer acquisition (and corresponding release) will have any memory synchronization effect; likewise, only the outer acquisition will detect release of the object and throw the associated exception.
- Throws:
SQLException
- if the native state or the Java state has been released.CancellationException
- if the thread is interrupted while waiting.
-
pinned
Obtains a pin on this state, returning anAutoCloseable
instance that can be used in atry
-with resources statement to ensure it is unpinned.- Throws:
SQLException
- if the native state or the Java state has been released.
-
pinnedNoChecked
Obtains a pin on this state, returning anAutoCloseable
instance that can be used in atry
-with resources statement to ensure it is unpinned.- Throws:
IllegalStateException
- for use where a checked exception is not wanted, any resulting SQLException will be wrapped in IllegalStateException
-
pinUnlessReleased
public final boolean pinUnlessReleased()Obtain a pin on this state, if it is still valid, blocking if necessary until release of a lock.Pins are re-entrant; a thread may obtain more than one on the same object, in strictly nested fashion. Only the outer acquisition (and corresponding release) will have any memory synchronization effect; likewise, only the outer acquisition will detect release of the object and throw the associated exception.
- Returns:
- true if the state has already been released; this will often be
used in a caller (such as a
close
orfree
operation) that will have nothing to do and return immediately if this method returns true. - Throws:
CancellationException
- if the thread is interrupted while waiting.
-
unlessReleased
Runs r with this state pinned, unless the state has already been released, completing normally without running r in that case.- Throws:
E
-
unpin
public final void unpin()Release a pin.If the current thread has pinned the same object more than once, only the last
unpin
will have any memory synchronization effect. -
pinnedByCurrentThread
public final boolean pinnedByCurrentThread()Whether the current thread has pinned this object, for use in assertions.- Returns:
- true if the current thread holds a(t least one) pin on the receiver, or is the PG thread and holds the lock.
-
lock
protected final int lock(boolean upgrade) Take an exclusive lock in preparation to mutate the state.Only a thread for which
Backend.threadMayEnterPG()
returns true may acquire this lock.- Parameters:
upgrade
- whether to acquire the lock without blocking even in the presence of a pin held by this thread; should be true only in cases where inspection shows a nearby enclosing pin whose assumptions clearly will not be violated by the actions to be taken under the lock.- Returns:
- A semi-redacted version of the lock state, enough to discern
whether it contains
NATIVE_RELEASED
orJAVA_RELEASED
in case the caller cares, and for the pairedunlock
call to know whether this was a reentrant call, or should really be released.
-
unlock
protected final void unlock(int s) Callsunlock(s, false)
.- Parameters:
s
- must be the value returned by thelock
call.
-
unlock
protected final void unlock(int s, boolean isNativeRelease) Release a lock, optionally setting theNATIVE_RELEASED
flag atomically in the process.- Parameters:
s
- must be the value returned by thelock
call.isNativeRelease
- whether to set theNATIVE_RELEASED
flag.
-
adoptionLock
Specialized version oflock
for use by code implementing anadopt
operation (in which complete control of an object is handed back to PostgreSQL and it is dissociated from Java).Can only be called on the PG thread, which must already hold a pin. No other thread can hold a pin, and neither the
NATIVE_RELEASED
norJAVA_RELEASED
flag may be set. This method is non-blocking and will simply throw an exception if these preconditions are not satisfied.- Throws:
SQLException
-
adoptionUnlock
Specialized version ofunlock
for use by code implementing anadopt
operation (in which complete control of an object is handed back to PostgreSQL and it is dissociated from Java).Must only be called on the PG thread, which must have acquired
adoptionLock
. Invokes thenativeStateReleased
callback, then releases the lock, leaving bothNATIVE_RELEASED
andJAVA_RELEASED
flags set. When the calling code releases the prior pin it was expected to hold, thejavaStateReleased
callback will execute. A value of false will be passed to both callbacks.- Throws:
SQLException
-
identifierForMessage
Return a string identifying this object in a way useful within an exception message for use of this state after native release or Java release.This implementation returns the class name of the referent, or of this object if the referent has already been cleared.
-
invalidMessage
Return a string for an exception message reporting the use of this object after the native state has been released.This implementation returns
identifierForMessage()
with " used beyond its PostgreSQL lifetime" appended. -
releasedMessage
Return a string for an exception message reporting the use of this object after the Java state has been released.This implementation returns
identifierForMessage()
with " used after released by Java" appended. -
invalidSqlState
Return the SQLSTATE appropriate for an attempt to use this object after its native state has been released.This implementation returns 55000, object not in prerequisite state.
-
releasedSqlState
Return the SQLSTATE appropriate for an attempt to use this object after its Java state has been released.This implementation returns 55000, object not in prerequisite state.
-
toString
Produce a string describing this state object in a way useful for debugging, with such information as the associatedLifespan
and whether the state is fresh or stale.This method calls
toString(Object)
passingthis
. Subclasses are encouraged to override that method with versions that add subclass-specific details. -
toString
Produce a string with such details of this object as might be useful for debugging, starting with an abbreviated form of the class name of the supplied object.Subclasses are encouraged to override this and then call it, via super, passing the object unchanged, and then append additional subclass-specific details to the result.
Because the recursion ends here, this one actually does construct the abbreviated form of the class name of the object, and use it at the start of the returned string.
- Parameters:
o
- An object whose class name (abbreviated by stripping the package prefix) will be used at the start of the string. Passingnull
is the same as passingthis
.- Returns:
- Description of this state object, prefixed with the abbreviated class name of the passed object.
-