// Copyright 2012 Citrix Systems, Inc. Licensed under the // Apache License, Version 2.0 (the "License"); you may not use this // file except in compliance with the License. Citrix Systems, Inc. // reserves all rights not expressly granted by 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. // // Automatically generated by addcopyright.py at 04/03/2012 package com.cloud.utils.db; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TimeZone; import javax.management.StandardMBean; import org.apache.log4j.Logger; import com.cloud.utils.DateUtil; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.mgmt.JmxUtil; import com.cloud.utils.time.InaccurateClock; public class Merovingian2 extends StandardMBean implements MerovingianMBean { private static final Logger s_logger = Logger.getLogger(Merovingian2.class); private static final String ACQUIRE_SQL = "INSERT INTO op_lock (op_lock.key, op_lock.mac, op_lock.ip, op_lock.thread, op_lock.acquired_on, waiters) VALUES (?, ?, ?, ?, ?, 1)"; private static final String INCREMENT_SQL = "UPDATE op_lock SET waiters=waiters+1 where op_lock.key=? AND op_lock.mac=? AND op_lock.ip=? AND op_lock.thread=?"; private static final String SELECT_SQL = "SELECT op_lock.key, mac, ip, thread, acquired_on, waiters FROM op_lock"; private static final String INQUIRE_SQL = SELECT_SQL + " WHERE op_lock.key=?"; private static final String DECREMENT_SQL = "UPDATE op_lock SET waiters=waiters-1 where op_lock.key=? AND op_lock.mac=? AND op_lock.ip=? AND op_lock.thread=?"; private static final String RELEASE_LOCK_SQL = "DELETE FROM op_lock WHERE op_lock.key = ?"; private static final String RELEASE_SQL = RELEASE_LOCK_SQL + " AND op_lock.mac=? AND waiters=0"; private static final String CLEANUP_MGMT_LOCKS_SQL = "DELETE FROM op_lock WHERE op_lock.mac = ?"; private static final String SELECT_MGMT_LOCKS_SQL = SELECT_SQL + " WHERE mac=?"; private static final String SELECT_THREAD_LOCKS_SQL = SELECT_SQL + " WHERE mac=? AND ip=?"; private static final String CLEANUP_THREAD_LOCKS_SQL = "DELETE FROM op_lock WHERE mac=? AND ip=? AND thread=?"; TimeZone s_gmtTimeZone = TimeZone.getTimeZone("GMT"); private final long _msId; private static Merovingian2 s_instance = null; private ConnectionConcierge _concierge = null; private static ThreadLocal<Count> s_tls = new ThreadLocal<Count>(); private Merovingian2(long msId) { super(MerovingianMBean.class, false); _msId = msId; Connection conn = null; try { conn = Transaction.getStandaloneConnectionWithException(); conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); conn.setAutoCommit(true); _concierge = new ConnectionConcierge("LockMaster", conn, true); } catch (SQLException e) { s_logger.error("Unable to get a new db connection", e); throw new CloudRuntimeException("Unable to initialize a connection to the database for locking purposes: ", e); } } public static synchronized Merovingian2 createLockMaster(long msId) { assert s_instance == null : "No lock can serve two masters. Either he will hate the one and love the other, or he will be devoted to the one and despise the other."; s_instance = new Merovingian2(msId); s_instance.cleanupThisServer(); try { JmxUtil.registerMBean("Locks", "Locks", s_instance); } catch (Exception e) { s_logger.error("Unable to register for JMX", e); } return s_instance; } public static Merovingian2 getLockMaster() { return s_instance; } protected void incrCount() { Count count = s_tls.get(); if (count == null) { count = new Count(); s_tls.set(count); } count.count++; } protected void decrCount() { Count count = s_tls.get(); if (count == null) { return; } count.count--; } public boolean acquire(String key, int timeInSeconds) { Thread th = Thread.currentThread(); String threadName = th.getName(); int threadId = System.identityHashCode(th); if (s_logger.isTraceEnabled()) { s_logger.trace("Acquiring lck-" + key + " with wait time of " + timeInSeconds); } long startTime = InaccurateClock.getTime(); while ((InaccurateClock.getTime() - startTime) < (timeInSeconds * 1000)) { int count = owns(key); if (count >= 1) { return increment(key, threadName, threadId); } else if (count == 0) { if (doAcquire(key, threadName, threadId)) { return true; } } try { if (s_logger.isTraceEnabled()) { s_logger.trace("Sleeping more time while waiting for lck-" + key); } Thread.sleep(5000); } catch (InterruptedException e) { } } if (s_logger.isTraceEnabled()) { s_logger.trace("Timed out on acquiring lock " + key + ". Waited for " + (InaccurateClock.getTime() - startTime)); } return false; } protected boolean increment(String key, String threadName, int threadId) { PreparedStatement pstmt = null; try { pstmt = _concierge.conn().prepareStatement(INCREMENT_SQL); pstmt.setString(1, key); pstmt.setLong(2, _msId); pstmt.setString(3, threadName); pstmt.setInt(4, threadId); int rows = pstmt.executeUpdate(); assert (rows <= 1) : "hmm...non unique key? " + pstmt; if (s_logger.isTraceEnabled()) { s_logger.trace("lck-" + key + (rows == 1 ? " acquired again" : " failed to acquire again")); } if (rows == 1) { incrCount(); return true; } return false; } catch (SQLException e) { throw new CloudRuntimeException("Unable to increment " + key, e); } finally { try { if (pstmt != null) { pstmt.close(); } } catch (SQLException e) { } } } protected boolean doAcquire(String key, String threadName, int threadId) { PreparedStatement pstmt = null; long startTime = InaccurateClock.getTime(); try { pstmt = _concierge.conn().prepareStatement(ACQUIRE_SQL); pstmt.setString(1, key); pstmt.setLong(2, _msId); pstmt.setString(3, threadName); pstmt.setInt(4, threadId); pstmt.setString(5, DateUtil.getDateDisplayString(s_gmtTimeZone, new Date())); try { int rows = pstmt.executeUpdate(); if (rows == 1) { if (s_logger.isTraceEnabled()) { s_logger.trace("Acquired for lck-" + key); } incrCount(); return true; } } catch(SQLException e) { if (!(e.getSQLState().equals("23000") && e.getErrorCode() == 1062)) { throw new CloudRuntimeException("Unable to lock " + key + ". Waited " + (InaccurateClock.getTime() - startTime), e); } } } catch(SQLException e) { throw new CloudRuntimeException("Unable to lock " + key + ". Waited " + (InaccurateClock.getTime() - startTime), e); } finally { try { if (pstmt != null) { pstmt.close(); } } catch (SQLException e) { } } s_logger.trace("Unable to acquire lck-" + key); return false; } protected Map<String, String> isLocked(String key) { PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = _concierge.conn().prepareStatement(INQUIRE_SQL); pstmt.setString(1, key); rs = pstmt.executeQuery(); if (!rs.next()) { return null; } return toLock(rs); } catch (SQLException e) { throw new CloudRuntimeException("SQL Exception on inquiry", e); } finally { try { if (rs != null) { rs.close(); } if (pstmt != null) { pstmt.close(); } } catch (SQLException e) { s_logger.warn("Unexpected SQL exception " + e.getMessage(), e); } } } public void cleanupThisServer() { cleanupForServer(_msId); } @Override public void cleanupForServer(long msId) { s_logger.info("Cleaning up locks for " + msId); PreparedStatement pstmt = null; try { pstmt = _concierge.conn().prepareStatement(CLEANUP_MGMT_LOCKS_SQL); pstmt.setLong(1, msId); int rows = pstmt.executeUpdate(); s_logger.info("Released " + rows + " locks for " + msId); } catch (SQLException e) { throw new CloudRuntimeException("Unable to clear the locks", e); } finally { try { if (pstmt != null) { pstmt.close(); } } catch (SQLException e) { } } } public boolean release(String key) { PreparedStatement pstmt = null; Thread th = Thread.currentThread(); String threadName = th.getName(); int threadId = System.identityHashCode(th); try { pstmt = _concierge.conn().prepareStatement(DECREMENT_SQL); pstmt.setString(1, key); pstmt.setLong(2, _msId); pstmt.setString(3, threadName); pstmt.setLong(4, threadId); int rows = pstmt.executeUpdate(); assert (rows <= 1) : "hmmm....keys not unique? " + pstmt; if (s_logger.isTraceEnabled()) { s_logger.trace("lck-" + key + " released"); } if (rows == 1) { pstmt.close(); pstmt = _concierge.conn().prepareStatement(RELEASE_SQL); pstmt.setString(1, key); pstmt.setLong(2, _msId); int result = pstmt.executeUpdate(); if (result == 1 && s_logger.isTraceEnabled()) { s_logger.trace("lck-" + key + " removed"); } decrCount(); } else if (rows < 1) { s_logger.warn("Was unable to find lock for the key " + key + " and thread id " + threadId); } return rows == 1; } catch (SQLException e) { throw new CloudRuntimeException("Unable to release " + key, e); } finally { try { if (pstmt != null) { pstmt.close(); } } catch (SQLException e) { } } } protected Map<String, String> toLock(ResultSet rs) throws SQLException { Map<String, String> map = new HashMap<String, String>(); map.put("key", rs.getString(1)); map.put("mgmt", rs.getString(2)); map.put("name", rs.getString(3)); map.put("tid", Integer.toString(rs.getInt(4))); map.put("date", rs.getString(5)); map.put("count", Integer.toString(rs.getInt(6))); return map; } protected List<Map<String, String>> toLocks(ResultSet rs) throws SQLException { LinkedList<Map<String, String>> results = new LinkedList<Map<String, String>>(); while (rs.next()) { results.add(toLock(rs)); } return results; } protected List<Map<String, String>> getLocks(String sql, Long msId) { PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = _concierge.conn().prepareStatement(sql); if (msId != null) { pstmt.setLong(1, msId); } rs = pstmt.executeQuery(); return toLocks(rs); } catch (SQLException e) { throw new CloudRuntimeException("Unable to retrieve locks ", e); } finally { try { if (rs != null) { rs.close(); } if (pstmt != null) { pstmt.close(); } } catch(SQLException e) { } } } @Override public List<Map<String, String>> getAllLocks() { return getLocks(SELECT_SQL, null); } @Override public List<Map<String, String>> getLocksAcquiredByThisServer() { return getLocks(SELECT_MGMT_LOCKS_SQL, _msId); } public int owns(String key) { Thread th = Thread.currentThread(); int threadId = System.identityHashCode(th); Map<String, String> owner = isLocked(key); if (owner == null) { return 0; } if (owner.get("mgmt").equals(Long.toString(_msId)) && owner.get("tid").equals(Integer.toString(threadId))) { return Integer.parseInt(owner.get("count")); } return -1; } public List<Map<String, String>> getLocksAcquiredBy(long msId, String threadName) { PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = _concierge.conn().prepareStatement(SELECT_THREAD_LOCKS_SQL); pstmt.setLong(1, msId); pstmt.setString(2, threadName); rs = pstmt.executeQuery(); return toLocks(rs); } catch (SQLException e) { throw new CloudRuntimeException("Can't get locks " + pstmt, e); } finally { try { if (rs != null) { rs.close(); } if (pstmt != null) { pstmt.close(); } } catch (SQLException e) { } } } public void cleanupThread() { Count count = s_tls.get(); if (count == null || count.count == 0) { return; } int c = count.count; count.count = 0; Thread th = Thread.currentThread(); String threadName = th.getName(); int threadId = System.identityHashCode(th); PreparedStatement pstmt = null; try { pstmt = _concierge.conn().prepareStatement(CLEANUP_THREAD_LOCKS_SQL); pstmt.setLong(1, _msId); pstmt.setString(2, threadName); pstmt.setInt(3, threadId); int rows = pstmt.executeUpdate(); assert (false) : "Abandon hope, all ye who enter here....There were still " + rows + ":" + c + " locks not released when the transaction ended, check for lock not released or @DB is not added to the code that using the locks!"; } catch (SQLException e) { throw new CloudRuntimeException("Can't clear locks " + pstmt, e); } finally { try { if (pstmt != null) { pstmt.close(); } } catch (SQLException e) { } } } @Override public boolean releaseLockAsLastResortAndIReallyKnowWhatIAmDoing(String key) { s_logger.info("Releasing a lock from JMX lck-" + key); PreparedStatement pstmt = null; try { pstmt = _concierge.conn().prepareStatement(RELEASE_LOCK_SQL); pstmt.setString(1, key); int rows = pstmt.executeUpdate(); return rows > 0; } catch (SQLException e) { s_logger.error("Unable to release lock " + key, e); return false; } } protected static class Count { public int count = 0; } }