All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.caucho.env.dbpool.ManagedPoolItem Maven / Gradle / Ivy

/*
 * Copyright (c) 1998-2018 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.env.dbpool;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import com.caucho.sql.ManagedConnectionImpl;
import com.caucho.transaction.ManagedResource;
import com.caucho.transaction.ManagedXAResource;
import com.caucho.transaction.UserTransactionImpl;
import com.caucho.transaction.XAExceptionWrapper;
import com.caucho.util.CurrentTime;
import com.caucho.util.L10N;

/**
 * Manages a ManagedConnection in the pool. Each ManagedPoolItem corresponds
 * one-to-one to a ManagedConnection.
 *
 * The user's view of the pool item is a UserPoolItem. Because of transaction
 * sharing, it's possible for several UserPoolItems to refer to the same
 * ManagedPoolItem.
 */
class ManagedPoolItem implements ConnectionEventListener, ManagedXAResource {
  private static final L10N L = new L10N(ManagedPoolItem.class);
  private static final Logger log
    = Logger.getLogger(ManagedPoolItem.class.getName());

  private ConnectionPool _cm;

  private ManagedConnectionFactory _mcf;
  private ManagedConnection _mConn;

  private String _id;

  private XAResource _xaResource;
  private LocalTransaction _localTransaction;
  private int _defaultTransactionTimeout;
  private int _transactionTimeout;

  private Subject _subject;
  private ConnectionRequestInfo _requestInfo;

  final Object _shareLock = new Object();

  // The head shared connection for transaction
  // The UserPoolItem code is responsible for this field
  UserPoolItem _shareHead;

  // The other pool items joined transaction
  private ManagedPoolItem _xaHead;
  private ManagedPoolItem _xaNext;

  private boolean _hasConnectionError;
  private boolean _isPastActiveTime;

  private long _poolStartTime;
  private long _poolEventTime;

  private Xid _xid;
  private int _endFlags = -1;

  // flag forcing an XA transaction (for the local transaction optimization)
  private boolean _isXATransaction = true;

  // true if in local transaction
  private boolean _isLocalTransaction;

  private IllegalStateException _allocationStackTrace;

  //
  // statistics
  //

  private long _connectionStartTime;

  public ManagedPoolItem(ConnectionPool cm,
                         ManagedConnectionFactory mcf,
                         ManagedConnection conn)
  {
    _cm = cm;

    _id = _cm.generateId();

    _mcf = mcf;
    _mConn = conn;

    _poolStartTime = CurrentTime.getCurrentTime();
    _poolEventTime = CurrentTime.getCurrentTime();

    _connectionStartTime = cm.getConnectionTimeProbe().start();

    // Gets the resource object from the driver
    try {
      if (cm.isXATransaction()) {
        XAResource xaResource = conn.getXAResource();

        try {
          _defaultTransactionTimeout = xaResource.getTransactionTimeout();
        } catch (Throwable e) {
          log.log(Level.FINE, e.toString(), e);
        }

        _xaResource = xaResource;
      }
    } catch (NotSupportedException e) {
      _cm.setXATransaction(false);
      log.log(Level.FINER, e.toString(), e);
    } catch (Exception e) {
      log.log(Level.FINE, e.toString(), e);
    }

    if (_xaResource == null) {
      _isXATransaction = false;
    }

    // Gets the local transaction from the driver
    try {
      if (_cm.isLocalTransaction())
        _localTransaction = conn.getLocalTransaction();
    } catch (NotSupportedException e) {
      _cm.setLocalTransaction(false);
      log.log(Level.FINER, e.toString(), e);
    } catch (Exception e) {
      log.log(Level.FINER, e.toString(), e);
    }

    _mConn.addConnectionEventListener(this);

    if (log.isLoggable(Level.FINE))
      log.fine("create: " + this +
               "(active:" + _cm.getConnectionActiveCount() +
               ", total:" + _cm.getConnectionCount() + ")");
  }

