/** * Recover abandoned objects which have been checked out but not used since longer than the * removeAbandonedTimeout. * * @param ac The configuration to use to identify abandoned objects */ private void removeAbandoned(AbandonedConfig ac) { // Generate a list of abandoned objects to remove final long now = System.currentTimeMillis(); final long timeout = now - (ac.getRemoveAbandonedTimeout() * 1000L); ArrayList<PooledObject<T>> remove = new ArrayList<>(); Iterator<PooledObject<T>> it = allObjects.values().iterator(); while (it.hasNext()) { PooledObject<T> pooledObject = it.next(); synchronized (pooledObject) { if (pooledObject.getState() == PooledObjectState.ALLOCATED && pooledObject.getLastUsedTime() <= timeout) { pooledObject.markAbandoned(); remove.add(pooledObject); } } } // Now remove the abandoned objects Iterator<PooledObject<T>> itr = remove.iterator(); while (itr.hasNext()) { PooledObject<T> pooledObject = itr.next(); if (ac.getLogAbandoned()) { pooledObject.printStackTrace(ac.getLogWriter()); } try { invalidateObject(pooledObject.getObject()); } catch (Exception e) { e.printStackTrace(); } } }
/** * Attempts to create a new wrapped pooled object. * * <p>If there are {@link #getMaxTotal()} objects already in circulation or in process of being * created, this method returns null. * * @return The new wrapped pooled object * @throws Exception if the object factory's {@code makeObject} fails */ private PooledObject<T> create() throws Exception { int localMaxTotal = getMaxTotal(); long newCreateCount = createCount.incrementAndGet(); if (localMaxTotal > -1 && newCreateCount > localMaxTotal || newCreateCount > Integer.MAX_VALUE) { createCount.decrementAndGet(); return null; } final PooledObject<T> p; try { p = factory.makeObject(); } catch (Exception e) { createCount.decrementAndGet(); throw e; } AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getLogAbandoned()) { p.setLogAbandoned(true); } createdCount.incrementAndGet(); allObjects.put(new IdentityWrapper<>(p.getObject()), p); return p; }
@Override public void use(T pooledObject) { AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getUseUsageTracking()) { PooledObject<T> wrapper = allObjects.get(new IdentityWrapper<>(pooledObject)); wrapper.use(); } }
/** * Sets the abandoned object removal configuration. * * @param abandonedConfig the new configuration to use. This is used by value. * @see AbandonedConfig */ public void setAbandonedConfig(AbandonedConfig abandonedConfig) { if (abandonedConfig == null) { this.abandonedConfig = null; } else { this.abandonedConfig = new AbandonedConfig(); this.abandonedConfig.setLogAbandoned(abandonedConfig.getLogAbandoned()); this.abandonedConfig.setLogWriter(abandonedConfig.getLogWriter()); this.abandonedConfig.setRemoveAbandonedOnBorrow(abandonedConfig.getRemoveAbandonedOnBorrow()); this.abandonedConfig.setRemoveAbandonedOnMaintenance( abandonedConfig.getRemoveAbandonedOnMaintenance()); this.abandonedConfig.setRemoveAbandonedTimeout(abandonedConfig.getRemoveAbandonedTimeout()); this.abandonedConfig.setUseUsageTracking(abandonedConfig.getUseUsageTracking()); } }
/** * {@inheritDoc} * * <p>Successive activations of this method examine objects in sequence, cycling through objects * in oldest-to-youngest order. */ @Override public void evict() throws Exception { assertOpen(); if (idleObjects.size() > 0) { PooledObject<T> underTest = null; EvictionPolicy<T> evictionPolicy = getEvictionPolicy(); synchronized (evictionLock) { EvictionConfig evictionConfig = new EvictionConfig( getMinEvictableIdleTimeMillis(), getSoftMinEvictableIdleTimeMillis(), getMinIdle()); boolean testWhileIdle = getTestWhileIdle(); for (int i = 0, m = getNumTests(); i < m; i++) { if (evictionIterator == null || !evictionIterator.hasNext()) { evictionIterator = new EvictionIterator(idleObjects); } if (!evictionIterator.hasNext()) { // Pool exhausted, nothing to do here return; } try { underTest = evictionIterator.next(); } catch (NoSuchElementException nsee) { // Object was borrowed in another thread // Don't count this as an eviction test so reduce i; i--; evictionIterator = null; continue; } if (!underTest.startEvictionTest()) { // Object was borrowed in another thread // Don't count this as an eviction test so reduce i; i--; continue; } // User provided eviction policy could throw all sorts of crazy // exceptions. Protect against such an exception killing the // eviction thread. boolean evict; try { evict = evictionPolicy.evict(evictionConfig, underTest, idleObjects.size()); } catch (Throwable t) { // Slightly convoluted as SwallowedExceptionListener uses // Exception rather than Throwable PoolUtils.checkRethrow(t); swallowException(new Exception(t)); // Don't evict on error conditions evict = false; } if (evict) { destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } else { if (testWhileIdle) { boolean active = false; try { factory.activateObject(underTest); active = true; } catch (Exception e) { destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } if (active) { if (!factory.validateObject(underTest)) { destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } else { try { factory.passivateObject(underTest); } catch (Exception e) { destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } } } } if (!underTest.endEvictionTest(idleObjects)) { // TODO - May need to add code here once additional // states are used } } } } } AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getRemoveAbandonedOnMaintenance()) { removeAbandoned(ac); } }
/** * Borrow an object from the pool using the specific waiting time which only applies if {@link * #getBlockWhenExhausted()} is true. * * <p>If there is one or more idle instance available in the pool, then an idle instance will be * selected based on the value of {@link #getLifo()}, activated and returned. If activation fails, * or {@link #getTestOnBorrow() testOnBorrow} is set to <code>true</code> and validation fails, * the instance is destroyed and the next available instance is examined. This continues until * either a valid instance is returned or there are no more idle instances available. * * <p>If there are no idle instances available in the pool, behavior depends on the {@link * #getMaxTotal() maxTotal}, (if applicable) {@link #getBlockWhenExhausted()} and the value passed * in to the <code>borrowMaxWaitMillis</code> parameter. If the number of instances checked out * from the pool is less than <code>maxTotal,</code> a new instance is created, activated and (if * applicable) validated and returned to the caller. If validation fails, a <code> * NoSuchElementException</code> is thrown. * * <p>If the pool is exhausted (no available idle instances and no capacity to create new ones), * this method will either block (if {@link #getBlockWhenExhausted()} is true) or throw a <code> * NoSuchElementException</code> (if {@link #getBlockWhenExhausted()} is false). The length of * time that this method will block when {@link #getBlockWhenExhausted()} is true is determined by * the value passed in to the <code>borrowMaxWaitMillis</code> parameter. * * <p>When the pool is exhausted, multiple calling threads may be simultaneously blocked waiting * for instances to become available. A "fairness" algorithm has been implemented to ensure that * threads receive available instances in request arrival order. * * @param borrowMaxWaitMillis The time to wait in milliseconds for an object to become available * @return object instance from the pool * @throws NoSuchElementException if an instance cannot be returned * @throws Exception if an object instance cannot be returned due to an error */ public T borrowObject(long borrowMaxWaitMillis) throws Exception { assertOpen(); AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) && (getNumActive() > getMaxTotal() - 3)) { removeAbandoned(ac); } PooledObject<T> p = null; // Get local copy of current config so it is consistent for entire // method execution boolean blockWhenExhausted = getBlockWhenExhausted(); boolean create; long waitTime = System.currentTimeMillis(); while (p == null) { create = false; if (blockWhenExhausted) { p = idleObjects.pollFirst(); if (p == null) { p = create(); if (p != null) { create = true; } } if (p == null) { if (borrowMaxWaitMillis < 0) { p = idleObjects.takeFirst(); } else { p = idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS); } } if (p == null) { throw new NoSuchElementException("Timeout waiting for idle object"); } if (!p.allocate()) { p = null; } } else { p = idleObjects.pollFirst(); if (p == null) { p = create(); if (p != null) { create = true; } } if (p == null) { throw new NoSuchElementException("Pool exhausted"); } if (!p.allocate()) { p = null; } } if (p != null) { try { factory.activateObject(p); } catch (Exception e) { try { destroy(p); } catch (Exception e1) { // Ignore - activation failure is more important } p = null; if (create) { NoSuchElementException nsee = new NoSuchElementException("Unable to activate object"); nsee.initCause(e); throw nsee; } } if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) { boolean validate = false; Throwable validationThrowable = null; try { validate = factory.validateObject(p); } catch (Throwable t) { PoolUtils.checkRethrow(t); validationThrowable = t; } if (!validate) { try { destroy(p); destroyedByBorrowValidationCount.incrementAndGet(); } catch (Exception e) { // Ignore - validation failure is more important } p = null; if (create) { NoSuchElementException nsee = new NoSuchElementException("Unable to validate object"); nsee.initCause(validationThrowable); throw nsee; } } } } } updateStatsBorrow(p, System.currentTimeMillis() - waitTime); return p.getObject(); }
/** * Obtain the timeout before which an object will be considered to be abandoned by this pool. * * @return The abandoned object timeout in seconds if abandoned object removal is configured for * this pool; Integer.MAX_VALUE otherwise. * @see AbandonedConfig#getRemoveAbandonedTimeout() */ @Override public int getRemoveAbandonedTimeout() { AbandonedConfig ac = this.abandonedConfig; return ac != null ? ac.getRemoveAbandonedTimeout() : Integer.MAX_VALUE; }
/** * Will a check be made for abandoned objects when the evictor runs? * * @return {@code true} if abandoned object removal is configured to be activated when the evictor * runs otherwise {@code false} * @see AbandonedConfig#getRemoveAbandonedOnMaintenance() */ @Override public boolean getRemoveAbandonedOnMaintenance() { AbandonedConfig ac = this.abandonedConfig; return ac != null && ac.getRemoveAbandonedOnMaintenance(); }
/** * Will this pool identify and log any abandoned objects? * * @return {@code true} if abandoned object removal is configured for this pool and removal events * are to be logged otherwise {@code false} * @see AbandonedConfig#getLogAbandoned() */ @Override public boolean getLogAbandoned() { AbandonedConfig ac = this.abandonedConfig; return ac != null && ac.getLogAbandoned(); }