| /* |
| * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0, which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the |
| * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| * version 2 with the GNU Classpath Exception, which is available at |
| * https://www.gnu.org/software/classpath/license.html. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| */ |
| |
| package com.sun.enterprise.resource.pool.resizer; |
| |
| import com.sun.appserv.connectors.internal.api.PoolingException; |
| import com.sun.enterprise.resource.ResourceHandle; |
| import com.sun.enterprise.resource.ResourceState; |
| import com.sun.enterprise.resource.allocator.ResourceAllocator; |
| import com.sun.enterprise.resource.pool.PoolProperties; |
| import com.sun.enterprise.resource.pool.ResourceHandler; |
| import com.sun.enterprise.resource.pool.datastructure.DataStructure; |
| import com.sun.logging.LogDomains; |
| import org.glassfish.resourcebase.resources.api.PoolInfo; |
| |
| import jakarta.resource.ResourceException; |
| import jakarta.resource.spi.ManagedConnection; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.TimerTask; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * Resizer to remove unusable connections, maintain steady-pool <br> |
| * <code> |
| * Remove all invalid and idle resources, as a result one of the following may happen<br> |
| * i) equivalent to "pool-resize" quantity of resources are removed<br> |
| * ii) less than "pool-reize" quantity of resources are removed<br> |
| * remove more resources to match pool-resize quantity, atmost scale-down till steady-pool-size<br> |
| * iii) more than "pool-resize" quantity of resources are removed<br> |
| * (1) if pool-size is less than steady-pool-size, bring it back to steady-pool-size.<br> |
| * (2) if pool-size is greater than steady-pool-size, don't do anything.<br></code> |
| * |
| * @author Jagadish Ramu |
| */ |
| public class |
| Resizer extends TimerTask { |
| protected PoolInfo poolInfo; |
| protected DataStructure ds; |
| protected PoolProperties pool; |
| protected ResourceHandler handler; |
| protected boolean preferValidateOverRecreate = false; |
| |
| protected final static Logger _logger = LogDomains.getLogger(Resizer.class, LogDomains.RSR_LOGGER); |
| |
| public Resizer(PoolInfo poolInfo, DataStructure ds, PoolProperties pp, ResourceHandler handler, |
| boolean preferValidateOverRecreate) { |
| this.poolInfo = poolInfo; |
| this.ds = ds; |
| this.pool = pp; |
| this.handler = handler; |
| this.preferValidateOverRecreate = preferValidateOverRecreate; |
| } |
| |
| public void run() { |
| debug("Resizer for pool " + poolInfo); |
| try { |
| resizePool(true); |
| } catch(Exception ex) { |
| Object[] params = new Object[]{poolInfo, ex.getMessage()}; |
| _logger.log(Level.WARNING, "resource_pool.resize_pool_error", params); |
| } |
| } |
| |
| /** |
| * Resize the pool |
| * |
| * @param forced when force is true, scale down the pool. |
| */ |
| public void resizePool(boolean forced) { |
| |
| //If the wait queue is NOT empty, don't do anything. |
| if (pool.getWaitQueueLength() > 0) { |
| return; |
| } |
| |
| //remove invalid and idle resource(s) |
| int noOfResourcesRemoved = removeIdleAndInvalidResources(); |
| int poolScaleDownQuantity = pool.getResizeQuantity() - noOfResourcesRemoved; |
| |
| //scale down pool by atmost "resize-quantity" |
| scaleDownPool(poolScaleDownQuantity, forced); |
| |
| //ensure that steady-pool-size is maintained |
| ensureSteadyPool(); |
| |
| debug("No. of resources held for pool [ " + poolInfo + " ] : " + ds.getResourcesSize()); |
| } |
| |
| /** |
| * Make sure that steady pool size is maintained after all idle-timed-out, |
| * invalid and scale-down resource removals. |
| */ |
| private void ensureSteadyPool() { |
| if (ds.getResourcesSize() < pool.getSteadyPoolSize()) { |
| // Create resources to match the steady pool size |
| for (int i = ds.getResourcesSize(); i < pool.getSteadyPoolSize(); i++) { |
| try { |
| handler.createResourceAndAddToPool(); |
| } catch (PoolingException ex) { |
| Object[] params = new Object[]{poolInfo, ex.getMessage()}; |
| _logger.log(Level.WARNING, "resource_pool.resize_pool_error", params); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Scale down pool by a <code>size <= pool-resize-quantity</code> |
| * |
| * @param forced scale-down only when forced |
| * @param scaleDownQuantity no. of resources to remove |
| */ |
| protected void scaleDownPool(int scaleDownQuantity, boolean forced) { |
| |
| if (pool.getResizeQuantity() > 0 && forced) { |
| |
| scaleDownQuantity = (scaleDownQuantity <= (ds.getResourcesSize() - pool.getSteadyPoolSize())) ? scaleDownQuantity : 0; |
| |
| ResourceHandle h; |
| while (scaleDownQuantity > 0 && ((h = ds.getResource()) != null)) { |
| ds.removeResource(h); |
| scaleDownQuantity--; |
| } |
| } |
| } |
| |
| /** |
| * Get the free connections list from the pool, remove idle-timed-out resources |
| * and then invalid resources. |
| * |
| * @return int number of resources removed |
| */ |
| protected int removeIdleAndInvalidResources() { |
| |
| int poolSizeBeforeRemoval = ds.getResourcesSize(); |
| int noOfResourcesRemoved; |
| //Find all Connections that are free/not-in-use |
| ResourceState state; |
| int size = ds.getFreeListSize(); |
| // let's cache the current time since precision is not required here. |
| long currentTime = System.currentTimeMillis(); |
| int validConnectionsCounter = 0; |
| int idleConnKeptInSteadyCounter = 0; |
| |
| //iterate through all thre active resources to find idle-time lapsed ones. |
| ResourceHandle h; |
| Set<ResourceHandle> activeResources = new HashSet<ResourceHandle>(); |
| Set<String> resourcesToValidate = new HashSet<String>(); |
| try { |
| while ((h = ds.getResource()) != null ) { |
| state = h.getResourceState(); |
| if (currentTime - state.getTimestamp() < pool.getIdleTimeout()) { |
| //Should be added for validation. |
| validConnectionsCounter++; |
| resourcesToValidate.add(h.toString()); |
| activeResources.add(h); |
| } else { |
| boolean isResourceEligibleForRemoval = |
| isResourceEligibleForRemoval(h, validConnectionsCounter); |
| if(!isResourceEligibleForRemoval) { |
| //preferValidateOverrecreate true and connection is valid within SPS |
| validConnectionsCounter++; |
| idleConnKeptInSteadyCounter++; |
| activeResources.add(h); |
| debug("PreferValidateOverRecreate: Keeping idle resource " |
| + h + " in the steady part of the free pool " |
| + "as the RA reports it to be valid (" + validConnectionsCounter |
| + " <= " + pool.getSteadyPoolSize() + ")"); |
| |
| } else { |
| //Add to remove |
| ds.removeResource(h); |
| } |
| } |
| } |
| } finally { |
| for(ResourceHandle activeResource : activeResources) { |
| ds.returnResource(activeResource); |
| } |
| } |
| |
| //remove invalid resources from the free (active) resources list. |
| //Since the whole pool is not locked, it may happen that some of these resources may be |
| //given to applications. |
| removeInvalidResources(resourcesToValidate); |
| |
| //These statistic computations will work fine as long as resizer locks the pool throughout its operations. |
| if (preferValidateOverRecreate) { |
| debug("Idle resources validated and kept in the steady pool for pool [ " + |
| poolInfo + " ] - " + idleConnKeptInSteadyCounter); |
| debug("Number of Idle resources freed for pool [ " + poolInfo + " ] - " + |
| (size - activeResources.size() - idleConnKeptInSteadyCounter)); |
| debug("Number of Invalid resources removed for pool [ " + poolInfo + " ] - " + |
| (activeResources.size() - ds.getFreeListSize() + idleConnKeptInSteadyCounter)); |
| } else { |
| debug("Number of Idle resources freed for pool [ " + poolInfo + " ] - " + |
| (size - activeResources.size())); |
| debug("Number of Invalid resources removed for pool [ " + poolInfo + " ] - " + |
| (activeResources.size() - ds.getFreeListSize())); |
| } |
| noOfResourcesRemoved = poolSizeBeforeRemoval - ds.getResourcesSize(); |
| return noOfResourcesRemoved; |
| } |
| |
| /** |
| * Removes invalid resource handles in the pool while resizing the pool. |
| * Uses the Connector 1.5 spec 6.5.3.4 optional RA feature to obtain |
| * invalid ManagedConnections |
| * |
| * @param freeConnectionsToValidate Set of free connections |
| */ |
| private void removeInvalidResources(Set<String> freeConnectionsToValidate) { |
| try { |
| debug("Sending a set of free connections to RA, " + |
| "of size : " + freeConnectionsToValidate.size()); |
| int invalidConnectionsCount = 0; |
| ResourceHandle handle; |
| Set<ResourceHandle> validResources = new HashSet<ResourceHandle>(); |
| try { |
| while ((handle = ds.getResource()) != null ) { |
| //validate if the connection is one in the freeConnectionsToValidate |
| if (freeConnectionsToValidate.contains(handle.toString())) { |
| Set connectionsToTest = new HashSet(); |
| connectionsToTest.add(handle.getResource()); |
| Set invalidConnections = handler.getInvalidConnections(connectionsToTest); |
| if (invalidConnections != null && invalidConnections.size() > 0) { |
| invalidConnectionsCount = validateAndRemoveResource(handle, invalidConnections); |
| } else { |
| //valid resource, return to pool |
| validResources.add(handle); |
| } |
| } else { |
| //valid resource, return to pool |
| validResources.add(handle); |
| } |
| } |
| } finally { |
| for(ResourceHandle resourceHandle : validResources){ |
| ds.returnResource(resourceHandle); |
| } |
| validResources.clear(); |
| debug("No. of invalid connections received from RA : " + invalidConnectionsCount); |
| } |
| } catch (ResourceException re) { |
| if(_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "ResourceException while trying to get invalid connections from MCF", re); |
| } |
| } catch (Exception e) { |
| if(_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Exception while trying to get invalid connections from MCF", e); |
| } |
| } |
| } |
| |
| |
| protected static void debug(String debugStatement) { |
| if (_logger.isLoggable(Level.FINE)) |
| _logger.log(Level.FINE, debugStatement); |
| } |
| |
| protected int validateAndRemoveResource(ResourceHandle handle, Set invalidConnections) { |
| int invalidConnectionsCount = 0; |
| for (Object o : invalidConnections) { |
| ManagedConnection invalidConnection = (ManagedConnection) o; |
| if (invalidConnection.equals(handle.getResource())) { |
| ds.removeResource(handle); |
| handler.invalidConnectionDetected(handle); |
| invalidConnectionsCount++; |
| } |
| } |
| return invalidConnectionsCount; |
| } |
| |
| protected boolean isResourceEligibleForRemoval(ResourceHandle h, |
| int validConnectionsCounter) { |
| boolean isResourceEligibleForRemoval = false; |
| |
| ResourceState state = h.getResourceState(); |
| //remove all idle-time lapsed resources. |
| ResourceAllocator alloc = h.getResourceAllocator(); |
| if (preferValidateOverRecreate && alloc.hasValidatingMCF()) { |
| //validConnectionsCounter is incremented if the connection |
| //is valid but only till the steady pool size. |
| if (validConnectionsCounter < pool.getSteadyPoolSize() |
| && alloc.isConnectionValid(h)) { |
| |
| h.setLastValidated(System.currentTimeMillis()); |
| state.touchTimestamp(); |
| } else { |
| //Connection invalid and hence remove resource. |
| if (_logger.isLoggable(Level.FINEST)) { |
| if (validConnectionsCounter <= pool.getSteadyPoolSize()) { |
| _logger.log(Level.FINEST, "PreferValidateOverRecreate: " |
| + "Removing idle resource " + h |
| + " from the free pool as the RA reports it to be invalid"); |
| } else { |
| _logger.log(Level.FINEST, "PreferValidateOverRecreate: " |
| + "Removing idle resource " + h |
| + " from the free pool as the steady part size has " |
| + "already been exceeded (" + validConnectionsCounter + " > " |
| + pool.getSteadyPoolSize() + ")"); |
| } |
| } |
| isResourceEligibleForRemoval = true; |
| } |
| } else { |
| isResourceEligibleForRemoval = true; |
| } |
| return isResourceEligibleForRemoval; |
| } |
| } |