001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.store.jdbc; 018 019import java.io.IOException; 020import java.sql.Connection; 021import java.sql.PreparedStatement; 022import java.sql.ResultSet; 023import java.sql.SQLException; 024import java.sql.Timestamp; 025import java.util.Date; 026import java.util.concurrent.TimeUnit; 027import org.apache.activemq.util.IOExceptionSupport; 028import org.apache.activemq.util.ServiceStopper; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032/** 033 * Represents an exclusive lease on a database to avoid multiple brokers running 034 * against the same logical database. 035 * 036 * @org.apache.xbean.XBean element="lease-database-locker" 037 * 038 */ 039public class LeaseDatabaseLocker extends AbstractJDBCLocker { 040 private static final Logger LOG = LoggerFactory.getLogger(LeaseDatabaseLocker.class); 041 042 protected int maxAllowableDiffFromDBTime = 0; 043 protected long diffFromCurrentTime = Long.MAX_VALUE; 044 protected String leaseHolderId; 045 protected boolean handleStartException; 046 047 public void doStart() throws Exception { 048 049 if (lockAcquireSleepInterval < lockable.getLockKeepAlivePeriod()) { 050 LOG.warn("LockableService keep alive period: " + lockable.getLockKeepAlivePeriod() 051 + ", which renews the lease, is greater than lockAcquireSleepInterval: " + lockAcquireSleepInterval 052 + ", the lease duration. These values will allow the lease to expire."); 053 } 054 055 LOG.info(getLeaseHolderId() + " attempting to acquire exclusive lease to become the master"); 056 String sql = getStatements().getLeaseObtainStatement(); 057 LOG.debug(getLeaseHolderId() + " locking Query is "+sql); 058 059 long now = 0l; 060 while (!isStopping()) { 061 Connection connection = null; 062 PreparedStatement statement = null; 063 try { 064 connection = getConnection(); 065 initTimeDiff(connection); 066 067 statement = connection.prepareStatement(sql); 068 setQueryTimeout(statement); 069 070 now = System.currentTimeMillis() + diffFromCurrentTime; 071 statement.setString(1, getLeaseHolderId()); 072 statement.setLong(2, now + lockAcquireSleepInterval); 073 statement.setLong(3, now); 074 075 int result = statement.executeUpdate(); 076 if (result == 1) { 077 // we got the lease, verify we still have it 078 if (keepAlive()) { 079 break; 080 } 081 } 082 083 reportLeasOwnerShipAndDuration(connection); 084 085 } catch (Exception e) { 086 LOG.warn(getLeaseHolderId() + " lease acquire failure: "+ e, e); 087 if (isStopping()) { 088 throw new Exception( 089 "Cannot start broker as being asked to shut down. " 090 + "Interrupted attempt to acquire lock: " 091 + e, e); 092 } 093 if (handleStartException) { 094 throw e; 095 } 096 } finally { 097 close(statement); 098 close(connection); 099 } 100 101 LOG.debug(getLeaseHolderId() + " failed to acquire lease. Sleeping for " + lockAcquireSleepInterval + " milli(s) before trying again..."); 102 TimeUnit.MILLISECONDS.sleep(lockAcquireSleepInterval); 103 } 104 if (isStopping()) { 105 throw new RuntimeException(getLeaseHolderId() + " failing lease acquire due to stop"); 106 } 107 108 LOG.info(getLeaseHolderId() + ", becoming master with lease expiry " + new Date(now + lockAcquireSleepInterval) + " on dataSource: " + dataSource); 109 } 110 111 private void reportLeasOwnerShipAndDuration(Connection connection) throws SQLException { 112 PreparedStatement statement = null; 113 try { 114 statement = connection.prepareStatement(getStatements().getLeaseOwnerStatement()); 115 ResultSet resultSet = statement.executeQuery(); 116 while (resultSet.next()) { 117 LOG.debug(getLeaseHolderId() + " Lease held by " + resultSet.getString(1) + " till " + new Date(resultSet.getLong(2))); 118 } 119 } finally { 120 close(statement); 121 } 122 } 123 124 protected long initTimeDiff(Connection connection) throws SQLException { 125 if (Long.MAX_VALUE == diffFromCurrentTime) { 126 if (maxAllowableDiffFromDBTime > 0) { 127 diffFromCurrentTime = determineTimeDifference(connection); 128 } else { 129 diffFromCurrentTime = 0l; 130 } 131 } 132 return diffFromCurrentTime; 133 } 134 135 protected long determineTimeDifference(Connection connection) throws SQLException { 136 try (PreparedStatement statement = connection.prepareStatement(getStatements().getCurrentDateTime()); 137 ResultSet resultSet = statement.executeQuery()) { 138 long result = 0l; 139 if (resultSet.next()) { 140 Timestamp timestamp = resultSet.getTimestamp(1); 141 long diff = System.currentTimeMillis() - timestamp.getTime(); 142 if (Math.abs(diff) > maxAllowableDiffFromDBTime) { 143 // off by more than maxAllowableDiffFromDBTime so lets adjust 144 result = (-diff); 145 } 146 LOG.info(getLeaseHolderId() + " diff adjust from db: " + result + ", db time: " + timestamp); 147 } 148 return result; 149 } 150 } 151 152 public void doStop(ServiceStopper stopper) throws Exception { 153 if (lockable.getBrokerService() != null && lockable.getBrokerService().isRestartRequested()) { 154 // keep our lease for restart 155 return; 156 } 157 releaseLease(); 158 } 159 160 private void releaseLease() { 161 Connection connection = null; 162 PreparedStatement statement = null; 163 try { 164 connection = getConnection(); 165 statement = connection.prepareStatement(getStatements().getLeaseUpdateStatement()); 166 statement.setString(1, null); 167 statement.setLong(2, 0l); 168 statement.setString(3, getLeaseHolderId()); 169 if (statement.executeUpdate() == 1) { 170 LOG.info(getLeaseHolderId() + ", released lease"); 171 } 172 } catch (Exception e) { 173 LOG.error(getLeaseHolderId() + " failed to release lease: " + e, e); 174 } finally { 175 close(statement); 176 close(connection); 177 } 178 } 179 180 @Override 181 public boolean keepAlive() throws IOException { 182 boolean result = false; 183 final String sql = getStatements().getLeaseUpdateStatement(); 184 LOG.debug(getLeaseHolderId() + ", lease keepAlive Query is " + sql); 185 186 Connection connection = null; 187 PreparedStatement statement = null; 188 try { 189 connection = getConnection(); 190 191 initTimeDiff(connection); 192 statement = connection.prepareStatement(sql); 193 setQueryTimeout(statement); 194 195 final long now = System.currentTimeMillis() + diffFromCurrentTime; 196 statement.setString(1, getLeaseHolderId()); 197 statement.setLong(2, now + lockAcquireSleepInterval); 198 statement.setString(3, getLeaseHolderId()); 199 200 result = (statement.executeUpdate() == 1); 201 202 if (!result) { 203 reportLeasOwnerShipAndDuration(connection); 204 } 205 } catch (Exception e) { 206 LOG.warn(getLeaseHolderId() + ", failed to update lease: " + e, e); 207 IOException ioe = IOExceptionSupport.create(e); 208 lockable.getBrokerService().handleIOException(ioe); 209 throw ioe; 210 } finally { 211 close(statement); 212 close(connection); 213 } 214 return result; 215 } 216 217 public String getLeaseHolderId() { 218 if (leaseHolderId == null) { 219 if (lockable.getBrokerService() != null) { 220 leaseHolderId = lockable.getBrokerService().getBrokerName(); 221 } 222 } 223 return leaseHolderId; 224 } 225 226 public void setLeaseHolderId(String leaseHolderId) { 227 this.leaseHolderId = leaseHolderId; 228 } 229 230 public int getMaxAllowableDiffFromDBTime() { 231 return maxAllowableDiffFromDBTime; 232 } 233 234 public void setMaxAllowableDiffFromDBTime(int maxAllowableDiffFromDBTime) { 235 this.maxAllowableDiffFromDBTime = maxAllowableDiffFromDBTime; 236 } 237 238 public boolean isHandleStartException() { 239 return handleStartException; 240 } 241 242 public void setHandleStartException(boolean handleStartException) { 243 this.handleStartException = handleStartException; 244 } 245 246 @Override 247 public String toString() { 248 return "LeaseDatabaseLocker owner:" + leaseHolderId + ",duration:" + lockAcquireSleepInterval + ",renew:" + lockAcquireSleepInterval; 249 } 250}