// 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.net.InetAddress; import java.net.UnknownHostException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Savepoint; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.log4j.Logger; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.MacAddress; import com.cloud.utils.time.InaccurateClock; public class Merovingian { private static final Logger s_logger = Logger.getLogger(Merovingian.class); private static final String ACQUIRE_SQL = "INSERT IGNORE INTO op_lock (op_lock.key, op_lock.mac, op_lock.ip, op_lock.thread) VALUES (?, ?, ?, ?)"; private static final String INQUIRE_SQL = "SELECT op_lock.ip FROM op_lock WHERE op_lock.key = ?"; private static final String RELEASE_SQL = "DELETE FROM op_lock WHERE op_lock.key = ?"; private static final String CLEAR_SQL = "DELETE FROM op_lock WHERE op_lock.mac = ? AND op_lock.ip = ?"; private final static HashMap<String, Pair<Lock, Integer>> s_memLocks = new HashMap<String, Pair<Lock, Integer>>(1027); private final LinkedHashMap<String, Ternary<Savepoint, Integer, Long>> _locks = new LinkedHashMap<String, Ternary<Savepoint, Integer, Long>>(); private int _previousIsolation = Connection.TRANSACTION_NONE; private final static String s_macAddress; private final static String s_ipAddress; static { s_macAddress = MacAddress.getMacAddress().toString(":"); String address = null; try { InetAddress addr = InetAddress.getLocalHost(); address = addr.getHostAddress().toString(); } catch (UnknownHostException e) { address = "127.0.0.1"; } s_ipAddress = address; } Connection _conn = null; public Merovingian(short dbId) { _conn = null; } protected void checkIsolationLevel(Connection conn) throws SQLException { PreparedStatement pstmt = conn.prepareStatement("SELECT @@global.tx_isolation, @@session.tx_isolation;"); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { s_logger.info("global isolation = " + rs.getString(1)); s_logger.info("session isolation = " + rs.getString(2)); } } protected Connection getConnection(String key, boolean test) { try { if (_conn != null) { return _conn; } _conn = Transaction.getStandaloneConnection(); if (_previousIsolation == Connection.TRANSACTION_NONE) { _previousIsolation = _conn.getTransactionIsolation(); _conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); if (!test && !_conn.getAutoCommit()) { _conn.setAutoCommit(false); } } return _conn; } catch (SQLException e) { try { _conn.rollback(); } catch (SQLException e1) { } throw new CloudRuntimeException("Unable to acquire db connection for locking " + key, e); } } public boolean acquire(String key, int timeInSeconds) { Pair<Lock, Integer> memLock = null; boolean acquiredDbLock = false; boolean acquiredMemLock = false; try { synchronized(s_memLocks) { memLock = s_memLocks.get(key); if (memLock == null) { Lock l = new ReentrantLock(true); memLock = new Pair<Lock, Integer>(l, 0); s_memLocks.put(key, memLock); } memLock.second(memLock.second() + 1); } if (!memLock.first().tryLock(timeInSeconds, TimeUnit.SECONDS)) { return false; } acquiredMemLock = true; Ternary<Savepoint, Integer, Long> lock = _locks.get(key); if (lock != null) { lock.second(lock.second() + 1); if (s_logger.isTraceEnabled()) { s_logger.trace("Lock: Reacquiring " + key + " Count: " + lock.second()); } acquiredDbLock = true; return true; } long startTime = InaccurateClock.getTime(); while ((InaccurateClock.getTime() - startTime) < (timeInSeconds * 1000)) { if (isLocked(key)) { try { Thread.sleep(1000); } catch (InterruptedException e) { } } else { acquiredDbLock = doAcquire(key); if (acquiredDbLock) { return true; } } } if (s_logger.isTraceEnabled()) { s_logger.trace("Lock: Timed out on acquiring lock " + key); } return false; } catch (InterruptedException e) { s_logger.debug("Interrupted while trying to acquire " + key); return false; } finally { if (!acquiredMemLock || !acquiredDbLock) { synchronized(s_memLocks) { if (memLock.second(memLock.second() - 1) <= 0) { s_memLocks.remove(key); } } } if (acquiredMemLock && !acquiredDbLock) { memLock.first().unlock(); } } } protected boolean doAcquire(String key) { Connection conn = getConnection(key, true); PreparedStatement pstmt = null; Savepoint sp = null; try { sp = conn.setSavepoint(key); } catch (SQLException e) { s_logger.warn("Unable to set save point " + key); return false; } try { long startTime = InaccurateClock.getTime(); try { pstmt = conn.prepareStatement(ACQUIRE_SQL); pstmt.setString(1, key); pstmt.setString(2, s_macAddress); pstmt.setString(3, s_ipAddress); pstmt.setString(4, Thread.currentThread().getName()); String exceptionMessage = null; int rows = pstmt.executeUpdate(); if (rows == 1) { if (s_logger.isTraceEnabled()) { s_logger.trace("Lock: lock acquired for " + key); } Ternary<Savepoint, Integer, Long> lock = new Ternary<Savepoint, Integer, Long>(sp, 1, InaccurateClock.getTime()); _locks.put(key, lock); return true; } } catch(SQLException e) { s_logger.warn("Lock: Retrying lock " + key + ". Waited " + (InaccurateClock.getTime() - startTime), e); } conn.rollback(sp); s_logger.trace("Lock: Unable to acquire DB lock " + key); } catch (SQLException e) { s_logger.warn("Lock: Unable to acquire db connection for locking " + key, e); } finally { if (pstmt != null) { try { pstmt.close(); } catch (SQLException e) { } } } return false; } public boolean isLocked(String key) { Connection conn = getConnection(key, false); PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = conn.prepareStatement(INQUIRE_SQL); pstmt.setString(1, key); rs = pstmt.executeQuery(); return rs.next(); } catch (SQLException e) { s_logger.warn("SQL exception " + e.getMessage(), 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 clear() { if (_locks.size() == 0) { return; } Set<String> keys = new HashSet<String>(_locks.keySet()); // // disable assertion, when assert support is enabled, it throws an exception // which eventually eats the following on important messages for diagnostic // // assert (false) : "Who acquired locks but didn't release them? " + keys.toArray(new String[keys.size()]); for (String key : keys) { s_logger.warn("Lock: This is not good guys! Automatically releasing lock: " + key); release(key); } _locks.clear(); } public boolean release(String key) { boolean validLock = false; try { assert _locks.size() > 0 : "There are no locks here. Why are you trying to release " + key; Ternary<Savepoint, Integer, Long> lock = _locks.get(key); if (lock != null) { validLock = true; if (lock.second() > 1) { lock.second(lock.second() - 1); if (s_logger.isTraceEnabled()) { s_logger.trace("Lock: Releasing " + key + " but not in DB " + lock.second()); } return false; } if (s_logger.isDebugEnabled() && !_locks.keySet().iterator().next().equals(key)) { s_logger.trace("Lock: Releasing out of order for " + key); } _locks.remove(key); if (s_logger.isTraceEnabled()) { s_logger.trace("Lock: Releasing " + key + " after " + (InaccurateClock.getTime() - lock.third())); } Connection conn = getConnection(key, true); conn.rollback(lock.first()); } else { s_logger.warn("Merovingian.release() is called against key " + key + " but the lock of this key does not exist!"); } if (_locks.size() == 0) { closeConnection(); } } catch (SQLException e) { s_logger.warn("unable to rollback for " + key); } finally { synchronized(s_memLocks) { Pair<Lock, Integer> memLock = s_memLocks.get(key); if(memLock != null) { memLock.second(memLock.second() - 1); if (memLock.second() <= 0) { s_memLocks.remove(key); } if(validLock) memLock.first().unlock(); } else { throw new CloudRuntimeException("Merovingian.release() is called for key " + key + ", but its memory lock no longer exist! This is not good, guys"); } } } return true; } public void closeConnection() { try { if (_conn == null) { _previousIsolation = Connection.TRANSACTION_NONE; return; } if (_previousIsolation != Connection.TRANSACTION_NONE) { _conn.setTransactionIsolation(_previousIsolation); } try { // rollback just in case but really there shoul be nothing. _conn.rollback(); } catch (SQLException e) { } _conn.setAutoCommit(true); _previousIsolation = Connection.TRANSACTION_NONE; _conn.close(); _conn = null; } catch (SQLException e) { s_logger.warn("Unexpected SQL exception " + e.getMessage(), e); } } }