  /**
   * Sets the subject.
   */
  public void setSubject(Subject subject)
  {
    _subject = subject;
  }

  /**
   * Sets the info.
   */
  public void setInfo(ConnectionRequestInfo info)
  {
    _requestInfo = info;
  }

  /**
   * Returns true if the connection is active.
   */
  public boolean isActive()
  {
    return _shareHead != null;
  }

  /**
   * Returns true if the connection is dead
   */
  public boolean isDead()
  {
    return _mConn == null;
  }

  /**
   * Returns the time of the last event.
   */
  public long getEventTime()
  {
    return _poolEventTime;
  }

  /**
   * Returns the time the connection was first used.
   */
  public long getStartTime()
  {
    return _poolStartTime;
  }

  /**
   * Sets the item's transaction.
   */
  @Override
  public void setTransaction(UserTransactionImpl transaction)
  {
    // _transaction = transaction;
  }

  /**
   * Make this connection active.
   *
   * @return true if the pool item is valid, false if it should be removed.
   */
  synchronized UserPoolItem toActive(Subject subject,
                                     ConnectionRequestInfo info,
                                     UserPoolItem oldPoolItem)
    throws ResourceException
  {
    long now = CurrentTime.getCurrentTime();

    long maxIdleTime = _cm.getMaxIdleTime();
    long maxPoolTime = _cm.getMaxPoolTime();

    if (_hasConnectionError) {
      return null;
    }
    else if (0 < maxIdleTime && _poolEventTime + maxIdleTime < now) {
      return null;
    }
    else if (0 < maxPoolTime && _poolStartTime + maxPoolTime < now) {
      return null;
    }
    else if (_shareHead != null)
      throw new IllegalStateException(L.l("trying to activate active pool item."));

    _poolEventTime = now;
    _isXATransaction = _xaResource != null; // disable LT-optim by default

    UserPoolItem userPoolItem = null;

    if (oldPoolItem != null) {
      Object uConn = oldPoolItem.getUserConnection();

      if (uConn != null)
        _mConn.associateConnection(uConn);

      oldPoolItem.associatePoolItem(this);

      userPoolItem = oldPoolItem;
    }
    else
      userPoolItem = new UserPoolItem(_cm, this);

    if (! isValid(subject, info, userPoolItem)) {
      return null;
    }

    _subject = subject;
    _requestInfo = info;
    userPoolItem.associate(this, _mcf, subject, info);

    if (log.isLoggable(Level.FINE))
      log.fine("allocate " + this);

    if (_cm.getSaveAllocationStackTrace()) {
      _allocationStackTrace = new IllegalStateException(L.l("Connection {0} allocation stack trace", this));
      _allocationStackTrace.fillInStackTrace();
    }

    return userPoolItem;
  }

  /**
   * Checks if the pool item is still valid.
   *
   * @return true if the pool item is valid, false if it should be removed.
   */
  boolean isValid()
  {
    synchronized (this) {
      long now = CurrentTime.getCurrentTime();

      long maxIdleTime = _cm.getMaxIdleTime();
      long maxPoolTime = _cm.getMaxPoolTime();
      long maxActiveTime = _cm.getMaxActiveTime();

      boolean isActive = isActive() || _xid != null;
      boolean isDead = false;

      if (! isActive && _hasConnectionError) {
        isDead = true;
        log.fine("closing pool item from connection error:" + this);
      }
      else if (! isActive &&
               0 < maxIdleTime && _poolEventTime + maxIdleTime < now) {
        isDead = true;
        log.fine("closing pool item from idle timeout:" + this);
      }
      else if (! isActive &&
               0 < maxPoolTime && _poolStartTime + maxPoolTime < now) {
        isDead = true;
        log.fine("closing pool item from pool timeout:" + this);
      }
      else if (isActive &&
               0 < maxActiveTime && _poolEventTime + maxActiveTime < now) {
        isDead = true;
        _isPastActiveTime = true;
        log.warning("closing pool item from active timeout:" + this);
      }

      if (isDead) {
        _hasConnectionError = true;
        return false;
      }
      else
        return true;
    }
  }

