blob: c38d94fe1a0335191b93adecd1a8eb8f540e2ab3 [file] [log] [blame]
/*
* 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
*/
protected void setConnectionsAvailable(Vector connectionsAvailable) {
this.connectionsAvailable = connectionsAvailable;
}
/**
* INTERNAL:
* Set the list of connections being used.
*/
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
*/
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);
}
}