cn.codepub.redis.directory.RedisLockFactory.java Source code

Java tutorial

Introduction

Here is the source code for cn.codepub.redis.directory.RedisLockFactory.java

Source

package cn.codepub.redis.directory;

import cn.codepub.redis.directory.util.Constants;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.IOUtils;
import org.apache.lucene.store.*;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * <p>
 * Created by wangxu on 16/10/27 18:11.
 * </p>
 * <p>
 * Description: Mostly copied from NativeFSLockFactory. Using Java NIO to implements file lock, on certain NFS environments the
 * java.nio.* locks will
 * fail
 * (the lock
 * can incorrectly be double acquired)
 * </p>
 * <p>
 * The primary benefit of RedisLockFactory is that locks (not the lock file itsself) will be properly removed (by the OS) if
 * the JVM has an abnormal exit.
 * </p>
 *
 * @author Wang Xu
 * @version V1.0.0
 * @since V1.0.0 <br></br>
 * WebSite: http://codepub.cn <br></br>
 * Licence: Apache v2 License
 */
@Log4j2
public class RedisLockFactory extends LockFactory {
    private static final Set<String> LOCK_HELD = Collections.synchronizedSet(new HashSet<String>());
    private final Path lockFileDirectory = Paths.get(Constants.LOCK_FILE_PATH); // The underlying filesystem directory

    RedisLockFactory() throws IOException {
        if (!Files.isDirectory(this.lockFileDirectory)) {
            Files.createDirectories(lockFileDirectory); // create directory, if it doesn't exist
        }
    }

    @Override
    public Lock obtainLock(@NonNull Directory dir, String lockName) throws IOException {
        if (!(dir instanceof RedisDirectory)) {
            throw new IllegalArgumentException("Expect argument of type [" + RedisDirectory.class.getName() + "]!");
        }
        Path lockFile = lockFileDirectory.resolve(lockName);
        try {
            Files.createFile(lockFile);
            log.debug("Lock file path = {}", lockFile.toFile().getAbsolutePath());
        } catch (IOException ignore) {
            //ignore
            log.debug("Lock file already exists!");
        }
        final Path realPath = lockFile.toRealPath();
        final FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime();
        if (LOCK_HELD.add(realPath.toString())) {
            FileChannel fileChannel = null;
            FileLock lock = null;
            try {
                fileChannel = FileChannel.open(realPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
                lock = fileChannel.tryLock();
                if (lock != null) {
                    return new RedisLock(lock, fileChannel, realPath, creationTime);
                } else {
                    throw new LockObtainFailedException("Lock held by another program: " + realPath);
                }
            } finally {
                if (lock == null) {
                    IOUtils.closeQuietly(fileChannel);
                    clearLockHeld(realPath);
                }
            }
        } else {
            throw new LockObtainFailedException("Lock held by this virtual machine: " + realPath);
        }
    }

    private static void clearLockHeld(Path realPath) {
        boolean remove = LOCK_HELD.remove(realPath.toString());
        if (!remove) {
            throw new AlreadyClosedException("Lock path was cleared but never marked as held: " + realPath);
        }
    }

    private static class RedisLock extends Lock {
        final FileLock lock;
        final FileChannel fileChannel;
        final Path path;
        final FileTime creationTime;
        volatile boolean closed;

        RedisLock(FileLock lock, FileChannel channel, Path path, FileTime creationTime) {
            this.lock = lock;
            this.fileChannel = channel;
            this.path = path;
            this.creationTime = creationTime;
        }

        @Override
        public void close() throws IOException {
            if (closed) {
                return;
            }
            try (FileChannel channel = this.fileChannel; FileLock lock = this.lock) {
                Objects.requireNonNull(channel);
                Objects.requireNonNull(lock);
            } finally {
                closed = true;
                clearLockHeld(path);
            }
        }

        @Override
        public void ensureValid() throws IOException {
            if (closed) {
                throw new AlreadyClosedException("Lock instance already released: " + this);
            }
            if (!LOCK_HELD.contains(path.toString())) {
                throw new AlreadyClosedException("Lock path unexpectedly cleared from map: " + this);
            }
            if (!lock.isValid()) {
                throw new AlreadyClosedException("FileLock invalidated by an external force: " + this);
            }
            long size = fileChannel.size();
            if (size != 0) {
                throw new AlreadyClosedException("Unexpected lock file size: " + size + ", (lock=" + this + ")");
            }
            FileTime ctime = Files.readAttributes(path, BasicFileAttributes.class).creationTime();
            if (!creationTime.equals(ctime)) {
                throw new AlreadyClosedException("Underlying file changed by an external force at " + creationTime
                        + ", " + "(lock=" + this + ")");
            }
        }

        @Override
        public String toString() {
            return "RedisLock(path=" + path + ",impl=" + lock + ",ctime=" + creationTime + ")";
        }
    }
}