org.geoserver.platform.resource.FileLockProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.platform.resource.FileLockProvider.java

Source

/* Copyright (c) 2014 OpenPlans - www.openplans.org. All rights reserved.
 * Copyright (c) 2008-2010 GeoSolutions
 * 
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 * 
 * Original from GeoWebCache 1.5.1 under a LGPL license 
 */

package org.geoserver.platform.resource;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;

import javax.servlet.ServletContext;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geoserver.platform.GeoServerResourceLoader;
import org.springframework.web.context.ServletContextAware;

/**
 * A lock provider based on file system locks
 * 
 * @author Andrea Aime - GeoSolutions
 */
public class FileLockProvider implements LockProvider, ServletContextAware {

    public static Log LOGGER = LogFactory.getLog(FileLockProvider.class);

    private File root;
    /**
     * The wait to occur in case the lock cannot be acquired
     */
    int waitBeforeRetry = 20;
    /**
     * max lock attempts
     */
    int maxLockAttempts = 120 * 1000 / waitBeforeRetry;

    MemoryLockProvider memoryProvider = new MemoryLockProvider();

    public FileLockProvider() {
        // base directory obtained from servletContext
    }

    public FileLockProvider(File basePath) {
        this.root = basePath;
    }

    public Resource.Lock acquire(final String lockKey) {
        // first off, synchronize among threads in the same jvm (the nio locks won't lock 
        // threads in the same JVM)
        final Resource.Lock memoryLock = memoryProvider.acquire(lockKey);

        // then synch up between different processes
        final File file = getFile(lockKey);
        try {
            FileOutputStream currFos = null;
            FileLock currLock = null;
            try {
                // try to lock
                int count = 0;
                while (currLock == null && count < maxLockAttempts) {
                    // the file output stream can also fail to be acquired due to the
                    // other nodes deleting the file
                    currFos = new FileOutputStream(file);
                    try {
                        currLock = currFos.getChannel().lock();
                    } catch (OverlappingFileLockException e) {
                        IOUtils.closeQuietly(currFos);
                        try {
                            Thread.sleep(20);
                        } catch (InterruptedException ie) {
                            // ok, moving on
                        }
                    } catch (IOException e) {
                        // this one is also thrown with a message "avoided fs deadlock"
                        IOUtils.closeQuietly(currFos);
                        try {
                            Thread.sleep(20);
                        } catch (InterruptedException ie) {
                            // ok, moving on
                        }
                    }
                    count++;
                }

                // verify we managed to get the FS lock
                if (count >= maxLockAttempts) {
                    throw new IllegalStateException(
                            "Failed to get a lock on key " + lockKey + " after " + count + " attempts");
                }

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Lock " + lockKey + " acquired by thread " + Thread.currentThread().getId()
                            + " on file " + file);
                }

                // store the results in a final variable for the inner class to use
                final FileOutputStream fos = currFos;
                final FileLock lock = currLock;

                // nullify so that we don't close them, the locking occurred as expected
                currFos = null;
                currLock = null;

                return new Resource.Lock() {

                    boolean released;

                    public void release() {
                        if (released) {
                            return;
                        }

                        try {
                            released = true;
                            if (!lock.isValid()) {
                                // do not crap out, locks usage is only there to prevent duplication of work
                                if (LOGGER.isDebugEnabled()) {
                                    LOGGER.debug("Lock key " + lockKey + " for releasing lock is unkonwn, it means "
                                            + "this lock was never acquired, or was released twice. "
                                            + "Current thread is: " + Thread.currentThread().getId() + ". "
                                            + "Are you running two instances in the same JVM using NIO locks? "
                                            + "This case is not supported and will generate exactly this error message");
                                    return;
                                }
                            }
                            try {
                                lock.release();
                                IOUtils.closeQuietly(fos);
                                file.delete();

                                if (LOGGER.isDebugEnabled()) {
                                    LOGGER.debug("Lock " + lockKey + " released by thread "
                                            + Thread.currentThread().getId());
                                }
                            } catch (IOException e) {
                                throw new IllegalStateException(
                                        "Failure while trying to release lock for key " + lockKey, e);
                            }
                        } finally {
                            memoryLock.release();
                        }
                    }

                    @Override
                    public String toString() {
                        return "FileLock " + file.getName();
                    }
                };
            } finally {
                if (currLock != null) {
                    currLock.release();
                    memoryLock.release();
                }
                IOUtils.closeQuietly(currFos);
                file.delete();
            }
        } catch (IOException e) {
            throw new IllegalStateException("Failure while trying to get lock for key " + lockKey, e);
        }
    }

    private File getFile(String lockKey) {
        File locks = new File(root, "filelocks"); // avoid same directory as GWC
        locks.mkdirs();
        String sha1 = DigestUtils.shaHex(lockKey);
        return new File(locks, sha1 + ".lock");
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        String data = GeoServerResourceLoader.lookupGeoServerDataDirectory(servletContext);
        if (data != null) {
            root = new File(data);
        } else {
            throw new IllegalStateException("Unable to determine data directory");
        }
    }

    @Override
    public String toString() {
        return "FileLockProvider " + root;
    }
}