All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.oneandone.stool.locking.LockManager Maven / Gradle / Ivy

There is a newer version: 4.0.3
Show newest version
/**
 * Copyright 1&1 Internet AG, https://github.com/1and1/
 *
 * 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.
 */
package net.oneandone.stool.locking;

import net.oneandone.stool.util.Processes;
import net.oneandone.sushi.cli.Console;
import net.oneandone.sushi.fs.file.FileNode;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.management.ManagementFactory;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.List;

/**
 * High-level management for the locks file. Factory for locks.
 * Thread-save and save against concurrent processes.
 */
public class LockManager extends Thread implements AutoCloseable {
    public static LockManager create(FileNode file, String comment, int timeout) {
        return new LockManager(file, new Process(pid(), comment), timeout);
    }

    public static int pid() {
        String str;
        int idx;

        // see http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id
        str = ManagementFactory.getRuntimeMXBean().getName();
        idx = str.indexOf('@');
        if (idx == -1) {
            throw new IllegalStateException("cannot guess pid from " + str);
        }
        return Integer.parseInt(str.substring(0, idx));
    }

    private final List active;
    private final FileNode file;
    private final Process self;
    /** seconds */
    private final int timeout;

    public LockManager(FileNode file, Process self, int timeout) {
        super("LockManager Shutdown Hook");
        this.active = new ArrayList<>();
        this.file = file;
        this.self = self;
        this.timeout = timeout;
        Runtime.getRuntime().addShutdownHook(this);
    }

    public synchronized Lock acquire(String name, Console console, Mode mode) throws IOException {
        Lock result;

        awaitLock(name, mode, console);
        result = new Lock(this, name, mode);
        active.add(result);
        return result;
    }

    /** for testing */
    public synchronized Lock acquireOpt(String name, Console console, Mode mode) throws IOException {
        try {
            return acquire(name, console, mode);
        } catch (LockException e) {
            return null;
        }
    }


    private void awaitLock(String name, Mode mode, Console console) throws IOException {
        Queue problem;
        int seconds;

        seconds = 0;
        while (true) {
            try {
                problem = tryLock(name, mode);
            } catch (IOException e) {
                throw new IOException("cannot acquire lock '" + name + "': " + e.getMessage(), e);
            }
            if (problem == null) {
                return;
            }
            if (seconds > timeout) {
                throw new LockException(name, problem);
            }
            if (seconds % 10 == 0) {
                console.info.println("trying to acquire lock '" + name + "'");
            }
            seconds++;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // continue
            }
        }
    }

    /** @return null or problematic queue otherwise */
    private Queue tryLock(String name, Mode mode) throws IOException {
        Store lf;
        Queue problem;

        try (RandomAccessFile raf = new RandomAccessFile(file.toPath().toFile(), "rw"); FileLock lock = raf.getChannel().lock()) {
            lf = Store.load(raf);
            problem = lf.tryLock(name, mode, self);
            if (problem != null) {
                return problem;
            }
            lf.save(raf);
            return null;
        }

    }

    public synchronized void release(Lock lock) throws IOException {
        Store lf;

        active.remove(lock);
        try (RandomAccessFile raf = new RandomAccessFile(file.toPath().toFile(), "rw"); FileLock l = raf.getChannel().lock()) {
            lf = Store.load(raf);
            lf.release(lock.name, lock.mode, self);
            lf.save(raf);
        }
    }

    /** @return list of stale processes */
    public synchronized List validate(Processes running, boolean repair) throws IOException {
        Store lf;
        List stale;

        stale = new ArrayList<>();
        try (RandomAccessFile raf = new RandomAccessFile(file.toPath().toFile(), "rw"); FileLock lock = raf.getChannel().lock()) {
            lf = Store.load(raf);
            for (Process process : lf.processes()) {
                if (!running.hasPid(Integer.toString(process.id))) {
                    stale.add(process.id);
                    if (repair) {
                        lf.releaseAll(process);
                    }
                }
            }
            if (repair) {
                lf.save(raf);
            }
        }
        return stale;
    }

    public synchronized boolean empty() throws IOException {
        Store lf;

        try (RandomAccessFile raf = new RandomAccessFile(file.toPath().toFile(), "rw");
             FileLock lock = raf.getChannel().lock()) {
            lf = Store.load(raf);
            return lf.isEmpty();
        }
    }

    public synchronized boolean hasExclusiveLocks(String ... locks) throws IOException {
        Store lf;
        Queue queue;

        try (RandomAccessFile raf = new RandomAccessFile(file.toPath().toFile(), "rw");
             FileLock l = raf.getChannel().lock()) {
            lf = Store.load(raf);
            for (String lock : locks) {
                queue = lf.lookup(lock);
                if (queue != null) {
                    if (queue.exclusiveCount > 0) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    /** close explicitly on LockManager object */
    public void close() {
        Runtime.getRuntime().removeShutdownHook(this);
        removeActive();
    }

    /** close implicitly during shutdown hook */
    @Override
    public void run() {
        removeActive();
    }

    private synchronized void removeActive() {
        Lock lock;

        while (!active.isEmpty()) {
            // caution: do no invoke active.remove(0) - lock.close() will remove it from the list
            lock = active.get(0);
            System.err.println("shutdown: unlocking " + lock.name);
            try {
                lock.close(); // does not need sushi
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy