Class TransactionLifecycleMonitor

java.lang.Object
org.broadleafcommerce.common.persistence.transaction.TransactionLifecycleMonitor
All Implemented Interfaces:
EventListener, BroadleafApplicationListener<TransactionLifecycleEvent>, SqlStatementLoggable, org.springframework.beans.factory.Aware, org.springframework.context.ApplicationContextAware, org.springframework.context.ApplicationListener<TransactionLifecycleEvent>, org.springframework.context.Lifecycle, org.springframework.context.Phased, org.springframework.context.SmartLifecycle

@Component("blTransactionLifecycleMonitor") public class TransactionLifecycleMonitor extends Object implements BroadleafApplicationListener<TransactionLifecycleEvent>, org.springframework.context.ApplicationContextAware, org.springframework.context.SmartLifecycle, SqlStatementLoggable
Responsible for most of the work related to monitoring and logging transaction state. This class is notified via a LifecycleAwareJpaTransactionManager when a key TransactionLifecycleEvent occurs. Based on this information, this monitor attempts to detect transaction fault states and report them as part of SUPPORT level logging (SupportLogger).

Currently, the monitor attempts to recognize the following fault states:
  • TRANSACTIONMONITOR(1) - Leaked Resource: The transaction thread is not considered stuck, but no queries have been issued against the tracked EntityManager in loggingReportingLagThreshold. This could indicate the thread has moved on and the transaction was not correctly finalized.
  • TRANSACTIONMONITOR(2) - Long Running Transaction: The transaction thread is not considered stuck, but the transaction info has been alive for loggingThreshold. This is not necessarily a fault, but may warrant review. Long running or stuck transactions can account for long resource lock times. Note, this case can later become a stuck thread if the stuckThreshold has not elapsed.
  • TRANSACTIONMONITOR(3) - Exception During Finalization: The transaction is attempting to finalize normally, but has experienced an exception during that finalization attempt. This could indicate a problem communicating with the backing database and may result in orphaned resources in the data tier.
  • TRANSACTIONMONITOR(4) - Stuck Thread: The transaction thread is considered stuck with a transaction info alive for loggingThreshold and no change in thread stack for stuckThreshold. Long running or stuck transactions can account for long resource lock times.
  • TRANSACTIONMONITOR(5) - Alive At Shutdown: The transaction info is considered active at the time of container shutdown. This is not necessarily a fault, but may warrant review. Items at shutdown may simply be waiting for final closure. However, subsequent system kill calls (if applicable) could prematurely exit these connections and they may be interesting for review.

This monitor is enabled or disabled per LifecycleAwareJpaTransactionManager via LifecycleAwareJpaTransactionManager.isEnabled(). If no enabled transaction manager is found, the monitor is fully disabled.

The loggingThreshold variable can be controlled via the 'log.transaction.lifecycle.logging.threshold.millis' property. The default value is 600000.

The stuckThreshold variable can be controlled via the 'log.transaction.lifecycle.stuck.threshold.millis' property. The default values is 300000.

The loggingPollingResolution variable can be controlled via the 'log.transaction.lifecycle.logging.polling.resolution.millis' property. The default value is 30000.

The loggingReportingLagThreshold variable can be controlled via the 'log.transaction.lifecycle.reporting.lag.threshold.millis' property. The default value is 300000.

The countMax variable can be controlled via the 'log.transaction.lifecycle.info.count.max' property. The default value is 5000.

The useCompression variable can be controlled via the 'log.transaction.lifecycle.use.compression' property. The default value is true.

The abbreviateStatements variable can be controlled via the 'log.transaction.lifecycle.abbreviate.statements' property. The default value is true.

The abbreviateStatementsLength variable can be controlled via the 'log.transaction.lifecycle.abbreviate.statements.length' property. The default value is 200.

The decompressStatementForLog variable can be controlled via the 'log.transaction.lifecycle.decompress.statement' property. The default value is false.

