/* * Copyright (C) 2014 Shashank Tulsyan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package neembuu.release1.app; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import static java.nio.file.StandardOpenOption.*; import java.util.logging.Level; import java.util.logging.Logger; import neembuu.release1.api.log.LoggerUtil; import neembuu.util.Throwables; /** * * @author Shashank Tulsyan */ public final class EnsureSingleInstance { public static final long Instantiated = System.currentTimeMillis(); private long lastCall = Instantiated; private final Object lock = new Object(); private volatile Thread thread = null; private SingleInstanceCheckCallback callback; private FileLock fileLock = null; private final Logger l = LoggerUtil.getLogger(EnsureSingleInstance.class.getName()); public EnsureSingleInstance(){ } public EnsureSingleInstance(SingleInstanceCheckCallback callback){ this.callback = callback; } public void setCallback(SingleInstanceCheckCallback callback) { if(this.callback!=null)throw new IllegalStateException("Already intialized with "+this.callback); this.callback = callback; } public void startService(){ if(callback==null)throw new IllegalStateException("callback not intialized"); thread = Throwables.start(new Runnable() { @Override public void run() { boolean running = tryInstanceLock(); if(running){ l.info("It seems an instance is already running"); }else { l.log(Level.FINE, "Finished {0}", Thread.currentThread().getName()); } } },this.toString()); } public void stopService(){ thread = null; synchronized (lock){lock.notifyAll();} } private boolean tryInstanceLock(){ try(FileChannel fc = FileChannel.open(Application.getResource(".instance"), READ, WRITE,CREATE)) { acquireFileLock(fc); l.log(Level.FINE, "Initiating on {0}", Instantiated); boolean proceed = callback.solelyRunning(Instantiated); if(!proceed){ l.info("Callback denied permission to proceed"); return false; } shutDownHook(); startMonitoring(fc); return false; } catch (Exception e) { l.log(Level.INFO,"Error in locking",e ); return true; } } private void acquireFileLock(FileChannel fc)throws IOException{ fileLock = fc.tryLock(100,1024,false); if(fileLock==null){ long previousInstance = previousInstanceTime(fc); markInstance(Instantiated, fc); l.log(Level.INFO, "It seems an instance is already running, since : {0}", previousInstance); callback.alreadyRunning(previousInstance); throw new IOException("could not lock"); } markInstance(Instantiated, fc); } private long previousInstanceTime(FileChannel fc)throws IOException{ ByteBuffer bb = ByteBuffer.allocate(8); fc.position(0); int r = fc.read(bb); if(r<bb.capacity()){ //partial read l.log(Level.FINE, "Partial read ={0}", r); return 0; } bb.flip(); return bb.asLongBuffer().get(); } private boolean markInstance(long timeStamp, FileChannel fc)throws IOException{ l.log(Level.FINE, "making={0}", timeStamp); ByteBuffer bb = ByteBuffer.allocate(8); bb.putLong(timeStamp); bb.flip(); fc.position(0); int w = fc.write(bb); if(w<bb.capacity()){ //partial write l.log(Level.FINE, "Partial write ={0}", w); return false; } fc.force(true); l.info("done marking"); /*long prv = previousInstanceTime(fc); l.info("prv="+prv+" cur="+timeStamp);*/ return true; } private void shutDownHook(){ ShutdownHook shutdownHook = new ShutdownHook(); Runtime.getRuntime().addShutdownHook(shutdownHook); } private final class ShutdownHook extends Thread { @Override public void run() { try{ fileLock.close(); }catch(Exception a){ } synchronized (lock){ lock.notifyAll(); } } } private void startMonitoring(FileChannel fc){ l.info("starting looping"); while(Thread.currentThread() == thread){ try{ loopElement(fc); }catch(Exception a){ } tryWait(); } } private void loopElement(FileChannel fc)throws IOException{ long newTimeStamp = previousInstanceTime(fc); if(newTimeStamp > lastCall){ lastCall = newTimeStamp; callback.attemptedToRun(newTimeStamp); } } private void tryWait(){ synchronized (lock){ try{ lock.wait(300); }catch(InterruptedException ie){ } } } }