Repository: karaf Updated Branches: refs/heads/master f5b7cce2d -> a7a627467
[KARAF-5271] Improve JDBC generic lock to better support network glitches Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/a7a62746 Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/a7a62746 Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/a7a62746 Branch: refs/heads/master Commit: a7a627467981433512149b3658145bccbac0c80e Parents: eb99606 Author: Guillaume Nodet <[email protected]> Authored: Mon Jul 24 22:41:35 2017 +0200 Committer: Guillaume Nodet <[email protected]> Committed: Tue Jul 25 21:40:14 2017 +0200 ---------------------------------------------------------------------- .../karaf/main/lock/GenericDataSource.java | 170 +++++++++ .../apache/karaf/main/lock/GenericJDBCLock.java | 367 ++++++------------- 2 files changed, 291 insertions(+), 246 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/a7a62746/main/src/main/java/org/apache/karaf/main/lock/GenericDataSource.java ---------------------------------------------------------------------- diff --git a/main/src/main/java/org/apache/karaf/main/lock/GenericDataSource.java b/main/src/main/java/org/apache/karaf/main/lock/GenericDataSource.java new file mode 100644 index 0000000..57eaddd --- /dev/null +++ b/main/src/main/java/org/apache/karaf/main/lock/GenericDataSource.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.karaf.main.lock; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Properties; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Logger; + +public class GenericDataSource implements DataSource { + + private static final String DRIVER_MANAGER_USER_PROPERTY = "user"; + private static final String DRIVER_MANAGER_PASSWORD_PROPERTY = "password"; + + private final String driverClass; + private final String url; + private final Properties properties; + private final int validTimeoutMs; + + private final Queue<Connection> cache; + + private volatile Driver driver; + private volatile boolean driverClassLoaded; + + public GenericDataSource(String driverClass, String url, String user, String password, boolean cache, int validTimeoutMs) { + this.driverClass = driverClass; + this.url = url; + this.properties = new Properties(); + if (user != null) { + properties.setProperty(DRIVER_MANAGER_USER_PROPERTY, user); + } + if (password != null) { + properties.setProperty(DRIVER_MANAGER_PASSWORD_PROPERTY, password); + } + this.validTimeoutMs = validTimeoutMs; + this.cache = cache ? new ConcurrentLinkedQueue<>() : null; + } + + private void ensureDriverLoaded() throws SQLException { + try { + if (!driverClassLoaded) { + synchronized (this) { + if (!driverClassLoaded) { + if (driverClass != null) { + Class.forName(driverClass); + } + driverClassLoaded = true; + } + } + } + } catch (ClassNotFoundException e) { + throw new SQLException("Unable to load driver class " + driverClass, e); + } + } + + private Driver driver() throws SQLException { + if (driver == null) { + synchronized (this) { + if (driver == null) { + driver = DriverManager.getDriver(url); + } + } + } + return driver; + } + + public Connection getConnection() throws SQLException { + ensureDriverLoaded(); + while (true) { + Connection con = cache != null ? cache.poll() : null; + if (con == null) { + con = driver().connect(url, properties); + if (con == null) { + throw new SQLException("Invalid jdbc URL '" + url + "' for driver " + driver()); + } + } else { + if (!con.isValid(validTimeoutMs)) { + con.close(); + con = null; + } + } + if (con != null) { + return wrap(con); + } + } + } + + private Connection wrap(Connection con) { + return (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[] { Connection.class }, new InvocationHandler() { + private boolean closed = false; + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("close") && method.getParameterCount() == 0) { + closed = true; + if (!cache.offer(con)) { + con.close(); + } + return null; + } else if (method.getName().equals("isClosed") && method.getParameterCount() == 0) { + return closed; + } else { + if (closed) { + throw new SQLException("Connection closed"); + } + return method.invoke(con, args); + } + } + }); + } + + public Connection getConnection(String username, String password) throws SQLException { + throw new UnsupportedOperationException(); + } + + public PrintWriter getLogWriter() throws SQLException { + return null; + } + + public void setLogWriter(PrintWriter out) throws SQLException { + } + + public int getLoginTimeout() throws SQLException { + return 0; + } + + public void setLoginTimeout(int seconds) throws SQLException { + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return null; + } + + @Override + public <T> T unwrap(Class<T> iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class<?> iface) throws SQLException { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/a7a62746/main/src/main/java/org/apache/karaf/main/lock/GenericJDBCLock.java ---------------------------------------------------------------------- diff --git a/main/src/main/java/org/apache/karaf/main/lock/GenericJDBCLock.java b/main/src/main/java/org/apache/karaf/main/lock/GenericJDBCLock.java index fe49e92..0e485e9 100644 --- a/main/src/main/java/org/apache/karaf/main/lock/GenericJDBCLock.java +++ b/main/src/main/java/org/apache/karaf/main/lock/GenericJDBCLock.java @@ -19,7 +19,6 @@ package org.apache.karaf.main.lock; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -31,6 +30,8 @@ import java.util.logging.Logger; import org.apache.karaf.main.ConfigProperties; import org.apache.karaf.main.util.BootstrapLogManager; +import javax.sql.DataSource; + /** * <p>This class is the base class used to provide a master/slave configuration for * a given set of active karaf instances using JDBC. </p> @@ -127,22 +128,26 @@ public class GenericJDBCLock implements Lock { final Logger LOG = Logger.getLogger(this.getClass().getName()); - public static final String PROPERTY_LOCK_URL = "karaf.lock.jdbc.url"; - public static final String PROPERTY_LOCK_JDBC_DRIVER = "karaf.lock.jdbc.driver"; - public static final String PROPERTY_LOCK_JDBC_USER = "karaf.lock.jdbc.user"; - public static final String PROPERTY_LOCK_JDBC_PASSWORD = "karaf.lock.jdbc.password"; - public static final String PROPERTY_LOCK_JDBC_TABLE = "karaf.lock.jdbc.table"; - public static final String PROPERTY_LOCK_JDBC_TABLE_ID = "karaf.lock.jdbc.table_id"; - public static final String PROPERTY_LOCK_JDBC_CLUSTERNAME = "karaf.lock.jdbc.clustername"; + public static final String PROPERTY_LOCK_URL = "karaf.lock.jdbc.url"; + public static final String PROPERTY_LOCK_JDBC_DRIVER = "karaf.lock.jdbc.driver"; + public static final String PROPERTY_LOCK_JDBC_USER = "karaf.lock.jdbc.user"; + public static final String PROPERTY_LOCK_JDBC_PASSWORD = "karaf.lock.jdbc.password"; + public static final String PROPERTY_LOCK_JDBC_TABLE = "karaf.lock.jdbc.table"; + public static final String PROPERTY_LOCK_JDBC_TABLE_ID = "karaf.lock.jdbc.table_id"; + public static final String PROPERTY_LOCK_JDBC_CLUSTERNAME = "karaf.lock.jdbc.clustername"; + public static final String PROPERTY_LOCK_JDBC_CACHE = "karaf.lock.jdbc.cache"; + public static final String PROPERTY_LOCK_JDBC_VALID_TIMEOUT = "karaf.lock.jdbc.valid_timeout"; public static final String DEFAULT_PASSWORD = ""; public static final String DEFAULT_USER = ""; public static final String DEFAULT_TABLE = "KARAF_LOCK"; public static final String DEFAULT_TABLE_ID = "KARAF_NODE_ID"; public static final String DEFAULT_CLUSTERNAME = "karaf"; + public static final String DEFAULT_CACHE = "true"; + public static final String DEFAULT_VALID_TIMEOUT = "0"; final GenericStatements statements; - Connection lockConnection; + DataSource dataSource; String url; String driver; String user; @@ -179,6 +184,13 @@ public class GenericJDBCLock implements Lock { this.statements = createStatements(); + String url = this.url; + if (url.toLowerCase().startsWith("jdbc:derby")) { + url = (url.toLowerCase().contains("create=true")) ? url : url + ";create=true"; + } + boolean cacheEnabled = Boolean.parseBoolean(props.getProperty(PROPERTY_LOCK_JDBC_CACHE, DEFAULT_CACHE)); + int validTimeout = Integer.parseInt(props.getProperty(PROPERTY_LOCK_JDBC_VALID_TIMEOUT, DEFAULT_VALID_TIMEOUT)); + this.dataSource = new GenericDataSource(driver, url, user, password, cacheEnabled, validTimeout); init(); } @@ -188,15 +200,16 @@ public class GenericJDBCLock implements Lock { * @return An instance of a JDBCStatement object. */ GenericStatements createStatements() { - GenericStatements statements = new GenericStatements(table, table_id, clusterName); - return statements; + return new GenericStatements(table, table_id, clusterName); } void init() { try { createDatabase(); - createSchema(); - generateUniqueId(); + try (Connection connection = getConnection()) { + createSchema(connection); + generateUniqueId(connection); + } } catch (Exception e) { LOG.log(Level.SEVERE, "Error occured while attempting to obtain connection", e); } @@ -209,43 +222,28 @@ public class GenericJDBCLock implements Lock { /** * This method is called to check and create the required schemas that are used by this instance. */ - void createSchema() { - if (schemaExists()) { - return; - } - - String[] createStatments = this.statements.getLockCreateSchemaStatements(System.currentTimeMillis()); - Statement statement = null; - Connection connection = null; - + void createSchema(Connection connection) { try { - connection = getConnection(); - statement = connection.createStatement(); - connection.setAutoCommit(false); - - for (String stmt : createStatments) { - LOG.info("Executing statement: " + stmt); - statement.execute(stmt); + if (schemaExists(connection)) { + return; } + try (Statement statement = connection.createStatement()) { + connection.setAutoCommit(false); - connection.commit(); - } catch (Exception e) { - LOG.log(Level.SEVERE, "Could not create schema", e ); - try { - // Rollback transaction if and only if there was a failure... - if (connection != null) - connection.rollback(); - } catch (Exception ie) { - // Do nothing.... - } - } finally { - closeSafely(statement); - try { - // Reset the auto commit to true + String[] createStatements = this.statements.getLockCreateSchemaStatements(System.currentTimeMillis()); + for (String stmt : createStatements) { + LOG.info("Executing statement: " + stmt); + statement.execute(stmt); + } + + connection.commit(); connection.setAutoCommit(true); - } catch (SQLException ignored) { - LOG.log(Level.FINE, "Exception while setting the connection auto commit", ignored); + } catch (Exception e) { + connection.rollback(); + throw e; } + } catch (Exception e) { + LOG.log(Level.SEVERE, "Could not create schema", e ); } } @@ -254,9 +252,9 @@ public class GenericJDBCLock implements Lock { * * @return True, if the schemas are available else false. */ - boolean schemaExists() { - return schemaExist(this.statements.getLockTableName()) - && schemaExist(this.statements.getLockIdTableName()); + boolean schemaExists(Connection connection) { + return schemaExist(connection, this.statements.getLockTableName()) + && schemaExist(connection, this.statements.getLockIdTableName()); } /** @@ -265,16 +263,12 @@ public class GenericJDBCLock implements Lock { * @param tableName The name of the table to determine if it exists. * @return True, if the table exists else false. */ - private boolean schemaExist(String tableName) { - ResultSet rs = null; + private boolean schemaExist(Connection connection, String tableName) { boolean schemaExists = false; - try { - rs = getConnection().getMetaData().getTables(null, null, tableName, new String[] {"TABLE"}); + try (ResultSet rs = connection.getMetaData().getTables(null, null, tableName, new String[] {"TABLE"})) { schemaExists = rs.next(); } catch (Exception ignore) { LOG.log(Level.SEVERE, "Error testing for db table", ignore); - } finally { - closeSafely(rs); } return schemaExists; } @@ -283,23 +277,18 @@ public class GenericJDBCLock implements Lock { * This method will generate a unique id for this instance that is part of an active set of instances. * This method uses a simple algorithm to insure that the id will be unique for all cases. */ - void generateUniqueId() { - boolean uniqueIdSet = false; + void generateUniqueId(Connection connection) { String selectString = this.statements.getLockIdSelectStatement(); - PreparedStatement selectStatement = null, updateStatement = null; - try { - selectStatement = getConnection().prepareStatement(selectString); + try (PreparedStatement selectStatement = connection.prepareStatement(selectString)) { - // This loop can only be performed for so long and the chances that this will be + boolean uniqueIdSet = false; + // This loop can only be performed for so long and the chances that this will be // looping for more than a few times is unlikely since there will always be at // least one instance that is successful. while (!uniqueIdSet) { - ResultSet rs = null; - - try { - // Get the current ID from the karaf ids table - rs = selectStatement.executeQuery(); + // Get the current ID from the karaf ids table + try (ResultSet rs = selectStatement.executeQuery()) { // Check if we were able to retrieve the result... if (rs.next()) { @@ -308,127 +297,38 @@ public class GenericJDBCLock implements Lock { String updateString = this.statements.getLockIdUpdateIdStatement(currentId + 1, currentId); - updateStatement = getConnection().prepareStatement(updateString); - - int count = updateStatement.executeUpdate(); - - // Set the uniqueId if and only if is it greater that zero - uniqueId = ( uniqueIdSet = count > 0 ) ? currentId + 1 : 0; - - if (count > 1) { - LOG.severe("OOPS there are more than one row within the table ids..."); + try (PreparedStatement updateStatement = connection.prepareStatement(updateString)) { + + int count = updateStatement.executeUpdate(); + + // Set the uniqueId if and only if is it greater that zero + uniqueId = (uniqueIdSet = count > 0) ? currentId + 1 : 0; + + if (count > 1) { + LOG.severe("OOPS there are more than one row within the table ids..."); + } } } else { LOG.severe("No rows were found...."); } } catch (SQLException e) { LOG.log(Level.SEVERE, "Received an SQL exception while processing result set", e); - } finally { - this.closeSafely(rs); } } } catch (SQLException e) { LOG.log(Level.SEVERE, "Received an SQL exception while generating a prepate statement", e); - } catch (Exception e) { - LOG.log(Level.SEVERE, "Received an exception while trying to get a reference to a connection", e); - } finally { - closeSafely(selectStatement); } LOG.info("INSTANCE unique id: " + uniqueId); } /** - * This method is called to determine if this instance JDBC connection is - * still connected. - * - * @return True, if the connection is still connected else false. - * @throws SQLException If an SQL error occurs while checking if the lock is connected to the database. - */ - boolean isConnected() throws SQLException { - return lockConnection != null && !lockConnection.isClosed(); - } - - /** - * This method is called to safely close a Statement. - * - * @param preparedStatement The statement to be closed. - */ - void closeSafely(Statement preparedStatement) { - if (preparedStatement != null) { - try { - preparedStatement.close(); - } catch (SQLException e) { - LOG.log(Level.SEVERE, "Failed to close statement", e); - } - } - } - - /** - * This method is called to safely close a ResultSet instance. - * - * @param rs The result set to be closed. - */ - void closeSafely(ResultSet rs) { - if (rs != null) { - try { - rs.close(); - } catch (SQLException e) { - LOG.log(Level.SEVERE, "Error occured while releasing ResultSet", e); - } - } - } - - /** * This method will return an active connection for this given jdbc driver. * * @return The JDBC connection instance * @throws Exception If the JDBC connection can't be retrieved. */ protected Connection getConnection() throws Exception { - if (!isConnected()) { - lockConnection = createConnection(driver, url, user, password); - } - - return lockConnection; - } - - /** - * Create a new JDBC connection. - * - * @param driver The fully qualified driver class name. - * @param url The database connection URL. - * @param username The username for the database. - * @param password The password for the database. - * @return a new JDBC connection. - * @throws Exception If the JDBC connection can't be created. - */ - protected Connection createConnection(String driver, String url, String username, String password) throws Exception { - if (url.toLowerCase().startsWith("jdbc:derby")) { - url = (url.toLowerCase().contains("create=true")) ? url : url + ";create=true"; - } - - try { - return doCreateConnection(driver, url, username, password); - } catch (Exception e) { - LOG.log(Level.SEVERE, "Error occured while setting up JDBC connection", e); - throw e; - } - } - - /** - * This method could be used to inject a mock JDBC connection for testing purposes. - * - * @param driver The fully qualified driver class name. - * @param url The database connection URL. - * @param username The username for the database. - * @param password The password for the database. - * @return a new JDBC connection. - * @throws ClassNotFoundException If the JDBC driver class is not found. - * @throws SQLException If the JDBC connection can't be created. - */ - protected Connection doCreateConnection(String driver, String url, String username, String password) throws ClassNotFoundException, SQLException { - Class.forName(driver); - return DriverManager.getConnection(url, username, password); + return dataSource.getConnection(); } /** @@ -441,61 +341,60 @@ public class GenericJDBCLock implements Lock { * @see org.apache.karaf.main.lock.Lock#lock() */ public boolean lock() throws Exception { - - // Try to acquire/update the lock state - boolean lockAquired = acquireLock(statements.getLockUpdateIdStatement(uniqueId, ++state, lock_delay, uniqueId)); - - if (!lockAquired) { - - String lockSelectStatement = statements.getLockSelectStatement(); - PreparedStatement statement = null; - ResultSet rs = null; - - try { - statement = getConnection().prepareStatement(lockSelectStatement); - // Get the current master id and compare with information that we have locally.... - rs = statement.executeQuery(); - - if (rs.next()) { - int currentId = statements.getIdFromLockSelectStatement(rs); // The current master unique id or 0 - int currentState = statements.getStateFromLockSelectStatement(rs); // The current master state or whatever - - if (this.currentId == currentId) { - // It is the same instance that locked the table - if (this.currentState == currentState) { - // Its state has not been updated.... - if ( (this.currentStateTime + this.currentLockDelay + this.currentLockDelay) < System.currentTimeMillis() ) { - // The state was not been updated for more than twice the lock_delay value of the current master... - // Try to steal the lock.... - lockAquired = acquireLock(statements.getLockUpdateIdStatementToStealLock(uniqueId, state, lock_delay, currentId, currentState)); + try (Connection connection = getConnection()) { + // Try to acquire/update the lock state + boolean lockAquired = acquireLock(connection, statements.getLockUpdateIdStatement(uniqueId, ++state, lock_delay, uniqueId)); + + if (!lockAquired) { + + String lockSelectStatement = statements.getLockSelectStatement(); + + try (PreparedStatement statement = connection.prepareStatement(lockSelectStatement)) { + // Get the current master id and compare with information that we have locally.... + try (ResultSet rs = statement.executeQuery()) { + + if (rs.next()) { + int currentId = statements.getIdFromLockSelectStatement(rs); // The current master unique id or 0 + int currentState = statements.getStateFromLockSelectStatement(rs); // The current master state or whatever + + if (this.currentId == currentId) { + // It is the same instance that locked the table + if (this.currentState == currentState) { + // Its state has not been updated.... + if ((this.currentStateTime + this.currentLockDelay + this.currentLockDelay) < System.currentTimeMillis()) { + // The state was not been updated for more than twice the lock_delay value of the current master... + // Try to steal the lock.... + lockAquired = acquireLock(connection, statements.getLockUpdateIdStatementToStealLock(uniqueId, state, lock_delay, currentId, currentState)); + } + } else { + // Set the current time to be used to determine if we can + // try to steal the lock later... + this.currentStateTime = System.currentTimeMillis(); + this.currentState = currentState; + } + } else { + // This is a different currentId that is being used... + // at this time, it does not matter if the new master id is zero we can try to acquire it + // during the next lock call... + this.currentId = currentId; + this.currentState = currentState; + // Update the current state time since this is a new lock service... + this.currentStateTime = System.currentTimeMillis(); + // Get the lock delay value which is specific to the current master... + this.currentLockDelay = statements.getLockDelayFromLockSelectStatement(rs); } - } else { - // Set the current time to be used to determine if we can - // try to steal the lock later... - this.currentStateTime = System.currentTimeMillis(); - this.currentState = currentState; } - } else { - // This is a different currentId that is being used... - // at this time, it does not matter if the new master id is zero we can try to acquire it - // during the next lock call... - this.currentId = currentId; - this.currentState = currentState; - // Update the current state time since this is a new lock service... - this.currentStateTime = System.currentTimeMillis(); - // Get the lock delay value which is specific to the current master... - this.currentLockDelay = statements.getLockDelayFromLockSelectStatement(rs); } + } catch (Exception e) { + LOG.log(Level.SEVERE, "Unable to determine if the lock was obtain", e); } - } catch( Exception e ) { - LOG.log(Level.SEVERE, "Unable to determine if the lock was obtain", e); - } finally { - closeSafely(statement); - closeSafely(rs); } + + return lockAquired; + } catch (Exception e) { + LOG.log(Level.SEVERE, "Error while trying to obtain the lock", e); + return false; } - - return lockAquired; } /** @@ -506,22 +405,15 @@ public class GenericJDBCLock implements Lock { * @param lockUpdateIdStatement The sql statement used to execute the update. * @return True, if the row was updated else false. */ - private boolean acquireLock(String lockUpdateIdStatement) { - PreparedStatement preparedStatement = null; - boolean lockAquired = false; - - try { - preparedStatement = getConnection().prepareStatement(lockUpdateIdStatement); + private boolean acquireLock(Connection connection, String lockUpdateIdStatement) { + try (PreparedStatement preparedStatement = connection.prepareStatement(lockUpdateIdStatement)) { // This will only update the row that contains the ID of 0 or curId - lockAquired = preparedStatement.executeUpdate() > 0; + return preparedStatement.executeUpdate() > 0; } catch (Exception e) { // Do we want to display this message everytime??? LOG.log(Level.WARNING, "Failed to acquire database lock", e); - } finally { - closeSafely(preparedStatement); + return false; } - - return lockAquired; } /** @@ -532,27 +424,15 @@ public class GenericJDBCLock implements Lock { * @see org.apache.karaf.main.lock.Lock#release() */ public void release() throws Exception { - if (isConnected()) { + try (Connection connection = getConnection()) { String lockResetIdStatement = statements.getLockResetIdStatement(uniqueId); - PreparedStatement preparedStatement = null; - - try { - preparedStatement = getConnection().prepareStatement(lockResetIdStatement); + try (PreparedStatement preparedStatement = connection.prepareStatement(lockResetIdStatement)) { // This statement will set the ID to 0 and allow others to steal the lock... preparedStatement.executeUpdate(); - } catch (SQLException e) { - LOG.log(Level.SEVERE, "Exception while rollbacking the connection on release", e); - } finally { - closeSafely(preparedStatement); - try { - getConnection().close(); - } catch (SQLException ignored) { - LOG.log(Level.FINE, "Exception while closing connection on release", ignored); - } } + } catch (SQLException e) { + LOG.log(Level.SEVERE, "Exception while releasing lock", e); } - - lockConnection = null; } /** @@ -565,11 +445,6 @@ public class GenericJDBCLock implements Lock { * */ public boolean isAlive() throws Exception { - if (!isConnected()) { - LOG.severe("Lost lock!"); - return false; - } - return lock(); }
