/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan.util.locker; import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.lang.management.ManagementFactory; import java.util.Set; import fr.ens.biologie.genomique.eoulsan.util.ProcessUtils; import fr.ens.biologie.genomique.eoulsan.util.StringUtils; /** * This class define a lock to prevent execution of a process simultaneously on * multiples JVM. * @since 1.1 * @author Laurent Jourdren */ public class ExecLock implements Locker { private static final String LOCK_EXTENSION = ".lock"; private static final String PID_EXTENSION = ".pid"; private static final String PID_LOCK_EXTENSION = ".pidlock"; private static final int pid = getPid(); private final String execName; private final File tmpDir; private final File lockFile; private final File pidLockFile; private final File pidFile; private boolean lock; private static int getPid() { final String beanName = ManagementFactory.getRuntimeMXBean().getName(); final int index = beanName.indexOf('@'); return Integer.parseInt(beanName.substring(0, index)); } @Override public void lock() { while (this.lock) { sleep(5000); } try { if (!this.pidFile.createNewFile()) { throw new IOException( "Can not create pid file: " + this.pidFile.getAbsolutePath()); } int count = 0; do { if (count == 0) { checkLockJVMAlive(); } if (!this.lockFile.exists()) { if (checkPid()) { if (!this.lockFile.createNewFile()) { throw new IOException("Can not create lock file: " + this.lockFile.getAbsolutePath()); } if (!this.pidLockFile.createNewFile()) { throw new IOException("Can not create pid lock file: " + this.lockFile.getAbsolutePath()); } this.lock = true; return; } } if (count == 12) { count = 0; } count++; sleep(5000); } while (true); } catch (IOException e) { e.printStackTrace(); } } @Override public void unlock() { if (!this.lockFile.delete()) { getLogger().warning( "Can not delete lock file: " + this.lockFile.getAbsolutePath()); } if (!this.pidLockFile.delete()) { getLogger().warning("Can not delete pid lock file: " + this.pidLockFile.getAbsolutePath()); } if (!this.pidFile.delete()) { getLogger().warning( "Can not delete pid file: " + this.pidFile.getAbsolutePath()); } this.lock = false; sleep(10000); } /** * Check the pid that wait the resources are alive and if this JVM has the * oldest registered pid. * @return true if the JVM has the oldest registered pid. */ private boolean checkPid() { File[] files = this.tmpDir.listFiles(new FilenameFilter() { @Override public boolean accept(final File arg0, final String arg1) { return arg1.startsWith(ExecLock.this.execName + "-") && arg1.endsWith(PID_EXTENSION); } }); if (files == null) { return true; } Set<Integer> jvmPids = getJVMsPIDs(); int oldestPid = -1; long oldestPidFileDate = Long.MAX_VALUE; for (File f : files) { String basename = StringUtils.basename(f.getName()); int fPid; try { fPid = Integer.parseInt(basename.substring(this.execName.length() + 1)); } catch (NumberFormatException e) { continue; } if (!jvmPids.contains(fPid)) { if (!f.delete()) { getLogger() .warning("Can not delete pid file: " + f.getAbsolutePath()); } continue; } if (f.lastModified() < oldestPidFileDate) { oldestPidFileDate = f.lastModified(); oldestPid = fPid; } } return oldestPid == pid; } /** * Return a set withs pid of existing JVMs. * @return a set of integers with pid of existing JVMs */ private Set<Integer> getJVMsPIDs() { return ProcessUtils.getExecutablePids("java"); } /** * Sleep for n milliseconds * @param duration milliseconds to wait */ private void sleep(final int duration) { try { Thread.sleep(duration); } catch (InterruptedException e) { } } /** * Check that the JVM that lock the resource is alive. */ private void checkLockJVMAlive() { File[] files = this.tmpDir.listFiles(new FilenameFilter() { @Override public boolean accept(final File arg0, final String arg1) { return arg1.startsWith(ExecLock.this.execName + "-") && arg1.endsWith(PID_LOCK_EXTENSION); } }); if (files == null || files.length == 0) { if (!this.lockFile.delete()) { getLogger().warning( "Can not delete lock file: " + this.lockFile.getAbsolutePath()); } return; } final Set<Integer> jvmsPIDs = getJVMsPIDs(); int count = 0; for (File f : files) { String basename = StringUtils.basename(f.getName()); int fPid; try { fPid = Integer.parseInt(basename.substring(this.execName.length() + 1)); } catch (NumberFormatException e) { if (!f.delete()) { getLogger() .warning("Can not delete pid file: " + f.getAbsolutePath()); } continue; } if (jvmsPIDs.contains(fPid)) { count++; } else if (!f.delete()) { getLogger().warning("Can not delete pid file: " + f.getAbsolutePath()); } } if (count == 0) { if (!this.lockFile.delete()) { getLogger().warning( "Can not delete lock file: " + this.lockFile.getAbsolutePath()); } } } /** * Get the number of processes waiting. * @return the number of process waiting */ public int getProcessesWaiting() { File[] files = this.tmpDir.listFiles(new FilenameFilter() { @Override public boolean accept(final File arg0, final String arg1) { return arg1.startsWith(ExecLock.this.execName + "-") && arg1.endsWith(PID_EXTENSION); } }); if (files == null) { return 0; } return files.length; } // // Constructors // /** * Public constructor. * @param execName resource name * @param tmpDir temporary directory where to create lock files */ public ExecLock(final String execName, final File tmpDir) { this.execName = execName; this.tmpDir = tmpDir; this.lockFile = new File(this.tmpDir, this.execName + LOCK_EXTENSION); this.pidLockFile = new File(this.tmpDir, this.execName + "-" + pid + PID_LOCK_EXTENSION); this.pidFile = new File(this.tmpDir, this.execName + "-" + pid + PID_EXTENSION); } /** * Public constructor. * @param execName resource name */ public ExecLock(final String execName) { this(execName, new File("/tmp")); } }