The maxQueryListSize variable can be controlled via the 'log.transaction.lifecycle.query.list.max.size' property. The default value is 100.

This monitor is intended for temporary usage as part of transaction related debugging efforts. From both a heap utilization and performance standpoint, this monitor is suitable for production with default settings. Performance impacts should be minor and will generally be related to creation of the intial call stack, compression of that call stack and subsequent compression of sql statements. Compression can be turned off via the 'log.transaction.lifecycle.use.compression' for a minor performance benefit at the cost of additional heap usage. Heap usage can be capped via the 'log.transaction.lifecycle.info.count.max' property, but if the max happens to be reached, new transactions will not be monitored. This is more a safety net feature than anything and it's not anticipated that the default max count will be reached during normal usage. Set 'log.transaction.lifecycle.info.count.max' to -1 to uncap growth.

To avoid overly large disk usage in logs (in case many log statements are emitted with many queries per), the system will truncate statements to a default length of 200 characters and leave those statements compressed in the logs (if they were configured to be compressed). See the abbreviateStatements and abbreviateStatementsLength variable to change this behavior. To view the decompressed version, you'll need to pass the compressed string from the log line to the decompressLogLine(String) method to see the decompressed version. This can be easily done by writing a simple main program that instantiates TransactionLifecycleMonitor and calls this method. To emit the uncompressed version of the queries instead to the logs, change the decompressStatementForLog property value. Finally, by default, the system will only remember and emit the last 100 queries in the transaction. This value can be tweaked via the maxQueryListSize variable. Set this value to -1 to uncap the expansion of the query list.
Author:
Jeff Fischer
  • Field Details

    • transactionManagers

      @Autowired(required=false) protected List<LifecycleAwareJpaTransactionManager> transactionManagers
    • modifiers

      @Autowired(required=false) protected List<TransactionInfoCustomModifier> modifiers
    • loggingThreshold

      @Value("${log.transaction.lifecycle.logging.threshold.millis:600000}") protected long loggingThreshold
    • stuckThreshold

      @Value("${log.transaction.lifecycle.stuck.threshold.millis:300000}") protected long stuckThreshold
    • loggingPollingResolution

      @Value("${log.transaction.lifecycle.logging.polling.resolution.millis:30000}") protected long loggingPollingResolution
    • loggingReportingLagThreshold

      @Value("${log.transaction.lifecycle.reporting.lag.threshold.millis:300000}") protected long loggingReportingLagThreshold
    • countMax

      @Value("${log.transaction.lifecycle.info.count.max:5000}") protected int countMax
    • useCompression

      @Value("${log.transaction.lifecycle.use.compression:true}") protected boolean useCompression
    • abbreviateStatements

      @Value("${log.transaction.lifecycle.abbreviate.statements:true}") protected boolean abbreviateStatements
    • abbreviateStatementsLength

      @Value("${log.transaction.lifecycle.abbreviate.statements.length:200}") protected int abbreviateStatementsLength
    • decompressStatementForLog

      @Value("${log.transaction.lifecycle.decompress.statement:false}") protected boolean decompressStatementForLog
    • maxQueryListSize

      @Value("${log.transaction.lifecycle.query.list.max.size:100}") protected int maxQueryListSize
    • infos

      protected Map<Integer,TransactionInfo> infos
    • isStarted

      protected boolean isStarted
    • enabled

      protected boolean enabled
    • timer

      protected Timer timer
  • Constructor Details

    • TransactionLifecycleMonitor

      public TransactionLifecycleMonitor()
  • Method Details

    • getInstance

      public static TransactionLifecycleMonitor getInstance()
    • init

      @PostConstruct public void init()
    • setApplicationContext

      public void setApplicationContext(org.springframework.context.ApplicationContext applicationContext) throws org.springframework.beans.BeansException
      Specified by:
      setApplicationContext in interface org.springframework.context.ApplicationContextAware
      Throws:
      org.springframework.beans.BeansException
    • isAutoStartup

      public boolean isAutoStartup()
      Specified by:
      isAutoStartup in interface org.springframework.context.SmartLifecycle
    • stop

      public void stop(Runnable callback)
      Specified by:
      stop in interface org.springframework.context.SmartLifecycle
    • start

      public void start()
      Specified by:
      start in interface org.springframework.context.Lifecycle
    • stop

      public void stop()
      Specified by:
      stop in interface org.springframework.context.Lifecycle
    • isRunning

      public boolean isRunning()
      Specified by:
      isRunning in interface org.springframework.context.Lifecycle
    • getPhase

      public int getPhase()
      Specified by:
      getPhase in interface org.springframework.context.Phased
      Specified by:
      getPhase in interface org.springframework.context.SmartLifecycle
    • isAsynchronous

      public boolean isAsynchronous()
      Description copied from interface: BroadleafApplicationListener
      Indicates if this application listener should be run in a background thread if a TaskExecutor is configured. If no TaskExecutor is configure with a bean name of "blApplicationEventMulticastTaskExecutor" then this will be run synchronously regardless. Generally, this should return false as the default implementation has no reliability guarantees associated with Asynch processing. However, for convenience this allows asynch processing for situations that don't require guaranteed processing. For example, publishing statistics or log events in a background thread would be candidates for background processing. Handling important database updates would not.
      Specified by:
      isAsynchronous in interface BroadleafApplicationListener<TransactionLifecycleEvent>
      Returns:
      See Also:
    • onApplicationEvent

      public void onApplicationEvent(TransactionLifecycleEvent event)
      Specified by:
      onApplicationEvent in interface org.springframework.context.ApplicationListener<TransactionLifecycleEvent>
    • log

      public void log(String statement)
      Specified by:
      log in interface SqlStatementLoggable
    • decompressLogLine

      public String decompressLogLine(String compressedFromLog) throws IOException
      Specified by:
      decompressLogLine in interface SqlStatementLoggable
      Throws:
      IOException
    • getLoggingThreshold

      public long getLoggingThreshold()
    • setLoggingThreshold

      public void setLoggingThreshold(long loggingThreshold)
    • getStuckThreshold

      public long getStuckThreshold()
    • setStuckThreshold

      public void setStuckThreshold(long stuckThreshold)
    • getLoggingPollingResolution

      public long getLoggingPollingResolution()
    • setLoggingPollingResolution

      public void setLoggingPollingResolution(long loggingPollingResolution)
    • getLoggingReportingLagThreshold

      public long getLoggingReportingLagThreshold()
    • setLoggingReportingLagThreshold

      public void setLoggingReportingLagThreshold(long loggingReportingLagThreshold)
    • getCountMax

      public int getCountMax()
    • setCountMax

      public void setCountMax(int countMax)
    • isUseCompression

      public boolean isUseCompression()
    • setUseCompression

      public void setUseCompression(boolean useCompression)
    • groomInProgressTransactionInfos

      protected void groomInProgressTransactionInfos()
    • detectLeakage

      protected boolean detectLeakage(List<Integer> infosToRemove, Integer key, long currentTime, TransactionInfo info)
    • detectExpiry

      protected boolean detectExpiry(List<Integer> infosToRemove, Integer key, long currentTime, TransactionInfo info, StackTraceElement[] elements)
    • compileThreadInformation

      protected StackTraceElement[] compileThreadInformation(long currentTime, TransactionInfo info, Thread thread)
    • finalizeTransaction

      protected void finalizeTransaction(TransactionLifecycleEvent event)
    • getEntityManagerFromTransactionObject

      protected jakarta.persistence.EntityManager getEntityManagerFromTransactionObject(Object transactionObject)
    • getCurrentTransactionInfo

      protected TransactionInfo getCurrentTransactionInfo()
    • isAtLeastOneTransactionManagerEnabled

      protected boolean isAtLeastOneTransactionManagerEnabled()