Class 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, 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 resource owner must be associated with the instance.

    A parameter to the DualState constructor is a ResourceOwner, a PostgreSQL implementation concept introduced in PG 8.0. A nativeStateReleased event occurs when the corresponding ResourceOwner is released in PostgreSQL.

    However, this class does not require the resourceOwner parameter to be, in all cases, a pointer to a PostgreSQL ResourceOwner. It is treated simply as an opaque long value, to be compared to a value passed at release time (as if in a ResourceOwner callback). Other values (such as pointers to other allocated structures, which of course cannot match any PG ResourceOwner existing at the same time) can also be used. In PostgreSQL 9.5 and later, a MemoryContext could be used, with its address passed to a MemoryContextCallback for release. For state that is scoped to a single invocation of a PL/Java function, the address of the Invocation 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 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 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 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 resource owner 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 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 a DualState.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 that DualState subclass instances are being constructed from native code.
      static class  DualState.SingleFreeErrorData<T>
      A DualState subclass whose only native resource releasing action needed is FreeErrorData of a single pointer.
      static class  DualState.SingleFreeTupleDesc<T>
      A DualState subclass whose only native resource releasing action needed is FreeTupleDesc of a single pointer.
      static class  DualState.SingleGuardedLong<T>
      A DualState subclass serving only to guard access to a single nonzero long value (typically a native pointer).
      static class  DualState.SingleHeapFreeTuple<T>
      A DualState subclass whose only native resource releasing action needed is heap_freetuple of a single pointer.
      static class  DualState.SingleMemContextDelete<T>
      A DualState subclass whose only native resource releasing action needed is MemoryContextDelete of a single context.
      static class  DualState.SinglePfree<T>
      A DualState subclass whose only native resource releasing action needed is pfree of a single pointer.
      static class  DualState.SingleSPIcursorClose<T>
      A DualState subclass whose only native resource releasing action needed is SPI_cursor_close of a single pointer.
      static class  DualState.SingleSPIfreeplan<T>
      A DualState subclass whose only native resource releasing action needed is SPI_freeplan of a single pointer.
    • Field Summary

      Fields 
      Modifier and Type Field Description
      protected long m_resourceOwner
      Pointer value of the ResourceOwner this instance belongs to, if any.
    • Constructor Summary

      Constructors 
      Modifier Constructor Description
      protected DualState​(DualState.Key cookie, T referent, long resourceOwner)
      Construct a DualState 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 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 void adoptionUnlock​(DualState.Key cookie)
      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).
      protected static void checkCookie​(DualState.Key cookie)
      Check that a cookie is valid, throwing an unchecked exception otherwise.
      void clear()
      Throws UnsupportedOperationException; releaseFromJava must be used rather than calling this method directly.
      boolean enqueue()
      Throws UnsupportedOperationException; releaseFromJava must be used rather than calling this method directly.
      T get()
      Throws UnsupportedOperationException; 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 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 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 associated ResourceOwner 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 of close, for example).
      String toString()
      Produce a string describing this state object in a way useful for debugging, with such information as the associated ResourceOwner 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)
      protected void unlock​(int s, boolean isNativeRelease)
      Release a lock, optionally setting the NATIVE_RELEASED flag atomically in the process.
      void unpin()
      Release a pin.
    • Field Detail

      • m_resourceOwner

        protected final long m_resourceOwner
        Pointer value of the ResourceOwner this instance belongs to, if any.
    • Constructor Detail

      • DualState

        protected DualState​(DualState.Key cookie,
                            T referent,
                            long resourceOwner)
        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:
        cookie - Capability held by native code to invoke DualState constructors.
        referent - The Java object whose state this instance represents.
        resourceOwner - Pointer value of the native ResourceOwner 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 via javaStateUnreachable or javaStateReleased.
    • 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 associated ResourceOwner 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 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 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 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 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 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.
      • 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.
      • 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​(DualState.Key cookie)
                                   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.

        Parameters:
        cookie - Capability held by native code to invoke special DualState methods.
        Throws:
        SQLException
      • adoptionUnlock

        protected final void adoptionUnlock​(DualState.Key cookie)
                                     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.

        Parameters:
        cookie - Capability held by native code to invoke special DualState 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 associated ResourceOwner 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.