java.lang.Object
java.lang.ref.Reference<T>
java.lang.ref.WeakReference<T>
org.postgresql.pljava.internal.DualState<T>
Direct Known Subclasses:
DualState.ListHead, DualState.SingleGuardedBB, DualState.SingleGuardedLong, DualState.SingleMemContextDelete

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, 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 structs 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

    Nested Classes
    Modifier and Type
    Class
    Description
    static class 
    A DualState subclass whose only native resource releasing action needed is heap_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 of AutoCloseable whose close method throws no checked exceptions.
    static class 
    A DualState subclass whose only native resource releasing action needed is a JNI DeleteGlobalRef of a single pointer.
    static class 
    A DualState subclass whose only native resource releasing action needed is FreeErrorData of a single pointer.
    static class 
    A DualState subclass whose only native resource releasing action needed is FreeTupleDesc of a single pointer.
    static class 
    A DualState subclass serving only to guard access to a single nonnull ByteBuffer value.
    static class 
    A DualState subclass serving only to guard access to a single nonzero long value (typically a native pointer).
    static class 
    A DualState subclass whose only native resource releasing action needed is heap_freetuple of a single pointer.
    static class 
    A DualState subclass whose only native resource releasing action needed is MemoryContextDelete of a single context.
    static class 
    A DualState subclass whose only native resource releasing action needed is pfree of a single pointer.
    static class 
    A DualState subclass whose only native resource releasing action needed is SPI_cursor_close of a single pointer.
    static class 
    A DualState subclass whose only native resource releasing action needed is SPI_freeplan of a single pointer.
    static class 
    A DualState subclass whose only native resource releasing action needed is SPI_freetuptable of a single pointer.
  • Constructor Summary

    Constructors
    Modifier
    Constructor
    Description
    protected
    DualState(T referent, Lifespan lifespan)
    Construct a DualState instance with a reference to the Java object whose state it represents.
  • Method Summary

    Modifier and Type
    Method
    Description
    protected final void
    Specialized version of lock for use by code implementing an adopt operation (in which complete control of an object is handed back to PostgreSQL and it is dissociated from Java).
    protected final void
    Specialized version of unlock for use by code implementing an adopt operation (in which complete control of an object is handed back to PostgreSQL and it is dissociated from Java).
    final void
    Throws UnsupportedOperationException; releaseFromJava must be used rather than calling this method directly.
    final boolean
    Throws UnsupportedOperationException; releaseFromJava must be used rather than calling this method directly.
    final T
    get()
    Throws UnsupportedOperationException; 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 called releaseFromJava, always on a thread for which Backend.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 associated Lifespan 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.
    Obtains a pin on this state, returning an AutoCloseable instance that can be used in a try-with resources statement to ensure it is unpinned.
    final boolean
    Whether the current thread has pinned this object, for use in assertions.
    Obtains a pin on this state, returning an AutoCloseable instance that can be used in a try-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
    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 of close, for example).
    Produce a string describing this state object in a way useful for debugging, with such information as the associated Lifespan 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>
    void
    Runs 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)
    protected final void
    unlock(int s, boolean isNativeRelease)
    Release a lock, optionally setting the NATIVE_RELEASED flag atomically in the process.
    final void
    Release a pin.

    Methods inherited from class java.lang.ref.Reference

    clone, isEnqueued, reachabilityFence, refersTo

    Methods inherited from class java.lang.Object

    equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Constructor Details

    • DualState

      protected DualState(T referent, Lifespan lifespan)
      Construct a DualState 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 via javaStateUnreachable or javaStateReleased.
  • 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 associated Lifespan 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 use pin(), followed by unpin() in a finally 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 called releaseFromJava, always on a thread for which Backend.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 of close, 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()
      Throws UnsupportedOperationException; releaseFromJava must be used rather than calling this method directly.
      Overrides:
      enqueue in class Reference<T>
    • clear

      public final void clear()
      Throws UnsupportedOperationException; releaseFromJava must be used rather than calling this method directly.
      Overrides:
      clear in class Reference<T>
    • get

      public final T get()
      Throws UnsupportedOperationException; client code should already hold a reference.
      Overrides:
      get in class Reference<T>
    • 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.
    • pinned

      public final DualState.Pinned pinned() throws SQLException
      Obtains a pin on this state, returning an AutoCloseable instance that can be used in a try-with resources statement to ensure it is unpinned.
      Throws:
      SQLException - if the native state or the Java state has been released.
    • pinnedNoChecked

      public final DualState.Pinned pinnedNoChecked()
      Obtains a pin on this state, returning an AutoCloseable instance that can be used in a try-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 or free 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

      public final <E extends Throwable> void unlessReleased(Checked.Runnable<E> r) throws E
      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 or JAVA_RELEASED in case the caller cares, and for the paired unlock call to know whether this was a reentrant call, or should really be released.
    • unlock

      protected final void unlock(int s)
      Parameters:
      s - must be the value returned by the lock call.
    • unlock

      protected final void unlock(int s, boolean isNativeRelease)
      Release a lock, optionally setting the NATIVE_RELEASED flag atomically in the process.
      Parameters:
      s - must be the value returned by the lock call.
      isNativeRelease - whether to set the NATIVE_RELEASED flag.
    • adoptionLock

      protected final void adoptionLock() throws SQLException
      Specialized version of lock for use by code implementing an adopt 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 nor JAVA_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

      protected final void adoptionUnlock() throws SQLException
      Specialized version of unlock for use by code implementing an adopt 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 the nativeStateReleased callback, then releases the lock, leaving both NATIVE_RELEASED and JAVA_RELEASED flags set. When the calling code releases the prior pin it was expected to hold, the javaStateReleased callback will execute. A value of false will be passed to both callbacks.

      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 associated Lifespan and whether the state is fresh or stale.

      This method calls toString(Object) passing this. Subclasses are encouraged to override that method with versions that add subclass-specific details.

      Overrides:
      toString in class Object
      Returns:
      Description of this state object.
    • 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. Passing null is the same as passing this.
      Returns:
      Description of this state object, prefixed with the abbreviated class name of the passed object.