  /**
   * Use the item only if it's already been used for the current transaction
   * and is available.  allocateXA returns the same connection for the
   * following case:
   *
   * 
   * UserTransaction.begin();
   *
   * conn = ds.getConnection();
   * ...
   * conn.close();
   *
   * conn = ds.getConnection();
   * ...
   * conn.close();
   * 
* *

Nested connections are not reused. * * * @return true if the pool item has been allocated */ UserPoolItem allocateXA(ManagedConnectionFactory mcf, Subject subject, ConnectionRequestInfo info) { if (_mConn == null) // already closed return null; else if (_subject != subject) return null; else if (_requestInfo != info) return null; else if (_mcf != mcf) return null; else if (_shareHead != null && ! _cm.isShareable()) // is currently in use return null; /* server/14g9, #2708 else if (_hasConnectionError) // had a fatal error return null; */ if (log.isLoggable(Level.FINER)) log.finer("sharing xa-pool item: " + this); UserPoolItem userPoolItem = new UserPoolItem(_cm); userPoolItem.associate(this, _mcf, _subject, _requestInfo); return userPoolItem; } /** * Returns true if the tested pool item is the Xid leader. */ boolean isJoin(ManagedPoolItem item) { if (this == item) return false; else if (_xid != item._xid) return false; else if (_mcf != item._mcf) return false; else return true; } /** * Try to share the connection. */ @Override public boolean share(ManagedResource resource) { if (! (resource instanceof UserPoolItem)) return false; UserPoolItem userPoolItem = (UserPoolItem) resource; if (this == userPoolItem.getOwnPoolItem()) return true; else if (_mConn == null) // already closed return false; else if (! _cm.isShareable()) // not shareable return false; else if (_mcf != userPoolItem.getManagedConnectionFactory()) return false; else if (_subject != userPoolItem.getSubject()) return false; else if (_requestInfo != userPoolItem.getInfo()) return false; else if (_hasConnectionError) // had a fatal error return false; /* // skip for now if (true) return false; userPoolItem.associate(this, _mcf, _subject, _requestInfo); return true; */ return false; } /** * Returns the managed connection. */ ManagedConnection getManagedConnection() { return _mConn; } /** * Returns the user connection. */ /* Object getUserConnection() throws ResourceException { return _userPoolItem.getUserConnection(); } */ /** * Returns the user connection. */ Object allocateConnection() throws ResourceException { Object conn = _mConn.getConnection(_subject, _requestInfo); return conn; } /** * Returns true for a valid connection. */ boolean isValid(Subject subject, ConnectionRequestInfo requestInfo, UserPoolItem userPoolItem) { try { ManagedConnection mConn = getManagedConnection(); if (mConn == null) { return false; } Object userConn = userPoolItem.getUserConnection(); if (userConn == null) { userConn = mConn.getConnection(subject, requestInfo); userPoolItem.setUserConnection(userConn); } return userConn != null; } catch (ResourceException e) { log.log(Level.WARNING, e.toString(), e); return false; } } /** * Returns the XA resource. */ @Override public void enableLocalTransactionOptimization(boolean enableOptimization) { if (_xaResource == null) _isXATransaction = false; else if (_localTransaction == null) _isXATransaction = true; else if (! _cm.isLocalTransactionOptimization()) _isXATransaction = true; else if (! _cm.isShareable()) _isXATransaction = true; else _isXATransaction = ! enableOptimization; } /** * Returns true if the pooled connection supports transactions. */ @Override public boolean supportsTransaction() { // server/164j return _xaResource != null || _localTransaction != null; } /** * Returns the XA resource. */ XAResource getXAResource() { return _xaResource; } /** * Returns the Xid resource. */ @Override public Xid getXid() { return _xid; } /** * Notifies that an application has closed the connection. * * Remove and close the UserPoolItem associated with the PoolItem. If it's * the last UserPoolItem, move to the idle state. */ @Override public void connectionClosed(ConnectionEvent event) { Object handle = event.getConnectionHandle(); if (! _hasConnectionError && handle == null && _shareHead != null) { log.fine(L.l("JCA close event '{0}' for {1} did not have a connection handle. Please notify the JCA resource provider.", event, _mConn)); } UserPoolItem ptr = _shareHead; UserPoolItem prev = null; while (ptr != null) { UserPoolItem next = ptr.getShareNext(); Object userConn = ptr.getUserConnection(); if (userConn == handle || handle == null) { if (prev != null) prev.setShareNext(next); else _shareHead = next; ptr.close(); } else prev = ptr; ptr = next; } if (_shareHead == null) { toIdle(); return; } } /** * Notifies that a local transaction has started. */ @Override public void localTransactionStarted(ConnectionEvent event) { if (_isLocalTransaction || _xid != null) throw new IllegalStateException(L.l("attempted to start local transaction while transaction is in progress.")); // server/30c4, #2627 _isLocalTransaction = true; } /** * Notifies that a local transaction has committed. */ @Override public void localTransactionCommitted(ConnectionEvent event) { if (_isLocalTransaction) { } else if (_xid != null) throw new IllegalStateException(L.l("attempted to commit() local transaction from an active XA transaction.")); else throw new IllegalStateException(L.l("attempted to commit() with no active local transaction.")); // #2627, server/30c4 _isLocalTransaction = false; } /** * Notifies that a local transaction has rolled back. */ @Override public void localTransactionRolledback(ConnectionEvent event) { if (_xid != null) throw new IllegalStateException(L.l("attempted to rollback() local transaction from an active XA transaction.")); else if (! _isLocalTransaction) throw new IllegalStateException(L.l("attempted to rollback() with no active local transaction.")); // #2627, server/30c4 _isLocalTransaction = false; } /** * Notifies that a connection error has occurred. */ @Override public void connectionErrorOccurred(ConnectionEvent event) { _hasConnectionError = true; } /** * Notifies that a connection error has occurred. */ public void setConnectionError() { _hasConnectionError = true; } /** * Returns true if there was a connection error. */ public boolean isConnectionError() { return _hasConnectionError; } /** * Returns true if connection exceeded max-active-time */ public boolean isPastActiveTime() { return _isPastActiveTime; } /** * Returns the allocation stack trace. */ public IllegalStateException getAllocationStackTrace() { return _allocationStackTrace; } /** * Returns true if there is a connection error. */ // XAResource stuff /** * identity of resources */ @Override public boolean isSameRM(XAResource resource) throws XAException { if (! (resource instanceof ManagedPoolItem)) return false; ManagedPoolItem poolItem = (ManagedPoolItem) resource; //if (_cm == poolItem._cm) // return true; if (_xaResource == null) return false; boolean isSameRM = _xaResource.isSameRM(poolItem._xaResource); if (log.isLoggable(Level.FINER)) log.finer("isSameRM->" + isSameRM + " " + _xaResource); return isSameRM; } /** * starts work on a transaction branch */ @Override public void start(Xid xid, int flags) throws XAException { if (_xid != null) { if (log.isLoggable(Level.FINER)) log.finer("connection pool start XA: rejoin " + this); return; } if (flags == TMJOIN && _xid == null) { // TMJOIN means the resource manager is managing more than one // connection. The delegates tie the PoolItems managed by // the same resource manager together. _xid = xid; UserTransactionImpl trans = _cm.getTransaction(); if (trans != null) { ManagedPoolItem xaHead = _cm.findJoin(trans, this); if (xaHead != null) { _xaNext = xaHead._xaNext; _xaHead = xaHead; xaHead._xaNext = this; } } } if (flags == TMSUSPEND || flags == TMRESUME) { if (_xaResource != null && _xid != null && _isXATransaction && _localTransaction == null) { if (log.isLoggable(Level.FINER)) log.finer("suspend-XA: " + xid + " " + _xaResource); _xaResource.start(xid, flags); } return; } // local transaction optimization if (! _isXATransaction && flags != TMJOIN && _localTransaction != null) { try { if (log.isLoggable(Level.FINER)) log.finer("begin-local-XA: " + xid + " " + _localTransaction); _localTransaction.begin(); } catch (ResourceException e) { throw new XAExceptionWrapper(e); } _xid = xid; return; } if (_xaResource != null) { if (log.isLoggable(Level.FINER)) log.finer("start-XA: " + xid + " " + _xaResource); _xaResource.start(xid, flags); _isXATransaction = true; } else { if (log.isLoggable(Level.FINER)) log.finer("start-XA with non XA resource: " + xid + " " + _xaResource); } _xid = xid; } /** * Sets the transaction timeout */ @Override public boolean setTransactionTimeout(int seconds) throws XAException { if (seconds == _transactionTimeout) return true; XAResource xaResource = _xaResource; _transactionTimeout = seconds; if (xaResource == null) return true; else if (seconds == 0) return xaResource.setTransactionTimeout(_defaultTransactionTimeout); else return xaResource.setTransactionTimeout(seconds); } /** * Returns the timeout of the underlying resource. */ @Override public int getTransactionTimeout() throws XAException { return _transactionTimeout; } /** * forget about the transaction */ @Override public void forget(Xid xid) throws XAException { try { if (_isXATransaction) _xaResource.forget(xid); } finally { clearXid(); } } /** * Vote using phase-1 of the 2-phase commit. */ @Override public int prepare(Xid xid) throws XAException { if (_endFlags != -1) { int endFlags = _endFlags; _endFlags = -1; if (_isXATransaction) endResource(xid, endFlags); } if (_isXATransaction) { try { if (log.isLoggable(Level.FINER)) log.finer("prepare-XA: " + xid + " " + _xaResource); int result = _xaResource.prepare(xid); if (result == XA_RDONLY) { if (_xaResource != null) _isXATransaction = true; clearXid(); } return result; } catch (XAException e) { if (log.isLoggable(Level.FINER)) log.finer("failed prepare-XA: " + xid + " " + _xaResource + " " + e); throw e; } } else return XA_OK; } /** * recover the transaction */ @Override public Xid[]recover(int flag) throws XAException { if (_isXATransaction) return _xaResource.recover(flag); else return null; } /** * Ends work with the resource. Called before commit/rollback. */ @Override public void end(Xid xid, int flags) throws XAException { _endFlags = flags; // XXX: In theory, drop the _xid. The underlying XADataSource // can handle combining the connections itself. // Don't call the underlying _xaResource.end. The commit or rollback // will handle that automatically. } /** * rollback the resource */ @Override public void rollback(Xid xid) throws XAException { try { int endFlags = _endFlags; _endFlags = -1; if (endFlags != -1 && _isXATransaction) { boolean isValid = false; try { endResource(xid, endFlags); isValid = true; } finally { if (! isValid) _xaResource.rollback(xid); } } if (log.isLoggable(Level.FINER)) log.finer("connection pool rollback XA: " + this); if (_isXATransaction) _xaResource.rollback(xid); else if (_localTransaction != null) { try { _isLocalTransaction = false; _localTransaction.rollback(); } catch (ResourceException e) { throw new XAExceptionWrapper(e); } } } finally { if (_xaResource != null) _isXATransaction = true; clearXid(); } } /** * commit the resource */ @Override public void commit(Xid xid, boolean onePhase) throws XAException { boolean logFiner = log.isLoggable(Level.FINER); try { int endFlags = _endFlags; _endFlags = -1; if (endFlags != -1 && _isXATransaction) { boolean isValid = false; try { endResource(xid, endFlags); isValid = true; } finally { if (! isValid) _xaResource.rollback(xid); } } if (_isXATransaction) { if (logFiner) { log.finer("commit-XA" + (onePhase ? "-1p: " : ": ") + xid + " " + _xaResource); } try { _xaResource.commit(xid, onePhase); } catch (XAException e) { if (logFiner) log.finer("commit-XA failed: " + _xaResource + " " + e); throw e; } } else if (_localTransaction != null) { if (logFiner) log.finer("commit-local: " + _localTransaction); try { _localTransaction.commit(); } catch (ResourceException e) { if (logFiner) log.finer("commit failed: " + _localTransaction + " " + e); throw new XAExceptionWrapper(e); } finally { _isLocalTransaction = false; } } else { if (logFiner) log.finer("commit for resource with no XA support: " + this); } } finally { if (_xaResource != null) { _isXATransaction = true; } clearXid(); } } /** * Ends the resource. */ private void endResource(Xid xid, int flags) throws XAException { ManagedPoolItem xaPtr = this; for (; xaPtr != null; xaPtr = xaPtr._xaNext) { if (xaPtr._xaResource != null) { xaPtr._xaResource.end(xid, flags); } } } /** * Restores the delegation for the entire chain. */ private void clearXid() { _xid = null; UserPoolItem shareHead = _shareHead; _shareHead = null; ManagedPoolItem xaPtr = _xaNext; _xaHead = null; _xaNext = null; UserPoolItem assignedUserItem = null; for (UserPoolItem ptr = shareHead; ptr != null; ptr = ptr.getShareNext()) { if (ptr.getOwnPoolItem() == this) { assignedUserItem = ptr; break; } } for (UserPoolItem ptr = shareHead; ptr != null; ptr = ptr.getShareNext()) { if (assignedUserItem == null && ptr.getOwnPoolItem() == null) { ptr.associatePoolItem(this); assignedUserItem = ptr; } try { ptr.reassociatePoolItem(); } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); } } while (xaPtr != null) { ManagedPoolItem next = xaPtr._xaNext; xaPtr._xaNext = null; xaPtr._xaHead = null; xaPtr.clearXid(); xaPtr = next; } if (assignedUserItem != null) { //_shareHead = assignedUserItem; //_shareHead.setShareNext(null); } else if (_hasConnectionError) { destroy(); } else { toIdle(); } } /** * Changes the state to idle. */ void toIdle() { if (_shareHead != null) return; else if (_xid != null || _isLocalTransaction) return; else if (_hasConnectionError) { destroy(); return; } /* UserTransactionImpl transaction = _transaction; _transaction = null; if (transaction != null) { try { transaction.delistXaResource(this, XAResource.TMSUCCESS); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } } */ _isLocalTransaction = false; if (log.isLoggable(Level.FINE)) log.fine("idle " + this); _poolEventTime = CurrentTime.getCurrentTime(); _cm.toIdle(this); } /** * Closes the connection. */ @Override public void abortConnection() { destroy(); } /** * Closes the connection. */ @Override public void destroy() { ManagedConnection mConn = _mConn; _mConn = null; // _transaction = null; if (mConn == null) return; if (!_isPastActiveTime) { } else if (ManagedConnectionImpl.class.isAssignableFrom(mConn.getClass())) { ((ManagedConnectionImpl) mConn).setPastActiveTime(_isPastActiveTime); } _cm.removeItem(this, mConn); UserPoolItem userItem = _shareHead; if (log.isLoggable(Level.FINE)) log.fine("connection pool destroy " + this); try { while (userItem != null) { UserPoolItem next = userItem.getShareNext(); userItem.close(); userItem = next; } /* * XXX: transaction.delistXaResource() */ } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } try { mConn.destroy(); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); } finally { _cm.getConnectionTimeProbe().end(_connectionStartTime); } } @Override public String toString() { if (_mConn != null) { return (getClass().getSimpleName() + "[" + _cm.getName() + "," + _id + "," + _mConn.getClass().getSimpleName() + "]"); } else { return (getClass().getSimpleName() + "[" + _cm.getName() + "," + _id + ",null]"); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy