| /* |
| * Copyright (c) 1998, 2021 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, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| package org.eclipse.persistence.sessions.server; |
| |
| import java.util.*; |
| |
| import org.eclipse.persistence.internal.databaseaccess.*; |
| import org.eclipse.persistence.sessions.Login; |
| import org.eclipse.persistence.internal.helper.*; |
| import org.eclipse.persistence.exceptions.*; |
| import org.eclipse.persistence.internal.localization.*; |
| import org.eclipse.persistence.logging.SessionLog; |
| |
| /** |
| * <p> |
| * <b>Purpose</b>: Used to specify how connection should be pooled in a server session. |
| * @see ServerSession |
| */ |
| public class ConnectionPool { |
| protected static final String MONITOR_HEADER = "Info:ConnectionPool:"; |
| |
| protected boolean isConnected; |
| protected int maxNumberOfConnections; |
| protected int minNumberOfConnections; |
| protected int initialNumberOfConnections; |
| protected int waitTimeout; |
| protected List<Accessor> connectionsAvailable; |
| protected List<Accessor> connectionsUsed; |
| protected Login login; |
| protected String name; |
| protected ServerSession owner; |
| protected volatile boolean checkConnections; |
| protected volatile long timeOfDeath; |
| protected volatile long deadCheckTime; |
| protected volatile boolean isDead; |
| protected List<String> failoverConnectionPools; |
| |
| public static final long DEAD_CHECK_TIME = 1000 * 60 * 10; // 10 minutes. |
| public static final int MAX_CONNECTIONS = 32; |
| public static final int MIN_CONNECTIONS = 32; |
| public static final int INITIAL_CONNECTIONS = 1; |
| public static final int WAIT_TIMEOUT = 180000; // 3 minutes. |
| |
| /** |
| * PUBLIC: |
| * A connection pool is used to specify how connection should be pooled in a server session. |
| */ |
| public ConnectionPool() { |
| this(null, null, null); |
| } |
| |
| /** |
| * PUBLIC: |
| * A connection pool is used to specify how connection should be pooled in a server session. |
| */ |
| public ConnectionPool(String name, Login login, ServerSession owner) { |
| this(name, login, INITIAL_CONNECTIONS, MIN_CONNECTIONS, MAX_CONNECTIONS, owner); |
| } |
| |
| /** |
| * PUBLIC: |
| * A connection pool is used to specify how connection should be pooled in a server session. |
| */ |
| public ConnectionPool(String name, Login login, int minNumberOfConnections, int maxNumberOfConnections, ServerSession owner) { |
| this(name, login, Math.min(INITIAL_CONNECTIONS, minNumberOfConnections), minNumberOfConnections, maxNumberOfConnections, owner); |
| } |
| |
| /** |
| * PUBLIC: |
| * A connection pool is used to specify how connection should be pooled in a server session. |
| */ |
| public ConnectionPool(String name, Login login, int initialNumberOfConnections, int minNumberOfConnections, int maxNumberOfConnections, ServerSession owner) { |
| this.login = login; |
| this.owner = owner; |
| this.name = name; |
| this.maxNumberOfConnections = maxNumberOfConnections; |
| this.minNumberOfConnections = minNumberOfConnections; |
| this.initialNumberOfConnections = initialNumberOfConnections; |
| this.deadCheckTime = DEAD_CHECK_TIME; |
| this.waitTimeout = WAIT_TIMEOUT; |
| this.checkConnections = false; |
| this.failoverConnectionPools = new ArrayList<>(); |
| resetConnections(); |
| } |
| |
| /** |
| * INTERNAL: |
| * The connection pool is dead fail over to the fail-over pool. |
| */ |
| public Accessor failover() { |
| if ((this.timeOfDeath + this.deadCheckTime) < System.currentTimeMillis()) { |
| // Retry database to see if it is back up. |
| this.isDead = false; |
| return acquireConnection(); |
| } else { |
| for (String poolName : this.failoverConnectionPools) { |
| ConnectionPool pool = this.owner.getConnectionPool(poolName); |
| if (!pool.isDead()) { |
| if (this.owner.shouldLog(SessionLog.FINEST, SessionLog.CONNECTION)) { |
| Object[] args = new Object[2]; |
| args[0] = this.name; |
| args[1] = poolName; |
| this.owner.log(SessionLog.FINEST, SessionLog.CONNECTION, "failover", args); |
| } |
| return pool.acquireConnection(); |
| } |
| } |
| throw QueryException.failoverFailed(this.name); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Wait until a connection is available and allocate the connection for the client. |
| */ |
| public synchronized Accessor acquireConnection() throws ConcurrencyException { |
| // Check for dead database and fail-over. |
| if (this.isDead) { |
| return failover(); |
| } |
| // PERF: Using direct variable access to minimize concurrency bottleneck. |
| while (this.connectionsAvailable.isEmpty()) { |
| if ((this.connectionsUsed.size() + this.connectionsAvailable.size()) < this.maxNumberOfConnections) { |
| Accessor connection = null; |
| try { |
| connection = buildConnection(); |
| } catch (RuntimeException failed) { |
| if (!this.failoverConnectionPools.isEmpty()) { |
| this.isDead = true; |
| this.timeOfDeath = System.currentTimeMillis(); |
| this.owner.logThrowable(SessionLog.WARNING, SessionLog.SQL, failed); |
| return acquireConnection(); |
| } else { |
| throw failed; |
| } |
| } |
| this.connectionsUsed.add(connection); |
| if (this.owner.isInProfile()) { |
| this.owner.updateProfile(MONITOR_HEADER + this.name, this.connectionsUsed.size()); |
| } |
| if (this.owner.shouldLog(SessionLog.FINEST, SessionLog.CONNECTION)) { |
| Object[] args = new Object[1]; |
| args[0] = this.name; |
| this.owner.log(SessionLog.FINEST, SessionLog.CONNECTION, "acquire_connection", args, connection); |
| } |
| return connection; |
| } |
| try { |
| wait(this.waitTimeout);// Notify is called when connections are released. |
| } catch (InterruptedException exception) { |
| throw ConcurrencyException.waitFailureOnClientSession(exception); |
| } |
| } |
| |
| int connectionSize = this.connectionsAvailable.size(); |
| // Always used the last connection to avoid shift list and to use "hot" connection. |
| Accessor connection = this.connectionsAvailable.remove(connectionSize-1); |
| if (this.checkConnections) { |
| // EclipseLink has encountered a problem with a connection where the database no longer responded |
| // We need to now ensure that the failure was specific to that connection or we need to empty |
| // the pool of dead connections in the case of a database failover. |
| while (connectionSize >= 0) { |
| if (this.owner.getLogin().isConnectionHealthValidatedOnError() && this.owner.getServerPlatform().wasFailureCommunicationBased(null, connection, this.owner)) { |
| try { |
| //connection failed connect test |
| connection.closeConnection(); |
| } catch (Exception ex){ |
| //ignore |
| } finally { |
| connection.releaseCustomizer(); |
| } |
| if (this.connectionsAvailable.isEmpty()) { |
| this.checkConnections = false; |
| //we have emptied out all connections so let's have the connection pool build more |
| return acquireConnection(); |
| } else { |
| //test next connection |
| --connectionSize; |
| connection = this.connectionsAvailable.remove(connectionSize-1); |
| } |
| } else { |
| //connection was good use it. And make sure we stop testing connections |
| this.checkConnections = false; |
| break; |
| } |
| } |
| } |
| this.connectionsUsed.add(connection); |
| if (this.owner.isInProfile()) { |
| this.owner.updateProfile(MONITOR_HEADER + this.name, this.connectionsUsed.size()); |
| } |
| if (this.owner.shouldLog(SessionLog.FINEST, SessionLog.CONNECTION)) { |
| Object[] args = new Object[1]; |
| args[0] = this.name; |
| this.owner.log(SessionLog.FINEST, SessionLog.CONNECTION, "acquire_connection", args, connection); |
| } |
| return connection; |
| } |
| |
| /** |
| * INTERNAL: |
| * Create a new connection, accessors are used as connections. |
| */ |
| protected Accessor buildConnection() { |
| Accessor connection = this.login.buildAccessor(); |
| connection.setPool(this); |
| connection.connect(this.login, this.owner); |
| |
| return connection; |
| } |
| |
| /** |
| * INTERNAL: |
| * returns the connections currently available for use in the pool |
| */ |
| public List<Accessor> getConnectionsAvailable() { |
| return connectionsAvailable; |
| } |
| |
| /** |
| * Return a list of the connections that are being used. |
| * @return java.util.Vector |
| **/ |
| protected List<Accessor> getConnectionsUsed() { |
| return connectionsUsed; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the login used to create connections. |
| */ |
| public Login getLogin() { |
| return login; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the maximum number of connections allowed. |
| * If all connections are in use, a new connection will be created until the maximum size is reach. |
| * Only the minimum number of connections will be pooled, if the pool is between the min and max size |
| * the connection will be disconnected when returned to the pool. |
| * Typically it is desirable to have the min and max connections the same to avoid connects and disconnects. |
| * When the max is reached clients must wait for a connection to become available. |
| */ |
| public int getMaxNumberOfConnections() { |
| return maxNumberOfConnections; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the minimum number of connections. |
| * If all connections are in use, a new connection will be created until the maximum size is reach. |
| * Only the minimum number of connections will be pooled, if the pool is between the min and max size |
| * the connection will be disconnected when returned to the pool. |
| * Typically it is desirable to have the min and max connections the same to avoid connects and disconnects. |
| */ |
| public int getMinNumberOfConnections() { |
| return minNumberOfConnections; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the name of this pool. |
| * Pools are identified by name to allow multiple connection pools. |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Return the ServerSession that is the owner of this connection pool. |
| * @return org.eclipse.persistence.sessions.server.ServerSession |
| */ |
| protected ServerSession getOwner() { |
| return owner; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the total number of connections currently in use. |
| */ |
| public int getTotalNumberOfConnections() { |
| return getConnectionsUsed().size() + getConnectionsAvailable().size(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Wait until a connection is avaiable and allocate the connection for the client. |
| */ |
| public boolean hasConnectionAvailable() { |
| return !getConnectionsAvailable().isEmpty(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if this pool has been connected to the database. |
| */ |
| public boolean isConnected() { |
| return isConnected; |
| } |
| |
| /** |
| * INTERNAL: |
| * Checks for a conflict between pool's type and pool's login |
| */ |
| public boolean isThereConflictBetweenLoginAndType() { |
| return getLogin().shouldUseExternalConnectionPooling(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the connection as single that a new connection is available. |
| */ |
| public synchronized void releaseConnection(Accessor connection) throws DatabaseException { |
| if (this.owner.shouldLog(SessionLog.FINEST, SessionLog.CONNECTION)) { |
| Object[] args = new Object[1]; |
| args[0] = this.name; |
| this.owner.log(SessionLog.FINEST, SessionLog.CONNECTION, "release_connection", args, connection); |
| } |
| connection.reset(); |
| |
| this.connectionsUsed.remove(connection); |
| |
| if (!connection.isValid()) { |
| this.checkConnections = true; |
| try { |
| connection.disconnect(this.owner); |
| } catch (DatabaseException ex) { |
| //this is an invalid connection so expect an exception. |
| } |
| } else { |
| if ((this.connectionsUsed.size() + this.connectionsAvailable.size()) < this.minNumberOfConnections) { |
| this.connectionsAvailable.add(connection); |
| } else { |
| connection.disconnect(getOwner()); |
| } |
| } |
| if (this.owner.isInProfile()) { |
| this.owner.updateProfile(MONITOR_HEADER + this.name, this.connectionsUsed.size()); |
| } |
| notify(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Reset the connections on shutDown and when the pool is started. |
| */ |
| public void resetConnections() { |
| this.connectionsUsed = new Vector(); |
| this.connectionsAvailable = new Vector(); |
| this.checkConnections = false; |
| this.isDead = false; |
| this.timeOfDeath = 0; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is called to indicate that all available connections should be checked. |
| */ |
| public void setCheckConnections() { |
| this.checkConnections = true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set this list of connections available |
| * @param connectionsAvailable |
| */ |
| protected void setConnectionsAvailable(Vector connectionsAvailable) { |
| this.connectionsAvailable = connectionsAvailable; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the list of connections being used. |
| * @param connectionsUsed |
| */ |
| protected void setConnectionsUsed(Vector connectionsUsed) { |
| this.connectionsUsed = connectionsUsed; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set if this pool has been connected to the database. |
| */ |
| public void setIsConnected(boolean isConnected) { |
| this.isConnected = isConnected; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the login used to create connections. |
| */ |
| public void setLogin(Login login) { |
| this.login = login; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the initial number of connections allowed. |
| * This is the number of connections connected on startup. |
| */ |
| public int getInitialNumberOfConnections() { |
| return initialNumberOfConnections; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the initial number of connections allowed. |
| * This is the number of connections connected on startup. |
| * The default is 1. |
| */ |
| public void setInitialNumberOfConnections(int initialNumberOfConnections) { |
| this.initialNumberOfConnections = initialNumberOfConnections; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the maximum number of connections allowed. |
| * If all connections are in use, a new connection will be created until the maximum size is reach. |
| * Only the minimum number of connections will be pooled, if the pool is between the min and max size |
| * the connection will be disconnected when returned to the pool. |
| * Typically it is desirable to have the min and max connections the same to avoid connects and disconnects. |
| * When the max is reached clients must wait for a connection to become available. |
| * The default is 32. |
| */ |
| public void setMaxNumberOfConnections(int maxNumberOfConnections) { |
| this.maxNumberOfConnections = maxNumberOfConnections; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the minimum number of connections. |
| * If all connections are in use, a new connection will be created until the maximum size is reach. |
| * Only the minimum number of connections will be pooled, if the pool is between the min and max size |
| * the connection will be disconnected when returned to the pool. |
| * Typically it is desirable to have the min and max connections the same to avoid connects and disconnects. |
| * The default is 32. |
| */ |
| public void setMinNumberOfConnections(int minNumberOfConnections) { |
| this.minNumberOfConnections = minNumberOfConnections; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the name of this pool. |
| * Pools are identified by name to allow multiple connection pools. |
| */ |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| /** |
| * Set the ServerSession that owns this connection pool |
| * @param owner |
| */ |
| protected void setOwner(ServerSession owner) { |
| this.owner = owner; |
| } |
| |
| /** |
| * INTERNAL: |
| * Disconnect all connections. |
| */ |
| public synchronized void shutDown() { |
| setIsConnected(false); |
| |
| for (Iterator iterator = getConnectionsAvailable().iterator(); iterator.hasNext();) { |
| try { |
| ((Accessor)iterator.next()).disconnect(getOwner()); |
| } catch (DatabaseException exception) { |
| // Ignore. |
| } |
| } |
| |
| for (Iterator iterator = getConnectionsUsed().iterator(); iterator.hasNext();) { |
| try { |
| ((Accessor)iterator.next()).disconnect(getOwner()); |
| } catch (DatabaseException exception) { |
| // Ignore. |
| } |
| } |
| resetConnections(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Allocate the minimum connections. |
| */ |
| public synchronized void startUp() { |
| if (isConnected()) { |
| return; |
| } |
| for (int index = getInitialNumberOfConnections(); index > 0; index--) { |
| getConnectionsAvailable().add(buildConnection()); |
| } |
| |
| setIsConnected(true); |
| } |
| |
| /** |
| * INTERNAL: |
| * return a string representation of this connection pool |
| */ |
| @Override |
| public String toString() { |
| Object[] args = {getMinNumberOfConnections(), getMaxNumberOfConnections()}; |
| return Helper.getShortClassName(getClass()) + ToStringLocalization.buildMessage("min_max", args); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the time in milliseconds to wait for a available connection. |
| * If the wait time is exceeded and exception will occur. |
| * The default is 180000 or 3 minutes. |
| * A value of 0 means wait forever. |
| */ |
| public int getWaitTimeout() { |
| return waitTimeout; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the time in milliseconds to wait for an available connection. |
| * If the wait time is exceeded an exception will occur. |
| * The default is 180000 or 3 minutes. |
| * A value of 0 means wait forever. |
| */ |
| public void setWaitTimeout(int waitTimeout) { |
| this.waitTimeout = waitTimeout; |
| } |
| |
| /** |
| * ADVANCED: |
| * Return if the connection pool's database is down, and failover should be used. |
| */ |
| public boolean isDead() { |
| return isDead; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set if the connection pool's database is down, and failover should be used. |
| */ |
| public void setIsDead(boolean isDead) { |
| this.isDead = isDead; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the list of connection pools to used if this pool database goes down. |
| * The failover pools should be a clustered, replicated or backuped database. |
| */ |
| public List<String> getFailoverConnectionPools() { |
| return failoverConnectionPools; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the list of connection pools to used if this pool database goes down. |
| * The failover pools should be a clustered, replicated or backuped database. |
| */ |
| public void setFailoverConnectionPools(List<String> failoverConnectionPools) { |
| this.failoverConnectionPools = failoverConnectionPools; |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the connection pool to used if this pool database goes down. |
| * The failover pools should be a clustered, replicated or backuped database. |
| */ |
| public boolean addFailoverConnectionPool(String poolName) { |
| return this.failoverConnectionPools.add(poolName); |
| } |
| } |