/*************************GO-LICENSE-START********************************* * Copyright 2014 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with 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. *************************GO-LICENSE-END***********************************/ package com.thoughtworks.go.agent.launcher; import java.io.File; import java.io.IOException; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class Lockfile implements Runnable { private static final Log LOG = LogFactory.getLog(Lockfile.class); static final String TOUCH_FREQUENCY_PROPERTY = "lockfile.touch.frequency.mins"; static final String SLEEP_TIME_FOR_LAST_MODIFIED_CHECK_PROPERTY = "lockfile.sleep.before.lastmodified.check.secs"; private static final int DEFAULT_TOUCH_FREQUENCY = 5 * 60 * 1000; // 5 minutes private static final int DEFAULT_SLEEP_TIME_FOR_LAST_MODIFIED_CHECK = 10000; // 10 secs private final File lockFile; private final int touchFrequency; private Thread touchLoop; private volatile boolean keepRunning; private final int sleepTimeBeforeLastModifiedCheck; public Lockfile(File filename) { this.lockFile = filename; this.touchFrequency = getIntProperty(TOUCH_FREQUENCY_PROPERTY, DEFAULT_TOUCH_FREQUENCY); this.sleepTimeBeforeLastModifiedCheck = getIntProperty(SLEEP_TIME_FOR_LAST_MODIFIED_CHECK_PROPERTY, DEFAULT_SLEEP_TIME_FOR_LAST_MODIFIED_CHECK); } private int getIntProperty(String propertyName, int defaultValue) { int propValue = defaultValue; String propValueSet = System.getProperty(propertyName); if (propValueSet != null) { try { propValue = Integer.parseInt(propValueSet); } catch (Exception e) { } } return propValue; } boolean exists() { return lockFile.exists() && lockFileChangedWithinMinutes(10); } boolean lockFileChangedWithinMinutes(int minutes) { sleepForSomeTime(); // prevents race condition where the file was just created and not modified. long changedAgo = System.currentTimeMillis() - lockFile.lastModified(); return changedAgo < minutes * 60 * 1000; } private void sleepForSomeTime() { try { LOG.info("Sleeping for " + sleepTimeBeforeLastModifiedCheck + " secs to before 'last modified check'..."); Thread.sleep(sleepTimeBeforeLastModifiedCheck); } catch (InterruptedException e) { } } void setHooks() throws IOException { touch(); spawnTouchLoop(); } void touch() throws IOException { FileUtils.touch(lockFile); } synchronized void spawnTouchLoop() { if (touchLoop == null) { keepRunning = true; touchLoop = new Thread(this); touchLoop.setName("TouchLoop" + touchLoop.getName()); touchLoop.setDaemon(true); touchLoop.start(); } } public String toString() { return "<Lockfile: " + lockFile.getAbsolutePath() + ">"; } public void run() { LOG.info("Using lock file: " + lockFile.getAbsolutePath()); do { try { touch(); Thread.sleep(touchFrequency); } catch (Exception e) { return; } } while (keepRunning); } public synchronized void delete() { requestTouchLoopToStop(); waitForTouchLoopThread(); try { deleteLockFile(); } catch (Exception e) { LOG.error("Error deleting lock file at " + lockFile.getAbsolutePath(), e); } } private void requestTouchLoopToStop() { keepRunning = false; if (touchLoop != null) { touchLoop.interrupt(); } } private void deleteLockFile() { if (touchLoop == null) { //I haven't got the lock return; } if (!lockFile.exists()) { LOG.warn("No lock file found at " + lockFile.getAbsolutePath()); return; } if (!lockFile.delete()) { LOG.error("Unable to delete lock file at " + lockFile.getAbsolutePath()); } } private void waitForTouchLoopThread() { try { if (touchLoop != null) { // 10 secs for join touchLoop.join(10000); } } catch (InterruptedException e) { } } public boolean tryLock() { if (exists()) { String alreadyRunningMsg = "Already running agent launcher in this folder."; System.out.println(alreadyRunningMsg); LOG.error(alreadyRunningMsg); return false; } try { setHooks(); return true; } catch (IOException e) { String lockFileFailMsg = "Unable to lock file (" + lockFile.getAbsolutePath() + ")"; System.err.println(lockFileFailMsg); LOG.error(lockFileFailMsg, e); return false; } } }