001    /* ============================================================
002     * JRobin : Pure java implementation of RRDTool's functionality
003     * ============================================================
004     *
005     * Project Info:  http://www.jrobin.org
006     * Project Lead:  Sasa Markovic (saxon@jrobin.org);
007     *
008     * (C) Copyright 2003-2005, by Sasa Markovic.
009     *
010     * This library is free software; you can redistribute it and/or modify it under the terms
011     * of the GNU Lesser General Public License as published by the Free Software Foundation;
012     * either version 2.1 of the License, or (at your option) any later version.
013     *
014     * Developers:    Sasa Markovic (saxon@jrobin.org)
015     *
016     *
017     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019     * See the GNU Lesser General Public License for more details.
020     *
021     * You should have received a copy of the GNU Lesser General Public License along with this
022     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023     * Boston, MA 02111-1307, USA.
024     */
025    
026    package org.jrobin.core;
027    
028    import java.io.IOException;
029    import java.nio.channels.FileLock;
030    import java.nio.channels.FileChannel;
031    
032    /**
033     * JRobin backend which is used to store RRD data to ordinary files on the disk. This backend
034     * is SAFE: it locks the underlying RRD file during update/fetch operations, and caches only static
035     * parts of a RRD file in memory. Therefore, this backend is safe to be used when RRD files should
036     * be shared between several JVMs at the same time. However, this backend is a little bit slow
037     * since it does not use fast java.nio.* package (it's still based on the RandomAccessFile class).
038     */
039    public class RrdSafeFileBackend extends RrdFileBackend {
040            private static final Counters counters = new Counters();
041    
042            private FileLock lock;
043    
044            /**
045             * Creates RrdFileBackend object for the given file path, backed by RandomAccessFile object.
046             *
047             * @param path Path to a file
048             * @throws IOException Thrown in case of I/O error
049             */
050            public RrdSafeFileBackend(String path, long lockWaitTime, long lockRetryPeriod)
051                            throws IOException {
052                    super(path, false);
053                    try {
054                            lockFile(lockWaitTime, lockRetryPeriod);
055                    }
056                    catch (IOException ioe) {
057                            super.close();
058                            throw ioe;
059                    }
060            }
061    
062            private void lockFile(long lockWaitTime, long lockRetryPeriod) throws IOException {
063                    long entryTime = System.currentTimeMillis();
064                    FileChannel channel = file.getChannel();
065                    lock = channel.tryLock(0, Long.MAX_VALUE, false);
066                    if (lock != null) {
067                            counters.registerQuickLock();
068                            return;
069                    }
070                    do {
071                            try {
072                                    Thread.sleep(lockRetryPeriod);
073                            }
074                            catch (InterruptedException e) {
075                                    // NOP
076                            }
077                            lock = channel.tryLock(0, Long.MAX_VALUE, false);
078                            if (lock != null) {
079                                    counters.registerDelayedLock();
080                                    return;
081                            }
082                    } while (System.currentTimeMillis() - entryTime <= lockWaitTime);
083                    counters.registerError();
084                    throw new IOException("Could not obtain exclusive lock on file: " + getPath() +
085                                    "] after " + lockWaitTime + " milliseconds");
086            }
087    
088            public void close() throws IOException {
089                    try {
090                            if (lock != null) {
091                                    lock.release();
092                                    lock = null;
093                                    counters.registerUnlock();
094                            }
095                    }
096                    finally {
097                            super.close();
098                    }
099            }
100    
101            /**
102             * Defines the caching policy for this backend.
103             *
104             * @return <code>false</code>
105             */
106            protected boolean isCachingAllowed() {
107                    return false;
108            }
109    
110            public static String getLockInfo() {
111                    return counters.getInfo();
112            }
113    
114            static class Counters {
115                    long locks, quickLocks, unlocks, locked, errors;
116    
117                    synchronized void registerQuickLock() {
118                            locks++;
119                            quickLocks++;
120                            locked++;
121                    }
122    
123                    synchronized void registerDelayedLock() {
124                            locks++;
125                            locked++;
126                    }
127    
128                    synchronized void registerUnlock() {
129                            unlocks++;
130                            locked--;
131                    }
132    
133                    synchronized void registerError() {
134                            errors++;
135                    }
136    
137                    synchronized String getInfo() {
138                            return "LOCKS=" + locks + ", " + "UNLOCKS=" + unlocks + ", " +
139                                            "DELAYED_LOCKS=" + (locks - quickLocks) + ", " + "LOCKED=" + locked + ", " +
140                                            "ERRORS=" + errors;
141                    }
142            }
143    }