All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.blagerweij.sessionlock.SessionLockService Maven / Gradle / Ivy

Go to download

Replaces the default DATABASECHANGELOGLOCK with a RDBMS lock, which is automatically released when the container is stopped unexpectedly

The newest version!
/*
 * This module, both source code and documentation,
 * is in the Public Domain, and comes with NO WARRANTY.
 */
package com.github.blagerweij.sessionlock;

import java.sql.Connection;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.Date;
import liquibase.database.Database;
import liquibase.database.DatabaseConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LockException;
import liquibase.lockservice.DatabaseChangeLogLock;
import liquibase.lockservice.LockService;
import liquibase.logging.LogFactory;
import liquibase.logging.Logger;

/**
 * Abstract base for {@code LockService} implementations that provide session-level
 * (vs. transaction-level) locking. Session-level locks get automatically released if
 * the database connection drops, and overcome the shortcoming of the {@code
 * StandardLockService}:
 *
 * 
* *

If Liquibase does not exit cleanly, the lock row may be left as locked. You can clear out the * current lock by running liquibase releaseLocks which runs UPDATE * DATABASECHANGELOGLOCK SET LOCKED=0 * *

* *

Running liquibase releaseLocks in a micro-service production environment is not * really feasible. * *

Subclasses need to override {@link #supports(Database)}. If {@code listLocks} necessary to * provide actual info, {@link #usedLock(Connection)} has to be overridden, also. */ public abstract class SessionLockService implements LockService { protected long changeLogLockWaitTime = 5; // max wait time in minutes protected long changeLogLockRecheckTime = 5; // recheck interval in seconds protected boolean hasChangeLogLock; protected Database database; /** This implementation returns {@code super.getPriority() + 1}. */ @Override public int getPriority() { return PRIORITY_DEFAULT + 1; } @Override public void setChangeLogLockWaitTime(long changeLogLockWaitTime) { this.changeLogLockWaitTime = changeLogLockWaitTime; } @Override public void setChangeLogLockRecheckTime(long changeLogLocRecheckTime) { this.changeLogLockRecheckTime = changeLogLocRecheckTime; } /** This implementation returns {@code false}. */ @Override public boolean supports(Database database) { return false; } @Override public void waitForLock() throws LockException { boolean locked = false; final long timeToGiveUp = new Date().getTime() + (changeLogLockWaitTime * 1000 * 60); while (!locked && (new Date().getTime() < timeToGiveUp)) { locked = acquireLock(); if (!locked) { getLog(getClass()).info("Waiting for changelog lock...."); try { Thread.sleep(changeLogLockRecheckTime * 1000); } catch (InterruptedException e) { // Restore thread interrupt status Thread.currentThread().interrupt(); } } } if (!locked) { DatabaseChangeLogLock[] locks = listLocks(); String lockedBy; if (locks.length > 0) { DatabaseChangeLogLock lock = locks[0]; lockedBy = lock.getLockedBy() + " since " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) .format(lock.getLockGranted()); } else { lockedBy = "UNKNOWN"; } throw new LockException( "Could not acquire change log lock. Currently locked by " + lockedBy); } } @Override public void forceReleaseLock() throws LockException { try { releaseLock(getConnection()); getLog(getClass()).info("Successfully force-released change log lock"); } catch (SQLException e) { throw new LockException(e); } finally { hasChangeLogLock = false; } } @Override public void reset() { try { if (hasChangeLogLock) { forceReleaseLock(); } } catch (LockException e) { getLog(getClass()).debug("Could not reset lock, ignoring", e); } } @Override public void destroy() { reset(); } @Override public void setDatabase(Database database) { this.database = database; } @Override public void init() { } private Connection getConnection() throws LockException { DatabaseConnection dbCon = database.getConnection(); if (dbCon instanceof JdbcConnection) { return ((JdbcConnection) dbCon).getUnderlyingConnection(); } throw new LockException("Not a JdbcConnection: " + dbCon); } @Override public boolean acquireLock() throws LockException { if (hasChangeLogLock) { getLog(getClass()).debug("Skipped acquiring of already existing change log lock"); return true; } try { if (acquireLock(getConnection())) { hasChangeLogLock = true; getLog(getClass()).info("Successfully acquired change log lock"); return true; } return false; } catch (SQLException e) { throw new LockException(e); } } /** * Attempts to acquire lock for the associated {@link #database} (schema) using the given * connection. * * @param con the connection identifying and used by the database session. * @return {@code true} if lock successfully obtained, or {@code false} if lock is held by another * session. * @throws SQLException if a database access error occurs; * @throws LockException if other logical error happens, preventing the operation from completing * normally. * @see #acquireLock() */ protected abstract boolean acquireLock(Connection con) throws SQLException, LockException; @Override public void releaseLock() throws LockException { if (!hasChangeLogLock) { getLog(getClass()).debug("Skipped releasing of nonexistent change log lock"); return; } try { releaseLock(getConnection()); getLog(getClass()).info("Successfully released change log lock"); } catch (SQLException e) { throw new LockException(e); } finally { hasChangeLogLock = false; } } /** * Releases the lock previously obtained by {@code acquireLock()}. * * @param con the connection identifying and used by the database session. * @throws SQLException if a database access error occurs; * @throws LockException if other logical error happens, preventing the operation from completing * normally. * @see #releaseLock() * @see #acquireLock(Connection) */ protected abstract void releaseLock(Connection con) throws SQLException, LockException; @Override public DatabaseChangeLogLock[] listLocks() throws LockException { try { DatabaseChangeLogLock usedLock = usedLock(getConnection()); return (usedLock == null) ? new DatabaseChangeLogLock[0] : new DatabaseChangeLogLock[] {usedLock}; } catch (SQLException e) { throw new LockException(e); } } @Override public boolean hasChangeLogLock() { return hasChangeLogLock; } /** * This implementation returns {@code null}. * * @param con the connection identifying and used by the database session. * @return Information about the database changelog lock, or {@code null}. * @throws SQLException if a database access error occurs; * @throws LockException if other logical error happens, preventing the operation from completing * normally. * @see #listLocks() */ protected DatabaseChangeLogLock usedLock(Connection con) throws SQLException, LockException { return null; } protected static Logger getLog(Class clazz) { return LogFactory.getLogger(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy