- java.lang.Object
-
- java.lang.ref.Reference<T>
-
- java.lang.ref.WeakReference<T>
-
- org.postgresql.pljava.internal.DualState<T>
-
- Direct Known Subclasses:
DualState.SingleGuardedLong
public abstract class DualState<T> extends WeakReference<T>
Base class for object state with corresponding Java and native components.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, aWeakReference
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
, ornativeStateReleased
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 resource owner must be associated with the instance.A parameter to the
DualState
constructor is aResourceOwner
, a PostgreSQL implementation concept introduced in PG 8.0. AnativeStateReleased
event occurs when the correspondingResourceOwner
is released in PostgreSQL.However, this class does not require the
resourceOwner
parameter to be, in all cases, a pointer to a PostgreSQLResourceOwner
. It is treated simply as an opaquelong
value, to be compared to a value passed at release time (as if in aResourceOwner
callback). Other values (such as pointers to other allocated structures, which of course cannot match any PGResourceOwner
existing at the same time) can also be used. In PostgreSQL 9.5 and later, aMemoryContext
could be used, with its address passed to aMemoryContextCallback
for release. For state that is scoped to a single invocation of a PL/Java function, the address of theInvocation
can be used. Such references can be considered "generalized" resource owners.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 thepljava.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 thepljava.java_thread_pg_entry
settingallow
, the status is represented by holding the object monitor onBackend.THREADLOCK
, andBackend.threadMayEnterPG()
returns true for whatever thread holds it. Under that setting, there can be moments whenBackend.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 observeBackend.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
, andnativeStateReleased
, 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
settingallow
, 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
andunpin
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 resource owner 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 untilunpin
. A subclass can customize the messages orSQLSTATE
codes for the exceptionspin()
may throw, by overriding one or more ofidentifierForMessage
,invalidMessage
,releasedMessage
,invalidSqlState
, orreleasedSqlState
.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
andunpin
arelock
andunlock
, which are not expected to be used as widely. The chief use oflock
/unlock
is around the call tonativeStateReleased
when handling a resource owner callback from PostgreSQL. They can be used in subclasses to surround modifications to the state, as needed. Alock
will block until all earlier-acquired pins are released; subsequent pins block until the lock is released. Only the PG thread may uselock
/unlock
. Anupgrade
argument tolock
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 timereleaseFromJava
is called, that could confuse any code that already holds a pin and is relying on the state. Such changes must be made in thejavaStateReleased
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 thatDualState
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 resource owner'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. The requirement to pass any constructor aDualState.Key
instance, obtainable by native code, is intended to reinforce that convention. It is not abuse-proof, or intended as a security mechanism, but only a guard against programming mistakes. - Reference queue processing takes place only at chosen points where a thread enters or exits native code, on the PG thread.
- Resource-owner callbacks originate in native code, on the PG thread.
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description static class
DualState.Key
Magic cookie needed as a constructor parameter to confirm thatDualState
subclass instances are being constructed from native code.static class
DualState.SingleFreeErrorData<T>
ADualState
subclass whose only native resource releasing action needed isFreeErrorData
of a single pointer.static class
DualState.SingleFreeTupleDesc<T>
ADualState
subclass whose only native resource releasing action needed isFreeTupleDesc
of a single pointer.static class
DualState.SingleGuardedLong<T>
ADualState
subclass serving only to guard access to a single nonzerolong
value (typically a native pointer).static class
DualState.SingleHeapFreeTuple<T>
ADualState
subclass whose only native resource releasing action needed isheap_freetuple
of a single pointer.static class
DualState.SingleMemContextDelete<T>
ADualState
subclass whose only native resource releasing action needed isMemoryContextDelete
of a single context.static class
DualState.SinglePfree<T>
ADualState
subclass whose only native resource releasing action needed ispfree
of a single pointer.static class
DualState.SingleSPIcursorClose<T>
ADualState
subclass whose only native resource releasing action needed isSPI_cursor_close
of a single pointer.static class
DualState.SingleSPIfreeplan<T>
ADualState
subclass whose only native resource releasing action needed isSPI_freeplan
of a single pointer.
-
Field Summary
Fields Modifier and Type Field Description protected long
m_resourceOwner
Pointer value of theResourceOwner
this instance belongs to, if any.
-
Constructor Summary
Constructors Modifier Constructor Description protected
DualState(DualState.Key cookie, T referent, long resourceOwner)
Construct aDualState
instance with a reference to the Java object whose state it represents.
-
Method Summary
Modifier and Type Method Description protected void
adoptionLock(DualState.Key cookie)
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 void
adoptionUnlock(DualState.Key cookie)
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).protected static void
checkCookie(DualState.Key cookie)
Check that a cookie is valid, throwing an unchecked exception otherwise.void
clear()
ThrowsUnsupportedOperationException
;releaseFromJava
must be used rather than calling this method directly.boolean
enqueue()
ThrowsUnsupportedOperationException
;releaseFromJava
must be used rather than calling this method directly.T
get()
ThrowsUnsupportedOperationException
; client code should already hold a reference.protected String
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.protected String
invalidMessage()
Return a string for an exception message reporting the use of this object after the native state has been released.protected String
invalidSqlState()
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 int
lock(boolean upgrade)
Take an exclusive lock in preparation to mutate the state.protected void
nativeStateReleased(boolean javaStateLive)
Method that will be called when the associatedResourceOwner
is released, indicating that the native portion of the state is no longer valid.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.boolean
pinnedByCurrentThread()
Whether the current thread has pinned this object, for use in assertions.boolean
pinUnlessReleased()
Obtain a pin on this state, if it is still valid, blocking if necessary until release of a lock.protected T
referent()
Used internally to obtain this object's referent.protected String
releasedMessage()
Return a string for an exception message reporting the use of this object after the Java state has been released.protected String
releasedSqlState()
Return the SQLSTATE appropriate for an attempt to use this object after its Java state has been released.protected void
releaseFromJava()
What Java code will call to explicitly release this instance (in the implementation ofclose
, for example).String
toString()
Produce a string describing this state object in a way useful for debugging, with such information as the associatedResourceOwner
and whether the state is fresh or stale.String
toString(Object o)
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.protected void
unlock(int s)
Callsunlock(s, false)
.protected void
unlock(int s, boolean isNativeRelease)
Release a lock, optionally setting theNATIVE_RELEASED
flag atomically in the process.void
unpin()
Release a pin.-
Methods inherited from class java.lang.ref.Reference
clone, isEnqueued, reachabilityFence
-
-
-
-
Constructor Detail
-
DualState
protected DualState(DualState.Key cookie, T referent, long resourceOwner)
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:
cookie
- Capability held by native code to invokeDualState
constructors.referent
- The Java object whose state this instance represents.resourceOwner
- Pointer value of the nativeResourceOwner
whose release callback will indicate that this object's native state is no longer valid. If zero (a NULL pointer in C), it indicates that the state is held in long-lived native memory (such as JavaMemoryContext), and can only be released viajavaStateUnreachable
orjavaStateReleased
.
-
-
Method Detail
-
checkCookie
protected static void checkCookie(DualState.Key cookie)
Check that a cookie is valid, throwing an unchecked exception otherwise.
-
nativeStateReleased
protected void nativeStateReleased(boolean javaStateLive)
Method that will be called when the associatedResourceOwner
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 resource owner 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 resource-owner 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 resource-owner 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
public final T get()
ThrowsUnsupportedOperationException
; client code should already hold a reference.
-
referent
protected final T referent()
Used internally to obtain this object's referent.
-
pin
public final void pin() throws SQLException
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.
-
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.
-
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
protected final void adoptionLock(DualState.Key cookie) throws SQLException
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.- Parameters:
cookie
- Capability held by native code to invoke specialDualState
methods.- Throws:
SQLException
-
adoptionUnlock
protected final void adoptionUnlock(DualState.Key cookie) throws SQLException
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.- Parameters:
cookie
- Capability held by native code to invoke specialDualState
methods.- Throws:
SQLException
-
identifierForMessage
protected String 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
protected String 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
protected String 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
protected String 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
protected String 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
public String toString()
Produce a string describing this state object in a way useful for debugging, with such information as the associatedResourceOwner
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
public String toString(Object o)
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.
-
-