/* * Data Hub Service (DHuS) - For Space data distribution. * Copyright (C) 2013,2014,2015 GAEL Systems * * This file is part of DHuS software sources. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package fr.gael.dhus.util; import java.io.Closeable; import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileLock; import java.nio.channels.NonWritableChannelException; import java.nio.channels.OverlappingFileLockException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Objects; import java.util.concurrent.TimeoutException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * Manages a {@link FileLock} asynchronously. */ public class AsyncFileLock implements AutoCloseable, Closeable { private static final Logger LOGGER = LogManager.getLogger(AsyncFileLock.class); /** An FileChannel backing up our AsyncFileLock. */ private final FileChannel fileToLock; /** Wether close() has to close `fileToLock` */ private final boolean closeChannel; /** Lock on `fileToLock`. */ private FileLock fileLock = null; /** * Creates a new AsyncFileLock from the given FileChannel. * The channel must have been opened with the WRITE option. * @param file to lock. * @throws IOException I/O error happened. */ public AsyncFileLock(FileChannel file) throws IOException { Objects.requireNonNull(file); if (!file.isOpen()) { throw new IllegalArgumentException("param `file` is closed"); } fileToLock = file; closeChannel = false; } /** * Creates a new AsyncFileLock from a path to a file. * The file will be created if it does not exist. * The file will be used for locking purposes only. * @param p path to file to lock. * @throws IOException I/O error happened. */ public AsyncFileLock(Path p) throws IOException { Objects.requireNonNull(p); fileToLock = FileChannel.open(p, StandardOpenOption.CREATE, StandardOpenOption.WRITE); closeChannel = true; } /** * Returns wether the underlying file is locked, non-blocking. * @return true if the underlying file is locked. * @throws ClosedChannelException the underlying FileChannel has been closed. * @throws NonWritableChannelException the underlying FileChannel * hasn't been opened with the WRITE OpenOption. */ public boolean isLocked() throws IOException { try (FileLock fl = fileToLock.tryLock()) { return fl != null; } catch (OverlappingFileLockException e) { return true; } } /** * Locks the file (non-blocking). * @throws IOException while acquiring lock. */ public void tryObtain() throws IOException { if (fileLock != null && fileLock.isValid()) { // lock has already been obtained. return; } fileLock = fileToLock.tryLock(); } /** * Locks the file (blocking). * @throws IOException while acquiring lock. */ public void obtain() throws IOException { if (fileLock != null && fileLock.isValid()) { // lock has already been obtained. return; } fileLock = fileToLock.lock(); } /** * Locks the file, with a timeout (non-blocking). * @param timeout_ms timeout duration in milliseconds. * @throws IOException I/O exception occured. * @throws InterruptedException current thread interrupted. * @throws TimeoutException failed to obtain lock. */ public void obtain(long timeout_ms) throws IOException, InterruptedException, TimeoutException { Long quit_time = System.currentTimeMillis() + timeout_ms; if (fileLock != null && fileLock.isValid()) { // lock has already been obtained. return; } do { try { fileLock = fileToLock.tryLock(); return; } catch (OverlappingFileLockException e) { Thread.sleep(1000); } } while (System.currentTimeMillis() < quit_time); throw new TimeoutException(); } /** * Release a previously obtained lock. * @throws IOException If an I/O error occurs. */ public void release() throws IOException { if (fileLock != null) { fileLock.release(); } } // Autocloseable resource. @Override public void close() { if (fileLock != null) { try { fileLock.close(); } catch (Exception e) { LOGGER.warn("underlying close has thrown an exception", e); } } if (closeChannel) { try { fileToLock.close(); } catch (Exception e) { LOGGER.warn("underlying close has thrown an exception", e); } } } }