com.sequoiadb.datasource.SequoiadbDatasource Maven / Gradle / Ivy
Show all versions of sequoiadb-driver Show documentation
/**
* Copyright (C) 2018 SequoiaDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* @package com.sequoiadb.datasource;
* @brief SequoiaDB Data Source
* @author tanzhaobo
*/
package com.sequoiadb.datasource;
import com.sequoiadb.base.DBCursor;
import com.sequoiadb.base.Sequoiadb;
import com.sequoiadb.exception.BaseException;
import com.sequoiadb.exception.SDBError;
import com.sequoiadb.base.ConfigOptions;
import org.bson.BSONObject;
import org.bson.BasicBSONObject;
import org.bson.types.BasicBSONList;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* The implements for SequoiaDB data source
* @since v1.12.6 & v2.2
*/
public class SequoiadbDatasource {
// for coord address
private List _normalAddrs = Collections.synchronizedList(new ArrayList());
private ConcurrentSkipListSet _abnormalAddrs = new ConcurrentSkipListSet();
private ConcurrentSkipListSet _localAddrs = new ConcurrentSkipListSet();
private List _localIPs = Collections.synchronizedList(new ArrayList());
// for created connections
private LinkedBlockingQueue _destroyConnQueue = new LinkedBlockingQueue();
private IConnectionPool _idleConnPool = null;
private IConnectionPool _usedConnPool = null;
private IConnectStrategy _strategy = null;
private ConnectionItemMgr _connItemMgr = null;
private final Object _createConnSignal = new Object();
// for creating connections
private String _username = null;
private String _password = null;
private ConfigOptions _nwOpt = null;
private DatasourceOptions _dsOpt = null;
private long _currentSequenceNumber = 0;
// for thread
private ExecutorService _threadExec = null;
private ScheduledExecutorService _timerExec = null;
// for pool status
private volatile boolean _isDatasourceOn = false;
private volatile boolean _hasClosed = false;
// for thread safe
private ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final Object _objForReleaseConn = new Object();
// for error report
private volatile BaseException _lastException;
// for session
private volatile BSONObject _sessionAttr = null;
// for others
private Random _rand = new Random(47);
private double MULTIPLE = 1.5;
private volatile int _preDeleteInterval = 0;
private static final int _deleteInterval = 180000; // 3min
// finalizer guardian
@SuppressWarnings("unused")
private final Object finalizerGuardian = new Object() {
@Override
protected void finalize() throws Throwable {
try {
close();
} catch (Exception e) {
// do nothing
}
}
};
/// when client program finish running,
/// this task will be executed
class ExitClearUpTask extends Thread {
public void run() {
try {
close();
} catch (Exception e) {
// do nothing
}
}
}
class CreateConnectionTask implements Runnable {
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (_createConnSignal) {
_createConnSignal.wait();
}
Lock rlock = _rwLock.readLock();
rlock.lock();
try {
if (Thread.interrupted()) {
return;
} else {
_createConnections();
}
} finally {
rlock.unlock();
}
}
} catch (InterruptedException e) {
// do nothing
}
}
}
class DestroyConnectionTask implements Runnable {
public void run() {
try {
while (!Thread.interrupted()) {
Sequoiadb sdb = _destroyConnQueue.take();
try {
sdb.disconnect();
} catch (BaseException e) {
continue;
}
}
} catch (InterruptedException e) {
try {
Sequoiadb[] arr = (Sequoiadb[]) _destroyConnQueue.toArray();
for (Sequoiadb db : arr) {
try {
db.disconnect();
} catch (Exception ex) {
}
}
} catch (Exception exp) {
}
}
}
}
class CheckConnectionTask implements Runnable {
@Override
public void run() {
Lock wlock = _rwLock.writeLock();
wlock.lock();
try {
if (Thread.interrupted()) {
return;
}
if (_hasClosed) {
return;
}
if (!_isDatasourceOn) {
return;
}
// check keep alive timeout
if (_dsOpt.getKeepAliveTimeout() > 0) {
long lastTime = 0;
long currentTime = System.currentTimeMillis();
ConnItem connItem = null;
while ((connItem = _strategy.peekConnItemForDeleting()) != null) {
Sequoiadb sdb = _idleConnPool.peek(connItem);
lastTime = sdb.getLastUseTime();
if (currentTime - lastTime + _preDeleteInterval >= _dsOpt.getKeepAliveTimeout()) {
connItem = _strategy.pollConnItemForDeleting();
sdb = _idleConnPool.poll(connItem);
try {
_destroyConnQueue.add(sdb);
} finally {
_connItemMgr.releaseItem(connItem);
}
} else {
break;
}
}
}
// try to reduce the amount of idle connections
if (_idleConnPool.count() > _dsOpt.getMaxIdleCount()) {
int destroyCount = _idleConnPool.count() - _dsOpt.getMaxIdleCount();
_reduceIdleConnections(destroyCount);
}
} finally {
wlock.unlock();
}
}
}
class RetrieveAddressTask implements Runnable {
public void run() {
Lock rlock = _rwLock.readLock();
rlock.lock();
try {
if (Thread.interrupted()) {
return;
}
if (_hasClosed) {
return;
}
if (_abnormalAddrs.size() == 0) {
return;
}
Iterator abnormalAddrSetItr = _abnormalAddrs.iterator();
ConfigOptions nwOpt = new ConfigOptions();
String addr = "";
nwOpt.setConnectTimeout(100); // 100ms
nwOpt.setMaxAutoConnectRetryTime(0);
while (abnormalAddrSetItr.hasNext()) {
addr = abnormalAddrSetItr.next();
try {
@SuppressWarnings("unused")
Sequoiadb sdb = new Sequoiadb(addr, _username, _password, nwOpt);
try {
sdb.disconnect();
} catch (Exception e) {
// do nothing
}
} catch (Exception e) {
continue;
}
abnormalAddrSetItr.remove();
// add address to normal address set
synchronized (_normalAddrs) {
if (!_normalAddrs.contains(addr)) {
_normalAddrs.add(addr);
}
}
_strategy.addAddress(addr);
}
} finally {
rlock.unlock();
}
}
}
class SynchronizeAddressTask implements Runnable {
@Override
public void run() {
Lock wlock = _rwLock.writeLock();
wlock.lock();
try {
if (Thread.interrupted()) {
return;
}
if (_hasClosed) {
return;
}
if (_dsOpt.getSyncCoordInterval() == 0) {
return;
}
// we don't need "synchronized(_normalAddrs)" here, for
// "wlock" tell us that nobody is using "_normalAddrs"
Iterator itr = _normalAddrs.iterator();
Sequoiadb sdb = null;
while (itr.hasNext()) {
String addr = itr.next();
try {
sdb = new Sequoiadb(addr, _username, _password, _nwOpt);
break;
} catch (BaseException e) {
continue;
}
}
if (sdb == null) {
// if we can't connect to database, let's return
return;
}
// get the coord addresses from catalog
List cachedAddrList;
try {
cachedAddrList = _synchronizeCoordAddr(sdb);
} catch (Exception e) {
// if we failed, let's return
return;
} finally {
try {
sdb.disconnect();
} catch (Exception e) {
// ignore
}
sdb = null;
}
// get the difference of coord addresses between catalog and local
List incList = new ArrayList();
List decList = new ArrayList();
String addr = null;
if (cachedAddrList != null && cachedAddrList.size() > 0) {
itr = _normalAddrs.iterator();
for (int i = 0; i < 2; i++, itr = _abnormalAddrs.iterator()) {
while (itr.hasNext()) {
addr = itr.next();
if (!cachedAddrList.contains(addr))
decList.add(addr);
}
}
itr = cachedAddrList.iterator();
while (itr.hasNext()) {
addr = itr.next();
if (!_normalAddrs.contains(addr) &&
!_abnormalAddrs.contains(addr))
incList.add(addr);
}
}
// check whether we need to handle the difference or not
if (incList.size() > 0) {
// we are going to increase some coord addresses to local
itr = incList.iterator();
while (itr.hasNext()) {
addr = itr.next();
synchronized (_normalAddrs) {
if (!_normalAddrs.contains(addr)) {
_normalAddrs.add(addr);
}
}
_strategy.addAddress(addr);
}
}
if (decList.size() > 0) {
// we are going to remove some coord addresses from local
itr = decList.iterator();
while (itr.hasNext()) {
addr = itr.next();
_normalAddrs.remove(addr);
_abnormalAddrs.remove(addr);
_removeAddrInStrategy(addr);
}
}
} finally {
wlock.unlock();
}
}
private List _synchronizeCoordAddr(Sequoiadb sdb) {
List addrList = new ArrayList();
BSONObject condition = new BasicBSONObject();
condition.put("GroupName", "SYSCoord");
BSONObject select = new BasicBSONObject();
select.put("Group.HostName", "");
select.put("Group.Service", "");
DBCursor cursor = sdb.getList(Sequoiadb.SDB_LIST_GROUPS, condition, select, null);
BaseException exp = new BaseException(SDBError.SDB_SYS, "Invalid coord information got from catalog");
try {
while (cursor.hasNext()) {
BSONObject obj = cursor.getNext();
BasicBSONList arr = (BasicBSONList) obj.get("Group");
if (arr == null) throw exp;
Object[] objArr = arr.toArray();
for (int i = 0; i < objArr.length; i++) {
BSONObject subObj = (BasicBSONObject) objArr[i];
String hostName = (String) subObj.get("HostName");
if (hostName == null || hostName.trim().isEmpty()) throw exp;
String svcName = "";
BasicBSONList subArr = (BasicBSONList) subObj.get("Service");
if (subArr == null) throw exp;
Object[] subObjArr = subArr.toArray();
for (int j = 0; j < subObjArr.length; j++) {
BSONObject subSubObj = (BSONObject) subObjArr[j];
Integer type = (Integer) subSubObj.get("Type");
if (type == null) throw exp;
if (type == 0) {
svcName = (String) subSubObj.get("Name");
if (svcName == null || svcName.trim().isEmpty()) throw exp;
String ip;
try {
ip = _parseHostName(hostName.trim());
} catch (Exception e) {
break;
}
addrList.add(ip + ":" + svcName.trim());
break;
}
}
}
}
} finally {
cursor.close();
}
return addrList;
}
}
/**
* When offer several addresses for connection pool to use, if
* some of them are not available(invalid address, network error, coord shutdown,
* catalog replica group is not available), we will put these addresses
* into a queue, and check them periodically. If some of them is valid again,
* get them back for use. When connection pool get a unavailable address to connect,
* the default timeout is 100ms, and default retry time is 0. Parameter nwOpt can
* can change both of the default value.
* @param urls the addresses of coord nodes, can't be null or empty,
* e.g."ubuntu1:11810","ubuntu2:11810",...
* @param username the user name for logging sequoiadb
* @param password the password for logging sequoiadb
* @param nwOpt the options for connection
* @param dsOpt the options for connection pool
* @throws BaseException If error happens.
* @see ConfigOptions
* @see DatasourceOptions
*/
public SequoiadbDatasource(List urls, String username, String password,
ConfigOptions nwOpt, DatasourceOptions dsOpt) throws BaseException {
if (null == urls || 0 == urls.size())
throw new BaseException(SDBError.SDB_INVALIDARG, "coord addresses can't be empty or null");
// init connection pool
_init(urls, username, password, nwOpt, dsOpt);
}
/**
* @deprecated Use com.sequoiadb.base.ConfigOptions instead of com.sequoiadb.net.ConfigOptions.
* @see #SequoiadbDatasource(List, String, String, com.sequoiadb.base.ConfigOptions, DatasourceOptions)
*/
@Deprecated
public SequoiadbDatasource(List urls, String username, String password,
com.sequoiadb.net.ConfigOptions nwOpt, DatasourceOptions dsOpt) throws BaseException {
this(urls, username, password, (ConfigOptions) nwOpt, dsOpt);
}
/**
* @param url the address of coord, can't be empty or null, e.g."ubuntu1:11810"
* @param username the user name for logging sequoiadb
* @param password the password for logging sequoiadb
* @param dsOpt the options for connection pool
* @throws BaseException If error happens.
*/
public SequoiadbDatasource(String url, String username, String password,
DatasourceOptions dsOpt) throws BaseException {
if (url == null || url.isEmpty())
throw new BaseException(SDBError.SDB_INVALIDARG, "coord address can't be empty or null");
ArrayList urls = new ArrayList();
urls.add(url);
_init(urls, username, password, null, dsOpt);
}
/**
* Get the current idle connection amount.
*/
public int getIdleConnNum() {
if (_idleConnPool == null)
return 0;
else
return _idleConnPool.count();
}
/**
* Get the current used connection amount.
*/
public int getUsedConnNum() {
if (_usedConnPool == null)
return 0;
else
return _usedConnPool.count();
}
/**
* Get the current normal address amount.
*/
public int getNormalAddrNum() {
return _normalAddrs.size();
}
/**
* Get the current abnormal address amount.
*/
public int getAbnormalAddrNum() {
return _abnormalAddrs.size();
}
/**
* Get the amount of local coord node address.
* This method works only when the pool is enabled and the connect
* strategy is ConnectStrategy.LOCAL, otherwise return 0.
* @return the amount of local coord node address
* @throws BaseException If error happens.
* @since 2.2
*/
public int getLocalAddrNum() {
return _localAddrs.size();
}
/**
* Add coord address.
* @param url The address in format "192.168.20.168:11810"
* @throws BaseException If error happens.
*/
public void addCoord(String url) throws BaseException {
Lock wlock = _rwLock.writeLock();
wlock.lock();
try {
if (_hasClosed) {
throw new BaseException(SDBError.SDB_SYS, "connection pool has closed");
}
if (null == url || "" == url) {
throw new BaseException(SDBError.SDB_INVALIDARG, "coord address can't be empty or null");
}
// parse coord address to the format "192.168.20.165:11810"
String addr = _parseCoordAddr(url);
// check whether the url exists in pool or not
if (_normalAddrs.contains(addr) ||
_abnormalAddrs.contains(addr)) {
return;
}
// add to local
synchronized (_normalAddrs) {
if (!_normalAddrs.contains(addr)) {
_normalAddrs.add(addr);
}
}
if (ConcreteLocalStrategy.isLocalAddress(addr, _localIPs))
_localAddrs.add(addr);
// add to strategy
if (_isDatasourceOn) {
_strategy.addAddress(addr);
}
} finally {
wlock.unlock();
}
}
/**
* Remove coord address.
* @since 2.2
*/
public void removeCoord(String url) throws BaseException {
Lock wlock = _rwLock.writeLock();
wlock.lock();
try {
if (_hasClosed) {
throw new BaseException(SDBError.SDB_SYS, "connection pool has closed");
}
if (null == url) {
throw new BaseException(SDBError.SDB_INVALIDARG, "coord address can't be null");
}
// parse coord address to the format "192.168.20.165:11810"
String addr = _parseCoordAddr(url);
// remove from local
_normalAddrs.remove(addr);
_abnormalAddrs.remove(addr);
_localAddrs.remove(addr);
if (_isDatasourceOn) {
// remove from strategy
_removeAddrInStrategy(addr);
}
} finally {
wlock.unlock();
}
}
/**
* Get a copy of the connection pool options.
* @return a copy of the connection pool options
* @throws BaseException If error happens.
* @since 2.2
*/
public DatasourceOptions getDatasourceOptions() throws BaseException {
Lock rlock = _rwLock.readLock();
rlock.lock();
try {
return (DatasourceOptions) _dsOpt.clone();
} catch (CloneNotSupportedException e) {
throw new BaseException(SDBError.SDB_SYS, "failed to clone connnection pool options");
} finally {
rlock.unlock();
}
}
/**
* Update connection pool options.
* @throws BaseException If error happens.
* @since 2.2
*/
public void updateDatasourceOptions(DatasourceOptions dsOpt) throws BaseException {
Lock wlock = _rwLock.writeLock();
wlock.lock();
try {
if (_hasClosed) {
throw new BaseException(SDBError.SDB_SYS, "connection pool has closed");
}
// check options
_checkDatasourceOptions(dsOpt);
// save previous values
int previousMaxCount = _dsOpt.getMaxCount();
int previousCheckInterval = _dsOpt.getCheckInterval();
int previousSyncCoordInterval = _dsOpt.getSyncCoordInterval();
ConnectStrategy previousStrategy = _dsOpt.getConnectStrategy();
// reset options
try {
_dsOpt = (DatasourceOptions) dsOpt.clone();
} catch (CloneNotSupportedException e) {
throw new BaseException(SDBError.SDB_INVALIDARG, "failed to clone connection pool options");
}
// when data source is disable, return directly
if (!_isDatasourceOn) {
return;
}
// when _maxCount is set to 0, disable data source and return
if (_dsOpt.getMaxCount() == 0) {
disableDatasource();
return;
}
_preDeleteInterval = (int) (_dsOpt.getCheckInterval() * MULTIPLE);
// check need to adjust the capacity of connection pool or not.
// when the data source is disable, we can't change the "_currentSequenceNumber"
// to the value we want, that's a problem, so we will change "_currentSequenceNumber"
// in "_enableDatasource()"
if (previousMaxCount != _dsOpt.getMaxCount()) {
// when "_enableDatasource()" is not called, "_connItemMgr" will be null
if (_connItemMgr != null) {
_connItemMgr.resetCapacity(_dsOpt.getMaxCount());
if (_dsOpt.getMaxCount() < previousMaxCount) {
// make sure we have not get connection item more then
// _dsOpt.getMaxCount(), if so, let't decrease some in
// idle pool. But, we won't decrease any in used pool.
// When a connection is get out from used pool, we will
// check whether the item pool is full or not, if so, we
// won't let the connection and the item for it go to idle
// pool, we will destroy both out them.
int deltaNum = getIdleConnNum() + getUsedConnNum() - _dsOpt.getMaxCount();
int destroyNum = (deltaNum > getIdleConnNum()) ? getIdleConnNum() : deltaNum;
if (destroyNum > 0)
_reduceIdleConnections(destroyNum);
// update the version, so, all the outdated caching connections in used pool
// can not go back to idle pool any more.
_currentSequenceNumber = _connItemMgr.getCurrentSequenceNumber();
}
} else {
// should never happen
throw new BaseException(SDBError.SDB_SYS, "the item manager is null");
}
}
// check need to restart timer and threads or not
if (previousStrategy != _dsOpt.getConnectStrategy()) {
_cancelTimer();
_cancelThreads();
_changeStrategy();
_startTimer();
_startThreads();
_currentSequenceNumber = _connItemMgr.getCurrentSequenceNumber();
} else if (previousCheckInterval != _dsOpt.getCheckInterval() ||
previousSyncCoordInterval != _dsOpt.getSyncCoordInterval()) {
_cancelTimer();
_startTimer();
}
} finally {
wlock.unlock();
}
}
/**
* Enable data source.
* When maxCount is 0, set it to be the default value(500).
* @throws BaseException If error happens.
* @since 2.2
*/
public void enableDatasource() {
Lock wlock = _rwLock.writeLock();
wlock.lock();
try {
if (_hasClosed) {
throw new BaseException(SDBError.SDB_SYS, "connection pool has closed");
}
if (_isDatasourceOn) {
return;
}
if (_dsOpt.getMaxCount() == 0) {
_dsOpt.setMaxCount(500);
}
_enableDatasource(_dsOpt.getConnectStrategy());
} finally {
wlock.unlock();
}
return;
}
/**
* Disable the data source.
* After disable data source, the pool will not manage
* the connections again. When a getting request comes,
* the pool build and return a connection; When a connection
* is put back, the pool disconnect it directly.
* @throws BaseException If error happens.
* @since 2.2
*/
public void disableDatasource() {
Lock wlock = _rwLock.writeLock();
wlock.lock();
try {
if (_hasClosed) {
throw new BaseException(SDBError.SDB_SYS, "connection pool has closed");
}
if (!_isDatasourceOn) {
return;
}
// stop timer
_cancelTimer();
// stop threads
_cancelThreads();
// close the connections in idle pool
_closePoolConnections(_idleConnPool);
_isDatasourceOn = false;
} finally {
wlock.unlock();
}
return;
}
/**
* Get a connection from current connection pool.
* When the pool runs out, a request will wait up to 5 seconds. When time is up, if the pool
* still has no idle connection, it throws BaseException with the type of "SDB_DRIVER_DS_RUNOUT".
* @return Sequoiadb the connection for using
* @throws BaseException If error happens.
* @throws InterruptedException Actually, nothing happen. Throw this for compatibility reason.
*/
public Sequoiadb getConnection() throws BaseException, InterruptedException {
return getConnection(5000);
}
/**
* Get a connection from current connection pool.
* @param timeout the time for waiting for connection in millisecond. 0 for waiting until a connection is available.
* @return Sequoiadb the connection for using
* @throws BaseException when connection pool run out, throws BaseException with the type of "SDB_DRIVER_DS_RUNOUT"
* @throws InterruptedException Actually, nothing happen. Throw this for compatibility reason.
* @since 2.2
*/
public Sequoiadb getConnection(long timeout) throws BaseException, InterruptedException {
if (timeout < 0) {
throw new BaseException(SDBError.SDB_INVALIDARG, "timeout should >= 0");
}
Lock rlock = _rwLock.readLock();
rlock.lock();
try {
Sequoiadb sdb;
ConnItem connItem;
long restTime = timeout;
while (true) {
sdb = null;
connItem = null;
if (_hasClosed) {
throw new BaseException(SDBError.SDB_SYS, "connection pool has closed");
}
// when the pool is disabled
if (!_isDatasourceOn) {
return _newConnByNormalAddr();
}
if ((connItem = _strategy.pollConnItemForGetting()) != null) {
// when we still have connection in idle pool,
// get connection directly
sdb = _idleConnPool.poll(connItem);
// sanity check
if (sdb == null) {
_connItemMgr.releaseItem(connItem);
connItem = null;
// should never come here
throw new BaseException(SDBError.SDB_SYS, "no matching connection");
}
} else if ((connItem = _connItemMgr.getItem()) != null) {
// when we have no connection in idle pool,
// new a connection, and wait up background thread to create connections
try {
sdb = _newConnByNormalAddr();
} catch (BaseException e) {
_connItemMgr.releaseItem(connItem);
connItem = null;
throw e;
}
// sanity check
if (sdb == null) {
_connItemMgr.releaseItem(connItem);
connItem = null;
// should never come here
throw new BaseException(SDBError.SDB_SYS, "create connection directly failed");
} else if (_sessionAttr != null) {
try {
sdb.setSessionAttr(_sessionAttr);
} catch (Exception e) {
_connItemMgr.releaseItem(connItem);
connItem = null;
_destroyConnQueue.add(sdb);
throw new BaseException(SDBError.SDB_SYS,
String.format("failed to set the session attribute[%s]",
_sessionAttr.toString()), e);
}
}
connItem.setAddr(sdb.getServerAddress().toString());
synchronized (_createConnSignal) {
_createConnSignal.notify();
}
} else {
// when we can't get anything, let's wait
long beginTime = 0;
long endTime = 0;
// release the read lock before wait up
rlock.unlock();
try {
if (timeout != 0) {
if (restTime <= 0) {
// stop waiting
break;
}
beginTime = System.currentTimeMillis();
try {
synchronized (this) {
this.wait(restTime);
}
} finally {
endTime = System.currentTimeMillis();
restTime -= (endTime - beginTime);
}
// even if the restTime is up, let it retry one more time
continue;
} else {
try {
synchronized (this) {
// we have no double check of the connItem here,
// so we can't use this.wait() here.
// let it retry after a few seconds later
this.wait(5000);
}
} finally {
continue;
}
}
} finally {
// let's get the read lock before going on
rlock.lock();
}
}
// here we get the connection, let's check whether the connection is usable
if (sdb.isClosed() ||
(_dsOpt.getValidateConnection() && !sdb.isValid())) {
// let the item go back to _connItemMgr and destroy
// the connection, then try again
_connItemMgr.releaseItem(connItem);
connItem = null;
_destroyConnQueue.add(sdb);
sdb = null;
continue;
} else {
// stop looping
break;
}
} // while(true)
// when we can't get connection, try to report error
if (connItem == null) {
// make some debug info
String detail = _getDataSourceSnapshot();
// when the last connItem is hold by background creating thread,
// and it failed to create the last connection, let's report network error
if (getNormalAddrNum() == 0 && getUsedConnNum() < _dsOpt.getMaxCount()) {
BaseException exception = _getLastException();
String errMsg = "get connection failed, no available address for connection, " + detail;
if (exception != null) {
throw new BaseException(SDBError.SDB_NETWORK, errMsg, exception);
} else {
throw new BaseException(SDBError.SDB_NETWORK, errMsg);
}
} else {
throw new BaseException(SDBError.SDB_DRIVER_DS_RUNOUT,
"the pool has run out of connections, " + detail);
}
} else {
// insert the itemInfo and connection to used pool
_usedConnPool.insert(connItem, sdb);
// tell strategy used pool had add a connection
_strategy.updateUsedConnItemCount(connItem, 1);
return sdb;
}
} finally {
rlock.unlock();
}
}
/**
* Put the connection back to the connection pool.
* When the data source is enable, we can't double release
* one connection, and we can't offer a connection which is
* not belong to the pool.
* @param sdb the connection to come back, can't be null
* @throws BaseException If error happens.
* @since 2.2
*/
public void releaseConnection(Sequoiadb sdb) throws BaseException {
Lock rlock = _rwLock.readLock();
rlock.lock();
try {
if (sdb == null) {
throw new BaseException(SDBError.SDB_INVALIDARG, "connection can't be null");
}
if (_hasClosed) {
throw new BaseException(SDBError.SDB_SYS, "connection pool has closed");
}
// in case the data source is disable
if (!_isDatasourceOn) {
// when we disable data source, we should try to remove
// the connections left in used connection pool
synchronized (_objForReleaseConn) {
if (_usedConnPool != null && _usedConnPool.contains(sdb)) {
ConnItem item = _usedConnPool.poll(sdb);
if (item == null) {
// multi-thread may let item to be null, and it should never happen
throw new BaseException(SDBError.SDB_SYS,
"the pool does't have item for the coming back connection");
}
_connItemMgr.releaseItem(item);
}
}
try {
sdb.disconnect();
} catch (Exception e) {
// do nothing
}
return;
}
// in case the data source is enable
ConnItem item = null;
synchronized (_objForReleaseConn) {
// if the busy pool contains this connection
if (_usedConnPool.contains(sdb)) {
// remove it from used queue
item = _usedConnPool.poll(sdb);
if (item == null) {
throw new BaseException(SDBError.SDB_SYS,
"the pool does not have item for the coming back connection");
}
} else {
// throw exception to let user know current connection does't contained in the pool
throw new BaseException(SDBError.SDB_INVALIDARG,
"the connection pool doesn't contain the offered connection");
}
}
// we have decreased connection in used pool, let's update the strategy
_strategy.updateUsedConnItemCount(item, -1);
// check whether the connection can put back to idle pool or not
if (_connIsValid(item, sdb)) {
// let the connection come back to connection pool
_idleConnPool.insert(item, sdb);
// tell the strategy one connection is add to idle pool now
_strategy.addConnItemAfterReleasing(item);
// notify the people who waits
synchronized (this) {
notifyAll();
}
} else {
// let the item come back to item pool, and destroy the connection
_connItemMgr.releaseItem(item);
_destroyConnQueue.add(sdb);
synchronized (this) {
notifyAll();
}
}
} finally {
rlock.unlock();
}
}
/**
* Put the connection back to the connection pool.
* When the data source is enable, we can't double release
* one connection, and we can't offer a connection which is
* not belong to the pool.
* @param sdb the connection to come back, can't be null
* @throws BaseException If error happens.
* @see #releaseConnection(Sequoiadb)
* @deprecated use releaseConnection() instead
*/
public void close(Sequoiadb sdb) throws BaseException {
releaseConnection(sdb);
}
/**
* Clean all resources of current connection pool.
*/
public void close() {
Lock wlock = _rwLock.writeLock();
wlock.lock();
try {
if (_hasClosed) {
return;
}
if (_isDatasourceOn) {
_cancelTimer();
_cancelThreads();
}
// close connections
if (_idleConnPool != null)
_closePoolConnections(_idleConnPool);
if (_usedConnPool != null)
_closePoolConnections(_usedConnPool);
_isDatasourceOn = false;
_hasClosed = true;
} finally {
wlock.unlock();
}
}
private void _init(List addrList, String username, String password,
ConfigOptions nwOpt, DatasourceOptions dsOpt) throws BaseException {
// set arguments
for (String elem : addrList) {
if (elem != null && !elem.isEmpty()) {
// parse coord address to the format "192.168.20.165:11810"
String addr = _parseCoordAddr(elem);
synchronized (_normalAddrs) {
if (!_normalAddrs.contains(addr)) {
_normalAddrs.add(addr);
}
}
}
}
_username = (username == null) ? "" : username;
_password = (password == null) ? "" : password;
if (nwOpt == null) {
ConfigOptions temp = new ConfigOptions();
temp.setConnectTimeout(100);
temp.setMaxAutoConnectRetryTime(0);
_nwOpt = temp;
} else {
_nwOpt = nwOpt;
}
if (dsOpt == null) {
_dsOpt = new DatasourceOptions();
} else {
try {
_dsOpt = (DatasourceOptions) dsOpt.clone();
} catch (CloneNotSupportedException e) {
throw new BaseException(SDBError.SDB_INVALIDARG, "failed to clone connection pool options");
}
}
// pick up local coord address
List localIPList = ConcreteLocalStrategy.getNetCardIPs();
_localIPs.addAll(localIPList);
List localCoordList =
ConcreteLocalStrategy.getLocalCoordIPs(_normalAddrs, localIPList);
_localAddrs.addAll(localCoordList);
// check options
_checkDatasourceOptions(_dsOpt);
// if connection is shutdown, return directly
if (_dsOpt.getMaxCount() == 0) {
_isDatasourceOn = false;
} else {
_enableDatasource(_dsOpt.getConnectStrategy());
}
// set a hook for closing all the connection, when object is destroyed
Runtime.getRuntime().addShutdownHook(new ExitClearUpTask());
}
private void _startTimer() {
_timerExec = Executors.newScheduledThreadPool(1,
new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}
}
);
if (_dsOpt.getSyncCoordInterval() > 0) {
_timerExec.scheduleAtFixedRate(new SynchronizeAddressTask(), 0, _dsOpt.getSyncCoordInterval(),
TimeUnit.MILLISECONDS);
}
_timerExec.scheduleAtFixedRate(new CheckConnectionTask(), _dsOpt.getCheckInterval(),
_dsOpt.getCheckInterval(), TimeUnit.MILLISECONDS);
_timerExec.scheduleAtFixedRate(new RetrieveAddressTask(), 60, 60, TimeUnit.SECONDS);
}
private void _cancelTimer() {
_timerExec.shutdownNow();
}
private void _startThreads() {
_threadExec = Executors.newCachedThreadPool(
new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}
}
);
_threadExec.execute(new CreateConnectionTask());
_threadExec.execute(new DestroyConnectionTask());
// stop adding task
_threadExec.shutdown();
}
private void _cancelThreads() {
_threadExec.shutdownNow();
}
private void _changeStrategy() {
List idleConnPairs = new ArrayList();
List usedConnPairs = new ArrayList();
Iterator itr = null;
itr = _idleConnPool.getIterator();
while (itr.hasNext()) {
idleConnPairs.add(itr.next());
}
itr = _usedConnPool.getIterator();
while (itr.hasNext()) {
usedConnPairs.add(itr.next());
}
_strategy = _createStrategy(_dsOpt.getConnectStrategy());
// here we don't need to offer abnormal address, for "RetrieveAddressTask"
// will here us to add those addresses to strategy when those addresses
// can be use again
_strategy.init(_normalAddrs, idleConnPairs, usedConnPairs);
}
private void _closePoolConnections(IConnectionPool pool) {
if (pool == null) {
return;
}
// disconnect all the connections
Iterator iter = pool.getIterator();
while (iter.hasNext()) {
Pair pair = iter.next();
Sequoiadb sdb = pair.second();
try {
sdb.disconnect();
} catch (Exception e) {
// do nothing
}
}
// clear them from the pool
List list = pool.clear();
for (ConnItem item : list)
_connItemMgr.releaseItem(item);
// we are not clear the info in strategy,
// for the strategy instance is abandoned,
// and we will create a new one next time
}
private void _checkDatasourceOptions(DatasourceOptions newOpt) throws BaseException {
if (newOpt == null) {
throw new BaseException(SDBError.SDB_INVALIDARG, "the offering datasource options can't be null");
}
int deltaIncCount = newOpt.getDeltaIncCount();
int maxIdleCount = newOpt.getMaxIdleCount();
int maxCount = newOpt.getMaxCount();
int keepAliveTimeout = newOpt.getKeepAliveTimeout();
int checkInterval = newOpt.getCheckInterval();
int syncCoordInterval = newOpt.getSyncCoordInterval();
List